/* 888 Story — theme tokens + layout. Dark is the default theme. */
:root {
  --radius: 10px;
  --maxw: 1128px;
  --font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "PingFang SC", "Hiragino Sans GB", sans-serif;
}
:root[data-theme="dark"] {
  color-scheme: dark;
  --bg: #000000;
  --card: #1b1f23;
  --card-2: #262b30;
  --border: rgba(255, 255, 255, 0.14);
  --text: rgba(255, 255, 255, 0.92);
  --text-2: rgba(255, 255, 255, 0.65);
  --text-3: rgba(255, 255, 255, 0.45);
  --accent: #70b5f9;
  --accent-ink: #06121f;
  --accent-hover: #9ccdfb;
  --gold: #e9c46a;
  --danger: #f28b82;
  --hover: rgba(255, 255, 255, 0.06);
  --scrim: rgba(0, 0, 0, 0.72);
  --shadow-pop: 0 8px 30px rgba(0, 0, 0, 0.5);
}
:root[data-theme="light"] {
  color-scheme: light;
  --bg: #f4f2ee;
  --card: #ffffff;
  --card-2: #f1f4f7;
  --border: rgba(0, 0, 0, 0.14);
  --text: rgba(0, 0, 0, 0.9);
  --text-2: rgba(0, 0, 0, 0.6);
  --text-3: rgba(0, 0, 0, 0.45);
  --accent: #0a66c2;
  --accent-ink: #ffffff;
  --accent-hover: #084d92;
  --gold: #9a7118;
  --danger: #b3261e;
  --hover: rgba(0, 0, 0, 0.05);
  --scrim: rgba(0, 0, 0, 0.55);
  --shadow-pop: 0 8px 30px rgba(0, 0, 0, 0.18);
}

* { box-sizing: border-box; margin: 0; padding: 0; }
html { -webkit-text-size-adjust: 100%; }
body {
  font-family: var(--font);
  background: var(--bg);
  color: var(--text);
  font-size: 14px;
  line-height: 1.5;
  min-height: 100dvh;
}
img, video { max-width: 100%; display: block; }
a { color: inherit; text-decoration: none; }
button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer; }
input, textarea { font: inherit; color: var(--text); }
ul { list-style-position: inside; }
.hidden { display: none !important; }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }
body.modal-open { overflow: hidden; }
.icon svg { width: 1em; height: 1em; display: block; }

/* ---------- top bar ---------- */
.topbar {
  position: sticky; top: 0; z-index: 100;
  background: var(--card);
  border-bottom: 1px solid var(--border);
}
.topbar-inner {
  max-width: var(--maxw); margin: 0 auto;
  display: flex; align-items: center; gap: 12px;
  padding: 0 16px; height: 54px;
}
.logo { display: flex; align-items: center; gap: 8px; flex: none; }
.logo-mark {
  background: linear-gradient(135deg, #f5d882, #c9962e);
  color: #241a04; font-weight: 800; font-size: 15px;
  padding: 4px 7px; border-radius: 7px; letter-spacing: 0.5px;
}
.logo-words { display: flex; flex-direction: column; line-height: 1.15; }
.logo-text { font-weight: 700; font-size: 16px; }
.logo-sub { font-size: 9.5px; letter-spacing: 1.6px; text-transform: uppercase; color: var(--text-2); }
/* Deliberately the quietest text in the top bar: readable if you look for it, invisible
   if you are not. Tabular figures so the number does not jiggle as it counts up. */
.logo-ver {
  font-size: 9px; letter-spacing: 0.3px; color: var(--text-3);
  margin-top: 2px; font-variant-numeric: tabular-nums; white-space: nowrap;
}
.search {
  flex: 0 1 280px; display: flex; align-items: center; gap: 8px;
  background: var(--card-2); border: 1px solid var(--border);
  border-radius: 999px; padding: 7px 12px; color: var(--text-2);
}
.search .icon { font-size: 15px; flex: none; }
.search input { border: 0; outline: none; background: transparent; width: 100%; color: var(--text); }
.search input::placeholder { color: var(--text-3); }
.topnav { margin-left: auto; display: flex; align-items: center; gap: 2px; }
.topbar-actions { display: flex; align-items: center; gap: 8px; }
.nav-item {
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  padding: 7px 12px; border-radius: 8px; color: var(--text-2);
  font-size: 11px; min-width: 58px;
}
.nav-item .icon { font-size: 21px; }
.nav-item:hover { color: var(--text); background: var(--hover); }
.nav-active { color: var(--text); }
.btn-signin { margin-left: 8px; }
/* gap: 0 because the label is one sentence split across two nodes, not two items —
   .btn's 6px flex gap would put a second space after "Join". */
.btn-join { margin-left: 8px; white-space: nowrap; gap: 0; }

/* ---------- hover panels on the top bar (House rules, About) ---------- */
/* The panel is padded rather than offset, so the gap between the button and the card is
   still part of the hover target. With `top: calc(100% + 8px)` the pointer crosses dead
   space on the way down and the panel closes underneath it. */
.navpop { position: relative; }
.navpop-panel {
  display: none; position: absolute; top: 100%; right: 0;
  width: 320px; max-width: calc(100vw - 24px);
  padding-top: 8px; z-index: 150; text-align: left;
}
.navpop-panel .card { box-shadow: var(--shadow-pop); }
/* Click/tap and the keyboard open it anywhere; hover only where there is a real pointer,
   or a tap on a phone leaves a sticky hover state behind and it will not close again.
   Deliberately not `:focus-within` — clicking the button focuses it, so focus would hold
   the panel open and the second click could never close it. */
.navpop.open .navpop-panel { display: block; }
@media (hover: hover) {
  .navpop:hover .navpop-panel { display: block; }
}
.navpop.open > .nav-item { color: var(--text); background: var(--hover); }
.me-menu { position: relative; margin-left: 6px; }
.me-btn { display: flex; align-items: center; padding: 4px; border-radius: 50%; }
.me-btn:hover { background: var(--hover); }
.me-drop {
  position: absolute; right: 0; top: calc(100% + 8px);
  background: var(--card); border: 1px solid var(--border); border-radius: 10px;
  padding: 14px; width: 230px; box-shadow: var(--shadow-pop); z-index: 150; text-align: left;
}
.me-name { font-weight: 700; }
.me-email { font-size: 12px; color: var(--text-2); margin-bottom: 6px; word-break: break-all; }
.me-hint { font-size: 12px; color: var(--text-2); margin-top: 6px; text-align: center; }
.me-hint.due { color: var(--gold); }

/* ---------- layout ---------- */
.container {
  max-width: var(--maxw); margin: 0 auto;
  padding: 20px 16px 24px;
  display: grid; grid-template-columns: 225px minmax(0, 1fr);
  gap: 24px; align-items: start;
}
.card { background: var(--card); border: 1px solid var(--border); border-radius: var(--radius); }
.rail-left { position: sticky; top: 74px; display: flex; flex-direction: column; gap: 12px; }

/* Gallery mode gives the pictures the whole page: the profile rail steps aside and the
   container widens past its usual max, so the masonry has more columns to spread across
   and each tile grows with the extra room. Keyed off body.gallery-mode, set in loadView. */
body.gallery-mode .rail-left { display: none; }
/* No max-width cap: the gallery fills the whole screen so the masonry spreads across as
   many columns as the width allows, and the reader sees as many photos as possible. */
body.gallery-mode .container { grid-template-columns: 1fr; max-width: none; }

/* Gallery mode strips the top bar back to just the Join call-to-action (or, for a
   signed-in owner, the avatar). Everything else — the logo, the search, the nav icons
   and the Atlas button — steps aside; the way home is the dock at the bottom instead. */
body.gallery-mode .logo,
body.gallery-mode .search,
body.gallery-mode .topnav,
body.gallery-mode #atlasSwitch,
/* Not on the gallery. That page has "Add media", which files a photo straight into the
   gallery — a different thing from writing a post, and two filled buttons side by side
   that both take a photo would be a genuine trap. The 888 dock is the way home. */
body.gallery-mode #topPostBtn { display: none; }
body.gallery-mode .topbar-actions { margin-left: auto; }

/* profile card */
.profile-card { overflow: hidden; text-align: center; padding-bottom: 16px; }
.banner {
  position: relative; height: 78px;
  background: radial-gradient(130% 170% at 18% 0%, #33290f 0%, #191410 50%, #0a0908 100%);
  display: flex; align-items: center; justify-content: center; gap: 12px;
}
.banner-888 {
  font-size: 36px; font-weight: 800; letter-spacing: 2px;
  background: linear-gradient(180deg, #f7e19b, #c9962e);
  -webkit-background-clip: text; background-clip: text; color: transparent;
}
.banner-cn { color: #e9c46a; font-size: 11px; letter-spacing: 3px; writing-mode: vertical-rl; }
.profile-avatar {
  position: relative; width: 72px; height: 72px; border-radius: 50%;
  margin: -36px auto 0; border: 3px solid var(--card); z-index: 2;
  background: linear-gradient(135deg, #f5d882, #c9962e);
  display: flex; align-items: center; justify-content: center; overflow: hidden;
}
.profile-avatar img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.profile-glyph { font-size: 30px; font-weight: 700; color: #241a04; }
/* camera badge, only shown to the site owner */
.profile-photo-btn {
  position: absolute; inset: auto 0 0 0; height: 26px;
  display: flex; align-items: center; justify-content: center;
  background: rgba(0, 0, 0, 0.55); color: #fff; opacity: 0; transition: opacity 0.15s;
}
.profile-photo-btn svg { width: 15px; height: 15px; }
.profile-avatar:hover .profile-photo-btn, .profile-photo-btn:focus-visible { opacity: 1; }
@media (hover: none) { .profile-photo-btn { opacity: 1; } }
.profile-name { font-size: 16px; font-weight: 700; margin-top: 8px; }
.profile-headline { font-size: 12px; color: var(--text-2); padding: 4px 14px 0; }
.profile-loc { font-size: 12px; color: var(--text-3); padding: 6px 14px 0; }
.profile-stats { font-size: 12px; color: var(--text-2); padding: 10px 14px 0; }
.profile-stats strong { color: var(--gold); }

/* mini cards / channels */
.mini-card { padding: 12px 10px 8px; text-align: left; }
.mini-card h2 { font-size: 13px; font-weight: 600; margin: 0 6px 6px; }
.channel { display: flex; gap: 10px; align-items: center; padding: 8px 6px; border-radius: 8px; }
a.channel:hover { background: var(--hover); }
.channel-badge {
  width: 34px; height: 34px; border-radius: 8px; flex: none;
  display: flex; align-items: center; justify-content: center;
  font-size: 11px; font-weight: 800;
}
.channel-badge.gold { background: linear-gradient(135deg, #f5d882, #c9962e); color: #241a04; }
.channel-badge.walnut { background: #4a3524; color: #e8d9c3; }
.channel-badge.red { background: #9e2b25; color: #ffe9b8; }
/* Slots 4 to 6. Jade, ink and plum carry on the same palette as the first three
   rather than reaching for colours that would sit outside the brand. */
.channel-badge.jade { background: #1d6b57; color: #d6f2e6; }
.channel-badge.ink { background: #223047; color: #cfe0f5; }
.channel-badge.plum { background: #5a2a4a; color: #f5daec; }
.channel-txt { display: flex; flex-direction: column; min-width: 0; }
.channel-txt strong { font-size: 13px; }
.channel-txt small { color: var(--text-2); font-size: 12px; }

/* About and house rules — now the contents of the two top-bar hover panels */
.about-card { padding: 14px; }
.about-card h2 { font-size: 14px; margin-bottom: 6px; }
.about-card p { font-size: 13px; color: var(--text-2); }
.rules-list { list-style: none; color: var(--text-2); font-size: 12.5px; display: grid; gap: 6px; }
.rules-list li::before { content: "八 "; color: var(--gold); font-weight: 700; }
/* A page footer since v2.03, not the tail of a right-hand rail. */
.site-footer { text-align: center; color: var(--text-3); font-size: 12px; line-height: 1.7; padding: 8px 16px 40px; }
/* The same version again, for phones, where the logo's words are hidden entirely. */
.footer-ver { display: block; font-size: 11px; font-variant-numeric: tabular-nums; opacity: 0.85; }

/* ---------- buttons ---------- */
.btn {
  border-radius: 999px; font-weight: 600; font-size: 14px;
  padding: 8px 18px; transition: background 0.15s, color 0.15s;
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
}
.btn-primary { background: var(--accent); color: var(--accent-ink); }
.btn-primary:hover { background: var(--accent-hover); }
.btn-primary:disabled { opacity: 0.45; cursor: default; }
.btn-outline { border: 1px solid var(--accent); color: var(--accent); }
.btn-outline:hover { background: color-mix(in srgb, var(--accent) 12%, transparent); }
.btn-ghost { color: var(--text-2); }
.btn-ghost:hover { color: var(--text); background: var(--hover); }
.btn-ghost:disabled { opacity: 0.5; cursor: default; }
.btn-block { width: 100%; margin-top: 10px; }
/* The "Post" button, in the top bar and again beside "Choose your topics". Filled in the
   page's own accent, so it reads as the primary action in whichever row it sits in — the
   same colour "Join with your email" uses in that corner while signed out. */
.btn-post { white-space: nowrap; gap: 5px; }
.btn-post .icon { width: 16px; height: 16px; }
.btn-post .icon svg { width: 100%; height: 100%; }
.btn-sm { padding: 6px 14px; font-size: 13px; }
.text-btn { font-size: 12px; font-weight: 600; color: var(--text-2); }
.text-btn:hover { color: var(--accent); }
.text-btn.danger:hover { color: var(--danger); }

/* ---------- avatars ---------- */
.avatar {
  border-radius: 50%; background: #5b6770; color: #fff;
  display: flex; align-items: center; justify-content: center;
  font-weight: 700; flex: none; user-select: none;
}
.avatar-gold { background: linear-gradient(135deg, #f5d882, #c9962e); color: #241a04; }
.avatar-photo { overflow: hidden; }
.avatar-photo img { width: 100%; height: 100%; object-fit: cover; border-radius: 50%; display: block; }

/* ---------- composer card ---------- */
.composer-card { padding: 12px 14px 6px; margin-bottom: 10px; }
.composer-row { display: flex; gap: 10px; align-items: center; }
.composer-trigger {
  flex: 1; text-align: left; border: 1px solid var(--border);
  border-radius: 999px; padding: 11px 16px; color: var(--text-2); font-weight: 600;
}
.composer-trigger:hover { background: var(--hover); }
.composer-quick { display: flex; justify-content: space-around; padding-top: 6px; }
.quick-btn {
  display: flex; gap: 7px; align-items: center;
  padding: 9px 10px; border-radius: 6px;
  color: var(--text-2); font-weight: 600; font-size: 13px;
}
.quick-btn:hover { background: var(--hover); }
.quick-btn .icon { font-size: 18px; }
.q-photo .icon { color: #70b5f9; }
.q-video .icon { color: #7fc15e; }
.q-link .icon { color: #e7a33e; }

/* ---------- feed / posts ---------- */
.feed-notice {
  margin-bottom: 10px; padding: 10px 14px; font-size: 13px; color: var(--text-2);
  display: flex; justify-content: space-between; align-items: center; gap: 10px;
}
.empty-card { padding: 30px 20px; text-align: center; color: var(--text-2); }
.post { margin-bottom: 10px; padding-top: 14px; }
.post-head { display: flex; gap: 10px; padding: 0 14px 8px; align-items: flex-start; }
.post-meta { flex: 1; min-width: 0; }
.post-name .name { font-weight: 600; font-size: 14px; }
.badge-founder {
  background: linear-gradient(135deg, #f5d882, #c9962e); color: #241a04;
  font-size: 10px; font-weight: 700; border-radius: 4px;
  padding: 1px 6px; margin-left: 6px; vertical-align: 1px;
}
.post-sub { font-size: 12px; color: var(--text-2); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.post-time-row { display: flex; align-items: baseline; gap: 4px; }
.post-time { font-size: 12px; color: var(--text-3); }
.post-time:hover { color: var(--accent); text-decoration: underline; }
.post-edited { font-size: 12px; color: var(--text-3); cursor: help; }
.post-tools { display: flex; gap: 2px; flex: none; }
.post-tools .icon-btn { color: var(--text-3); }
.post-tools .icon-btn:hover { color: var(--text); }
.post-tools .post-del:hover { color: var(--danger); }
.post-body { padding: 0 14px 10px; white-space: pre-wrap; overflow-wrap: break-word; }
.post-body a.link, .comment-body a.link { color: var(--accent); }
.post-body a.link:hover, .comment-body a.link:hover { text-decoration: underline; }
.post-body.clamped {
  display: -webkit-box; -webkit-line-clamp: 4; -webkit-box-orient: vertical;
  overflow: hidden; margin-bottom: 0; padding-bottom: 2px;
}
.see-more { color: var(--text-2); font-weight: 600; padding: 0 14px 10px; font-size: 13px; }
.see-more:hover { color: var(--accent); }

.post-media { display: grid; gap: 2px; background: var(--border); }
.post-media.count-1 { grid-template-columns: 1fr; }
.post-media.count-2, .post-media.count-4 { grid-template-columns: 1fr 1fr; }
.post-media.count-3 { grid-template-columns: 1fr 1fr; }
.post-media.count-3 > :first-child { grid-column: span 2; }
.post-media img, .post-media video { width: 100%; display: block; background: #000; }

/* A lone photo is shown whole. It used to be cropped into a fixed 520px box, which
   cut the top and bottom off the 4:5 portraits that make up most posts. Height now
   simply follows the picture's own shape, so 4:5, 1:1 and 16:9 all arrive intact
   and edge to edge.

   No height cap here on purpose: a cap in vh went wrong the moment the window was
   short, squeezing a 4:5 portrait into black side bars. Anything taller than 4:5 is
   held to that shape from app.js instead, which depends on the picture rather than
   on the size of the window. */
.post-media.count-1 img { height: auto; object-fit: contain; }

/* Video can't be measured the same way before it loads, so it keeps a plain cap. */
.post-media.count-1 video { height: auto; max-height: min(80vh, 700px); object-fit: contain; }

/* Several photos share a grid, so their cells have to agree on a shape and these
   stay cropped to fill. Clicking any of them opens the whole thing. */
.post-media:not(.count-1) img, .post-media:not(.count-1) video {
  aspect-ratio: 1; object-fit: cover;
}
/* The wide one at the top of a three-photo post. Needs to out-rank the square
   rule above it, hence the longer selector. */
.post-media.count-3 > :is(img, video):first-child { aspect-ratio: 16 / 9; }

.post-media img { cursor: zoom-in; }

.link-card { display: block; border-top: 1px solid var(--border); }
.link-card:hover .link-title { text-decoration: underline; }
.link-img { width: 100%; max-height: 280px; object-fit: cover; background: var(--card-2); }
.link-txt { padding: 10px 14px; background: var(--card-2); }
.link-title { font-weight: 600; font-size: 14px; overflow-wrap: break-word; }
.link-desc {
  font-size: 12px; color: var(--text-2); margin-top: 2px;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.link-domain { font-size: 12px; color: var(--text-3); margin-top: 4px; }

.post-counts { display: flex; gap: 12px; padding: 8px 14px; font-size: 12px; color: var(--text-2); min-height: 18px; }
.post-actions { display: flex; border-top: 1px solid var(--border); margin: 0 14px; padding: 2px 0; }
.action-btn {
  flex: 1; display: flex; align-items: center; justify-content: center; gap: 7px;
  padding: 10px 8px; border-radius: 6px;
  color: var(--text-2); font-weight: 600; font-size: 13px;
}
.action-btn:hover { background: var(--hover); color: var(--text); }
.action-btn .icon { font-size: 17px; }
.action-btn.active { color: var(--accent); }

/* ---------- comments ---------- */
.comments { padding: 4px 14px 14px; }
.comment-composer { display: flex; gap: 8px; align-items: flex-start; margin: 8px 0 4px; }
.comment-input {
  flex: 1; border: 1px solid var(--border); background: transparent;
  border-radius: 16px; padding: 8px 12px; resize: none; outline: none; min-height: 36px;
}
.comment-input:focus { border-color: var(--accent); }
.comment-composer .btn { align-self: flex-end; }
.comment-signin { margin: 10px 0 4px; width: 100%; }
.comment { display: flex; gap: 8px; margin-top: 10px; }
.comment-replies { margin-left: 40px; }
.comment-col { min-width: 0; flex: 1; }
.comment-bubble {
  background: var(--card-2); border-radius: 0 12px 12px 12px;
  padding: 7px 12px 8px; display: inline-block; max-width: 100%;
}
.comment-head { display: flex; align-items: center; gap: 6px; font-size: 12px; flex-wrap: wrap; }
.comment-head .name { font-weight: 600; font-size: 13px; }
.comment-time { color: var(--text-3); }
.comment-body { font-size: 13px; white-space: pre-wrap; overflow-wrap: break-word; }
.comment-tools { display: flex; gap: 14px; padding: 3px 6px 0; }

/* ---------- modals ---------- */
.modal-backdrop {
  position: fixed; inset: 0; background: var(--scrim); z-index: 200;
  display: flex; align-items: flex-start; justify-content: center;
  padding: 8vh 16px 16px; overflow-y: auto;
}
.modal {
  background: var(--card); border: 1px solid var(--border); border-radius: 12px;
  width: 100%; max-width: 410px; padding: 22px; position: relative;
  box-shadow: var(--shadow-pop);
}
.modal-wide { max-width: 552px; }
.ph-preview { display: flex; justify-content: center; padding: 14px 0 16px; }
.modal h2 { font-size: 18px; margin-bottom: 4px; }
.modal .muted { color: var(--text-2); font-size: 13px; margin-bottom: 8px; }
.modal label { display: block; font-size: 12px; font-weight: 600; color: var(--text-2); margin: 12px 0 4px; }
.modal input, .modal textarea {
  width: 100%; background: transparent;
  border: 1px solid var(--border); border-radius: 6px;
  padding: 9px 11px; outline: none;
}
.modal input:focus, .modal textarea:focus { border-color: var(--accent); }
.modal input[readonly] { color: var(--text-2); background: var(--card-2); }
.modal-x {
  position: absolute; top: 10px; right: 12px;
  font-size: 22px; line-height: 1; color: var(--text-2);
  width: 32px; height: 32px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
}
.modal-x:hover { background: var(--hover); color: var(--text); }
/* ---------- checkboxes, invite, members ---------- */
.check-row {
  display: flex; align-items: flex-start; gap: 9px;
  margin: 14px 0 4px; font-size: 13px; color: var(--text-2);
  font-weight: 400; cursor: pointer; line-height: 1.45;
}
.check-row input { width: 16px; height: 16px; margin: 1px 0 0; flex: none; accent-color: var(--accent); cursor: pointer; }

.modal #invLink { font-size: 13px; margin-bottom: 12px; }
.inv-more { margin-top: 14px; border-top: 1px solid var(--border); padding-top: 12px; }
.inv-more summary { font-size: 13px; color: var(--text-2); cursor: pointer; }
.inv-more summary:hover { color: var(--text); }
.modal #invMessage {
  margin-top: 10px; font-size: 13px; line-height: 1.5; resize: vertical;
  font-family: inherit; color: var(--text-2);
}

.mem-list { max-height: 46vh; overflow-y: auto; margin: 8px 0 14px; }
.mem-row {
  display: flex; align-items: baseline; gap: 10px;
  padding: 9px 2px; border-bottom: 1px solid var(--border); font-size: 13px;
}
.mem-row:last-child { border-bottom: 0; }
.mem-name { font-weight: 600; flex: none; max-width: 40%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mem-email { color: var(--text-2); flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mem-tag { flex: none; font-size: 11px; color: var(--accent); }
.mem-empty { color: var(--text-2); font-size: 13px; padding: 14px 2px; }

/* ---------- gallery ---------- */
.gallery-head { padding: 16px 16px 14px; margin-bottom: 12px; }
.gallery-headings h1 { font-size: 20px; font-weight: 700; letter-spacing: -0.01em; }
.gallery-headings .muted { font-size: 13px; margin-top: 3px; }
.gallery-bar {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; flex-wrap: wrap; margin-top: 14px;
}
.gallery-filters { display: flex; gap: 6px; }
.gal-filter {
  font-size: 13px; font-weight: 600; color: var(--text-2);
  padding: 7px 14px; border-radius: 999px; border: 1px solid var(--border);
}
.gal-filter:hover { background: var(--hover); color: var(--text); }
.gal-filter.gal-on { background: var(--text); color: var(--bg); border-color: var(--text); }

/* Folders sit on their own line under the type filters, so the two ways of narrowing
   the grid never read as one row of unrelated buttons. */
.gallery-folders {
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
  margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--border);
}
.gallery-folders:empty { display: none; }
.gal-folder {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 13px; font-weight: 600; color: var(--text-2);
  padding: 6px 12px; border-radius: 8px; border: 1px solid var(--border);
  max-width: 100%;
}
.gal-folder > span:first-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.gal-folder:hover { background: var(--hover); color: var(--text); }
.gal-folder.gal-on { background: var(--card-2); color: var(--text); border-color: var(--text-3); }
.gal-folder-n {
  flex: none; font-size: 11px; font-weight: 700; color: var(--text-3);
  background: var(--hover); padding: 1px 6px; border-radius: 999px;
}
.gal-folder-new { border-style: dashed; color: var(--text-3); }
.gal-folder-tools { display: inline-flex; gap: 10px; margin-left: 4px; }
.gal-folder-tools .text-btn { font-size: 12.5px; }

/* folder modal */
.fm-list { display: flex; flex-direction: column; gap: 2px; margin: 12px 0 14px; }
.fm-row {
  display: flex; align-items: baseline; gap: 10px; width: 100%; text-align: left;
  padding: 10px 12px; border-radius: 8px; font-size: 14px; color: var(--text);
}
.fm-row:hover:not(:disabled) { background: var(--hover); }
.fm-row:disabled { opacity: 0.65; cursor: default; }
.fm-row-name { font-weight: 600; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fm-row-note { flex: none; font-size: 12px; color: var(--text-3); }
.fm-row-here { flex: none; font-size: 12px; color: var(--accent); font-weight: 600; }

/* ---------- months, and picking several at once ---------- */
.gallery-actions { display: flex; gap: 8px; }
.gallery-groups { display: flex; flex-direction: column; gap: 18px; }
.gal-group-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; margin-bottom: 8px;
}
.gal-group-toggle {
  display: flex; align-items: center; gap: 8px;
  font-size: 14.5px; font-weight: 700; color: var(--text);
  padding: 4px 6px; margin-left: -6px; border-radius: 8px;
}
.gal-group-toggle:hover { background: var(--hover); }
.gal-group-n {
  font-size: 11px; font-weight: 700; color: var(--text-3);
  background: var(--hover); padding: 1px 7px; border-radius: 999px;
}
.gal-chev { display: flex; color: var(--text-2); transition: transform 0.15s; }
.gal-chev svg { width: 16px; height: 16px; }
.gal-group-toggle[aria-expanded="false"] .gal-chev { transform: rotate(-90deg); }

/* Sticks to the top under the header so the count and the move button stay reachable
   while scrolling through a year of photos. */
.gal-selectbar {
  position: sticky; top: 66px; z-index: 5;
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; flex-wrap: wrap;
  background: var(--card); border: 1px solid var(--border); border-radius: var(--radius);
  padding: 10px 14px; margin-bottom: 12px; box-shadow: var(--shadow-pop);
}
.gs-count { font-size: 14px; font-weight: 700; }
.gs-actions { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.gs-actions .btn { padding: 7px 14px; font-size: 13.5px; }

.gal-selectable { cursor: pointer; }
.gal-selectable img, .gal-selectable video { transition: transform 0.15s, opacity 0.15s; }
.gal-cell.gal-picked { outline: 3px solid var(--accent); outline-offset: -3px; }
.gal-cell.gal-picked img, .gal-cell.gal-picked video { opacity: 0.72; transform: scale(0.94); }
.gal-tick {
  position: absolute; top: 6px; left: 6px;
  width: 24px; height: 24px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: rgba(0, 0, 0, 0.45); border: 2px solid rgba(255, 255, 255, 0.85);
  color: transparent; pointer-events: none;
}
.gal-tick svg { width: 14px; height: 14px; }
.gal-picked .gal-tick { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }
/* Hovering a picked cell must not zoom it back out of its "picked" look. */
.gal-selectable:hover img, .gal-selectable:hover video { transform: none; }
.gal-cell.gal-picked:hover img, .gal-cell.gal-picked:hover video { transform: scale(0.94); }

/* Pinterest-style masonry: each tile keeps its own shape and the columns pack around
   them, rather than everything cropped to a square. CSS columns do the packing — no
   JavaScript, and it reflows on its own as the width changes. The vertical gap is a
   margin on the tile, because column-gap only sets the space between columns. */
.gallery-grid {
  column-width: 240px; column-gap: 10px;
}
.gal-cell {
  position: relative; overflow: hidden;
  border-radius: var(--radius); background: var(--card-2);
  cursor: pointer;
  /* Keep a tile from being split across the bottom of one column and the top of the
     next, and give it the vertical gap the columns don't. */
  break-inside: avoid; margin-bottom: 10px; width: 100%;
}
.gal-cell img, .gal-cell video {
  width: 100%; height: auto; display: block;
  transition: transform 0.25s;
}
.gal-cell:hover img, .gal-cell:hover video { transform: scale(1.04); }
.gal-cell:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.gal-play {
  position: absolute; inset: 0; margin: auto;
  width: 44px; height: 44px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: rgba(0, 0, 0, 0.55); color: #fff; pointer-events: none;
}
.gal-play svg { width: 20px; height: 20px; margin-left: 2px; }
.gal-caption {
  position: absolute; left: 0; right: 0; bottom: 0;
  padding: 18px 10px 8px; color: #fff;
  background: linear-gradient(to top, rgba(0, 0, 0, 0.78), transparent);
  pointer-events: none;
}
/* The clamp lives on an inner element on purpose. An absolutely positioned box has
   its display blockified, so -webkit-box never survives on .gal-caption itself and
   a long caption runs down over the picture instead of ending in an ellipsis. */
.gal-caption span {
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; font-size: 12px; line-height: 1.35;
}
.gal-tools {
  position: absolute; top: 4px; right: 4px; display: flex; gap: 2px;
  opacity: 0; transition: opacity 0.15s;
}
.gal-cell:hover .gal-tools, .gal-cell:focus-within .gal-tools { opacity: 1; }
.gal-tools .icon-btn {
  width: 32px; height: 32px; background: rgba(0, 0, 0, 0.6); color: #fff;
}
.gal-tools .icon-btn:hover { background: rgba(0, 0, 0, 0.85); }
.gal-tools .icon-btn svg { width: 16px; height: 16px; }
/* Touch screens have no hover, so the tools would otherwise be unreachable. */
@media (hover: none) { .gal-tools { opacity: 1; } }

/* The dock — the way back to the classic home view. Off by default; in gallery mode it
   floats, sticky, at the bottom centre of the screen, at every width, because gallery
   mode takes Home off the top bar. */
.gal-dock { display: none; }
body.gallery-mode .gal-dock {
  display: flex; position: fixed; z-index: 40;
  left: 50%; transform: translateX(-50%);
  bottom: calc(16px + env(safe-area-inset-bottom, 0px));
  padding: 6px; border-radius: 999px;
  background: color-mix(in srgb, var(--card) 88%, transparent);
  border: 1px solid var(--border);
  -webkit-backdrop-filter: blur(12px); backdrop-filter: blur(12px);
  box-shadow: 0 6px 22px rgba(0, 0, 0, 0.35);
}
/* Room so the last row of pictures can scroll clear of the dock. */
body.gallery-mode #galleryView { padding-bottom: 92px; }
.gal-dock-btn {
  display: flex; align-items: center; gap: 9px;
  padding: 7px 16px 7px 8px; border-radius: 999px;
  background: var(--card); border: 1px solid var(--border); color: var(--text);
  font-weight: 600; font-size: 14px;
}
.gal-dock-btn:hover { text-decoration: none; }
.gal-dock-mark {
  /* The same gold pill as the top-bar logo, so the dock reads as the same 888. */
  background: linear-gradient(135deg, #f5d882, #c9962e);
  color: #241a04; font-weight: 800; font-size: 14px; letter-spacing: 0.5px;
  padding: 5px 8px; border-radius: 8px;
}

/* ---------- lightbox ---------- */
/* Edge to edge on purpose: the picture gets the whole window and keeps its shape,
   so a 4:5 portrait fills the height and a 16:9 fills the width. Nothing is scaled
   past its real size, which would only make it soft. */
.lightbox { padding: 0; align-items: center; overflow: hidden; background: rgba(0, 0, 0, 0.94); }
.lightbox img { max-width: 100%; max-height: 100%; object-fit: contain; display: block; }
/* A video is capped rather than left to its own dimensions, so its controls stay on
   screen instead of sitting under the bottom edge of the window. */
.lightbox video { max-width: 100%; max-height: 92%; display: block; background: #000; }

/* A video carries its own play controls along its bottom edge, which the caption and
   the counter would otherwise sit on top of. For a video only, the viewer becomes a
   column and the strip is laid out under the picture instead of over it. Photos keep
   the overlay — a caption across the bottom of a photograph is the point. */
.lightbox.lb-video { flex-direction: column; justify-content: center; gap: 10px; }
.lightbox.lb-video video { max-height: 78%; }
.lightbox.lb-video .lightbox-foot { position: static; }
.lightbox-x, .lightbox-nav, .lightbox-foot { position: fixed; z-index: 2; }
.lightbox-x {
  top: 14px; right: 16px; width: 40px; height: 40px; font-size: 26px;
  background: rgba(0, 0, 0, 0.45); color: #fff;
}
.lightbox-x:hover { background: rgba(0, 0, 0, 0.8); color: #fff; }
.lightbox-nav {
  top: 50%; transform: translateY(-50%);
  width: 46px; height: 46px; border-radius: 50%; font-size: 30px; line-height: 1;
  display: flex; align-items: center; justify-content: center;
  background: rgba(0, 0, 0, 0.45); color: #fff;
}
.lightbox-nav:hover { background: rgba(0, 0, 0, 0.8); }
.lightbox-prev { left: 16px; }
.lightbox-next { right: 16px; }
/* Caption, the link back to the post, and the counter share one strip so they can
   never land on top of each other. It ignores clicks except on its own children,
   so a click beside the picture still closes the viewer. */
.lightbox-foot {
  bottom: 14px; left: 0; right: 0;
  display: flex; flex-direction: column; align-items: center; gap: 7px;
  padding: 0 60px; pointer-events: none;
}
.lightbox-foot > * { pointer-events: auto; }
.lightbox-caption {
  max-width: 640px; text-align: center;
  background: rgba(0, 0, 0, 0.55); color: rgba(255, 255, 255, 0.92);
  font-size: 13.5px; line-height: 1.4; padding: 7px 14px; border-radius: 10px;
}
.lightbox-post {
  background: rgba(0, 0, 0, 0.55); color: #fff;
  font-size: 13px; font-weight: 600; padding: 7px 15px; border-radius: 999px;
}
.lightbox-post:hover { background: rgba(0, 0, 0, 0.85); text-decoration: none; }
.lightbox-count {
  background: rgba(0, 0, 0, 0.5); color: rgba(255, 255, 255, 0.85);
  font-size: 13px; padding: 4px 12px; border-radius: 999px;
}
@media (max-width: 620px) {
  .lightbox-nav { width: 40px; height: 40px; font-size: 26px; }
  .lightbox-prev { left: 6px; }
  .lightbox-next { right: 6px; }
  /* Two columns on a phone, the way Pinterest itself narrows. A tighter gap keeps the
     tiles as large as the screen allows. */
  .gallery-grid { column-count: 2; column-width: auto; column-gap: 6px; }
  .gal-cell { margin-bottom: 6px; }
  /* The bar is stuck to the top while picking, so every row it wraps to is a row of
     photos pushed off screen. Two rows: the count, then everything you can press. */
  .gal-selectbar { padding: 9px 12px; gap: 8px; }
  .gs-actions { width: 100%; justify-content: space-between; gap: 6px; }
  .gs-actions .btn { padding: 7px 11px; font-size: 13px; }
  .gal-tick { width: 20px; height: 20px; top: 4px; left: 4px; }
  .gal-tick svg { width: 12px; height: 12px; }
  .gal-caption { padding: 14px 7px 6px; }
  .gal-caption span { font-size: 11px; -webkit-line-clamp: 1; }
}

.dev-notice {
  background: color-mix(in srgb, #e9c46a 16%, transparent);
  border: 1px solid #c9962e; font-size: 13px;
  border-radius: 8px; padding: 10px 12px; margin: 4px 0 8px;
}
.field-error { color: var(--danger); font-size: 12.5px; margin-top: 8px; }

/* composer modal specifics */
.modal #pcBody { border: 0; padding: 6px 2px; min-height: 110px; font-size: 15px; resize: vertical; margin-top: 6px; }
.pc-previews { display: grid; grid-template-columns: repeat(auto-fill, minmax(110px, 1fr)); gap: 8px; margin-top: 10px; }
.pc-cell { position: relative; border-radius: 8px; overflow: hidden; aspect-ratio: 1; background: var(--card-2); }
.pc-cell img, .pc-cell video { width: 100%; height: 100%; object-fit: cover; }
.pc-remove {
  position: absolute; top: 4px; right: 4px;
  background: rgba(0, 0, 0, 0.65); color: #fff;
  width: 24px; height: 24px; border-radius: 50%; font-size: 15px;
  display: flex; align-items: center; justify-content: center;
}
.pc-actions { display: flex; align-items: center; justify-content: space-between; margin-top: 14px; }
.pc-attach { display: flex; gap: 4px; }
.icon-btn {
  width: 40px; height: 40px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center; color: var(--text-2);
}
.icon-btn:hover { background: var(--hover); color: var(--text); }
.icon-btn svg { width: 20px; height: 20px; }

/* ---------- toast ---------- */
#toast {
  position: fixed; left: 50%; bottom: 26px;
  transform: translateX(-50%) translateY(16px);
  background: var(--card); color: var(--text);
  border: 1px solid var(--border); border-radius: 999px;
  padding: 10px 20px; font-size: 13.5px; font-weight: 600;
  opacity: 0; pointer-events: none;
  transition: opacity 0.2s, transform 0.2s;
  z-index: 300; box-shadow: var(--shadow-pop);
  max-width: 90vw; text-align: center;
}
#toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }

/* ---------- responsive ---------- */
/* The About card and the house rules are no longer a column that has to survive being
   squeezed — they are hover panels on the top bar, identical at every width. */
@media (max-width: 1080px) {
  /* Not enough width left for a tab rail as well as columns — it is only switched on
     above 1080px (see .rail-tabs). The button in the deck header is the way in here. */
  body.deck-mode .container { max-width: var(--maxw); grid-template-columns: 225px minmax(0, 1fr); }

  /* The labels come off here rather than at 820px, because Build made the bar one
     control too wide for the band in between. Measured at 821px — the last width that
     used to show labels — the row needed 820px before Build was added and 880px after,
     while .search was already pinned at its 49px floor with nothing left to give, so
     821–879px scrolled sideways. Hiding every label above the phone breakpoint buys far
     more than the 58px needed and keeps the bar one consistent set of icons; hiding only
     Build's label left the overflow exactly where it was, because the width came from
     the extra control, not from its words. */
  .nav-label { display: none; }
  /* The Post button loses its word in the same band, for the same reason the labels do:
     it is a control the row did not carry before, and at 821px .search is back on its
     49px floor with nothing left to give. Measured — with the word, and with the Atlas
     switch held to one line, the row wanted 852px against an 821px screen. The filled +
     is still the only solid button up there, so nothing about finding it changes. */
  .btn-post .post-long { display: none; }
  .btn-post { padding: 8px 12px; }
  /* 58px is a minimum sized to hold a word under the icon. With the words gone it is
     14px of nothing per control, and seven of those was still enough to overrun
     821–851px. 44px is the tap-target floor, the same figure the phone block uses. */
  .nav-item { min-width: 44px; }
}
@media (max-width: 820px) {
  .container { grid-template-columns: 1fr; padding-top: 12px; gap: 12px; }
  .rail-left { position: static; }
  /* The feed comes first on a phone. Nobody scrolls past a profile card and three
     channel links to find out whether a site is worth reading — and until v2.0 that
     is exactly what a phone visitor had to do. */
  .feed-col { order: -1; }
  /* flex-basis 0 and min-width 0, not `auto` — with `auto` the box refuses to shrink
     below its placeholder and elbows the Join button onto a row of its own. */
  .search { flex: 1 1 0; min-width: 0; }
  .search input { min-width: 0; }
  .logo-words { display: none; }
  /* .nav-label is already hidden from 1080px down — see that block. */
  .nav-item { min-width: 44px; padding: 10px; }
  input, textarea { font-size: 16px; }
  /* Seven controls, a logo and a search box will not fit across 375px, and shrinking the
     icons to make them would take the tap targets below 44px. The icons get a row of
     their own instead, spread across the full width, which also leaves room for the
     Invite button that only Henry sees. */
  .topbar-inner { flex-wrap: wrap; height: auto; padding: 8px 12px 4px; row-gap: 2px; }
  .topnav { order: 3; flex: 1 0 100%; margin-left: 0; justify-content: space-between; }
  .topbar-actions { margin-left: auto; }
  /* A 320px panel hung off a button in the middle of the bar would run off the left
     edge. Dropping .navpop out of the way lets the panel anchor to the (sticky, and so
     positioned) top bar instead and span the screen underneath it. */
  .navpop { position: static; }
  .navpop-panel { left: 12px; right: 12px; width: auto; max-width: none; }
  /* Just "Join" once there is no room for the sentence. */
  .btn-join { font-size: 13px; padding: 8px 14px; margin-left: 4px; }
  .btn-join .join-long { display: none; }
  /* The word goes, the + stays. A bare + is still a 40px target and still the only
     filled button in the corner, which is all it has to be on a phone. */
  .btn-post { font-size: 13px; padding: 8px 12px; }
  .btn-post .post-long { display: none; }
}
@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; animation: none !important; }
}

/* ================================================================
   v2.0 — topic columns

   Henry writes into many topics; a reader picks up to five and each becomes a
   column. On a phone the same idea is turned on its side: each topic is a row
   scrolled sideways with a thumb, because three columns at 375px is 110px each
   and that is a thumbnail and four words.
   ================================================================ */

/* The deck needs room, so on the home view the page gets wider. The extra first column
   is the book-tab rail — so this is switched on at exactly the width that shows the rail
   (see .rail-tabs) and nowhere else. Written as a bare rule it out-specified the
   narrower `@media (max-width: 1080px)` override further up the file, which left a
   48px track on screen with nothing in it and squeezed the profile card into it. */
@media (min-width: 1081px) {
  body.deck-mode .container { max-width: 1360px; grid-template-columns: 48px 225px minmax(0, 1fr); }
}

/* ---------- the book tabs ---------- */
/* Hidden by default and only turned on where there is width for it, rather than shown
   and then hidden again at each breakpoint. A `body.deck-mode .rail-tabs` rule
   out-specifies a bare `.rail-tabs` one, so the "hide it later" version silently lost
   and the rail stayed on screen on a phone. */
.rail-tabs { display: none; position: sticky; top: 74px; flex-direction: column; gap: 6px; }
@media (min-width: 1081px) {
  body.deck-mode .rail-tabs { display: flex; }
}
.booktab {
  position: relative; writing-mode: vertical-rl; text-orientation: mixed;
  background: var(--card); border: 1px solid var(--border); border-right-width: 0;
  border-radius: var(--radius) 0 0 var(--radius);
  padding: 12px 7px; min-height: 74px;
  font-size: 12px; font-weight: 600; color: var(--text-2); letter-spacing: 0.02em;
  display: flex; align-items: center; justify-content: center; gap: 8px;
  transition: color 0.15s ease, background 0.15s ease, transform 0.15s ease;
}
.booktab:hover { color: var(--text); background: var(--card-2); transform: translateX(-2px); }
.booktab::before {
  content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 3px;
  background: var(--topic-hue, var(--gold)); border-radius: 3px 0 0 3px;
}
.booktab-glyph { font-size: 11px; color: var(--topic-hue, var(--gold)); font-weight: 700; }
.rail-add {
  background: var(--card); border: 1px dashed var(--border); border-radius: var(--radius);
  padding: 10px 0; color: var(--text-3); font-size: 17px; font-weight: 600;
}
.rail-add:hover { color: var(--gold); border-color: var(--gold); }

/* ---------- deck header ---------- */
.deck-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 12px; margin-bottom: 10px; flex-wrap: wrap;
}
.deck-head-acts { display: flex; align-items: center; gap: 10px; }
.deck-title { font-size: 17px; font-weight: 700; margin: 0; }
.deck-sub { font-size: 12.5px; color: var(--text-3); margin: 3px 0 0; }

/* The phone's way into the drawer, since the tab rail needs width it hasn't got. */
.topic-strip { display: none; }
.ts-names { font-size: 12.5px; color: var(--text-2); min-width: 0; }

/* ---------- the columns ---------- */
/* Columns share the width evenly, but a column is capped at a readable measure. Without
   the cap, a reader holding one topic got a single 1000px-wide column, which made every
   tile thumbnail about 630px tall and cropped the picture to pieces. `--deck-cols` is
   set by app.js from however many columns are actually on screen. */
/* The deck fills the width it is given, whatever the reader is holding. It used to be
   capped at `columns × 420px`, which was right for three and left a third of the row
   black for two and two thirds of it black for one — the page looked unfinished at
   exactly the moment a reader had narrowed it down to what they cared about.
   Three columns is still the measure the design is drawn around, so that is the ceiling;
   below three, the spare width goes to the tiles instead of the column (see --tile-cols
   on .tc-list), which is what keeps one topic from becoming one enormous column with
   600px-tall thumbnails in it. That was the original reason for the cap and it still
   holds — this changes where the width goes, not whether it is spent. */
.deck {
  display: grid; grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr); gap: 10px; align-items: start;
  max-width: calc(3 * 420px + 2 * 10px);
}
.topic-col {
  background: var(--card); border: 1px solid var(--border); border-radius: var(--radius);
  display: flex; flex-direction: column; min-width: 0;
}
.tc-head {
  display: flex; align-items: center; gap: 7px; width: 100%;
  padding: 10px 11px; border-bottom: 1px solid var(--border);
  border-radius: var(--radius) var(--radius) 0 0; text-align: left;
}
.tc-head:hover { background: var(--hover); }
.tc-dot { width: 8px; height: 8px; border-radius: 50%; flex: none; background: var(--topic-hue, var(--gold)); }
.tc-name {
  font-size: 12.5px; font-weight: 700; flex: 1; min-width: 0;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.tc-count { font-size: 11px; color: var(--text-3); font-variant-numeric: tabular-nums; }
/* One tile per row when the reader holds three topics — the original column. With one
   or two, --tile-cols (set in renderDeck) turns the same list into a wall of tiles, so
   the width is spent on more posts on screen rather than on bigger ones. */
.tc-list { display: grid; grid-template-columns: repeat(var(--tile-cols, 1), minmax(0, 1fr)); }
/* The wall packs rather than lines up, using the same CSS-columns masonry the gallery
   uses — no JavaScript, and it reflows on its own. A plain grid was tried first and read
   badly: rows align to their tallest tile, so one photo beside two text-only posts left
   two holes, and a wall of holes is worse than the empty margin it replaced.
   Tiles in a single column are separated by a rule; in the wall a rule is wrong — the
   last row would carry a hairline under some tiles and not others — so the wall separates
   them with space instead and each tile becomes its own small card. */
.tc-list.tc-grid {
  display: block; column-count: var(--tile-cols, 1); column-gap: 8px; padding: 9px;
}
.tc-grid .tile {
  border-bottom: 0; border-radius: 8px; padding: 8px;
  background: var(--card-2);
  /* Keep a tile from splitting across the foot of one column and the head of the next,
     and give it the vertical gap that column-gap does not. */
  break-inside: avoid; margin-bottom: 8px;
}
.tc-grid .tile:hover { background: var(--hover); }
.tc-empty { padding: 22px 12px; text-align: center; color: var(--text-3); font-size: 12px; }
.tc-more {
  padding: 9px; font-size: 12px; font-weight: 600; color: var(--accent);
  border-top: 1px solid var(--border);
}
.tc-more:hover { background: var(--hover); }
.tc-more:disabled { color: var(--text-3); cursor: default; background: none; }

/* ---------- a tile ---------- */
.tile {
  display: block; width: 100%; text-align: left;
  padding: 9px; border-bottom: 1px solid var(--border);
  transition: background 0.12s ease;
}
.tile:last-child { border-bottom: 0; }
.tile:hover { background: var(--hover); }
.tile-thumb {
  position: relative; width: 100%; aspect-ratio: 16 / 10;
  border-radius: 7px; margin-bottom: 7px; overflow: hidden;
  background: var(--card-2);
}
/* `contain`, not `cover`. Most of Henry's posts are text graphics, and cropping one to
   fill the box cuts words off the edges — a tile that hides the sentence is no use as a
   preview. The tile keeps a fixed shape so the columns stay tidy; a picture that does
   not match it is letterboxed against the card instead of trimmed. */
.tile-thumb img { width: 100%; height: 100%; object-fit: contain; }
.tile-badge {
  position: absolute; right: 6px; bottom: 6px;
  background: rgba(0, 0, 0, 0.62); color: #fff;
  border-radius: 5px; font-size: 10px; padding: 2px 6px; font-weight: 700;
}
.tile-badge.left { left: 6px; right: auto; font-variant-numeric: tabular-nums; }
.tile-text { font-size: 12.5px; line-height: 1.42; margin: 0; }
.tile-text.no-thumb { font-size: 13px; }
.tile-meta {
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
  margin-top: 6px; font-size: 10.5px; color: var(--text-3);
}
.tile-also { border: 1px solid var(--border); border-radius: 20px; padding: 0 6px; font-weight: 600; }

/* ---------- topic colours ----------
   These ids must stay in step with TOPIC_COLOURS in server.js. Each is already in
   the site's palette, so nothing Henry picks can look foreign next to the rest. */
.topic-gold     { --topic-hue: #e9c46a; --topic-1: #c9962e; --topic-2: #5c4413; }
.topic-cinnabar { --topic-hue: #e0685f; --topic-1: #a92f28; --topic-2: #4a1512; }
.topic-sky      { --topic-hue: #70b5f9; --topic-1: #2f6ea8; --topic-2: #14283a; }
.topic-walnut   { --topic-hue: #c9a27a; --topic-1: #8a5f3c; --topic-2: #3d2a1a; }
.topic-amber    { --topic-hue: #f0b429; --topic-1: #a8760f; --topic-2: #3f2c06; }
.topic-plum     { --topic-hue: #b48ee0; --topic-1: #6f4aa3; --topic-2: #2c1c42; }
.topic-jade     { --topic-hue: #5fc9b8; --topic-1: #2a8578; --topic-2: #10322d; }
.topic-bamboo   { --topic-hue: #7fc47f; --topic-1: #3d8a3d; --topic-2: #163516; }
.topic-slate    { --topic-hue: #9fb0c0; --topic-1: #5b6b7a; --topic-2: #232c34; }

/* A post with no photo still needs something to look at in a column. */
.tile-thumb.placeholder { background: linear-gradient(145deg, var(--topic-1), var(--topic-2)); }
.tile-glyph {
  position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  font-size: 22px; font-weight: 700; color: rgba(255, 255, 255, 0.5);
}

/* ---------- the topic drawer ---------- */
.drawer-scrim { position: fixed; inset: 0; background: var(--scrim); z-index: 190; }
.topic-drawer {
  position: fixed; top: 0; left: 0; bottom: 0; width: min(340px, 88vw);
  background: var(--card); border-right: 1px solid var(--border);
  z-index: 191; display: flex; flex-direction: column; box-shadow: var(--shadow-pop);
  animation: drawer-in 0.24s cubic-bezier(0.4, 0, 0.2, 1);
}
@keyframes drawer-in { from { transform: translateX(-100%); } to { transform: none; } }
.td-head { padding: 16px 16px 10px; border-bottom: 1px solid var(--border); }
.td-head h2 { font-size: 16px; margin: 0 0 3px; }
.td-list { flex: 1; overflow-y: auto; padding: 8px; display: flex; flex-direction: column; gap: 4px; }
.td-row {
  display: flex; align-items: center; gap: 10px; width: 100%; text-align: left;
  padding: 9px 10px; border-radius: 8px; border: 1px solid transparent;
}
.td-row:hover { background: var(--hover); }
.td-row.on { border-color: var(--border); background: var(--card-2); }
.td-row:disabled { opacity: 0.42; cursor: default; }
.td-badge {
  width: 30px; height: 30px; border-radius: 7px; flex: none;
  display: flex; align-items: center; justify-content: center;
  font-size: 12px; font-weight: 700; color: rgba(255, 255, 255, 0.9);
  background: linear-gradient(135deg, var(--topic-1), var(--topic-2));
}
.td-txt { flex: 1; min-width: 0; }
.td-txt strong { display: block; font-size: 13px; font-weight: 600; }
.td-txt small { display: block; font-size: 11px; color: var(--text-3); }
.td-check {
  width: 19px; height: 19px; border-radius: 5px; flex: none;
  border: 1.5px solid var(--border);
  display: flex; align-items: center; justify-content: center; font-size: 12px;
}
.td-row.on .td-check { background: var(--gold); border-color: var(--gold); color: #241a04; }
:root[data-theme="light"] .td-row.on .td-check { color: #fff; }
.td-foot {
  padding: 12px 16px; border-top: 1px solid var(--border);
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
}

/* ---------- topic chips on a full post ---------- */
.post-topics { display: flex; flex-wrap: wrap; gap: 6px; margin: 8px 0 0; }
.post-topic {
  font-size: 11px; font-weight: 600; padding: 2px 9px; border-radius: 20px;
  border: 1px solid var(--topic-hue, var(--border)); color: var(--topic-hue, var(--text-2));
}

/* ---------- phone: the same idea, turned on its side ---------- */
@media (max-width: 820px) {
  body.deck-mode .container { grid-template-columns: 1fr; }
  /* Rows, not columns — the cap above is a columns-only idea. */
  .deck { max-width: none; }
  .deck-head { margin-bottom: 6px; }
  /* The strip already offers "Change" — two of the same control is clutter. */
  .deck-head .btn { display: none; }
  .topic-strip {
    display: flex; align-items: center; justify-content: space-between; gap: 10px;
    padding: 9px 11px; margin-bottom: 12px;
    background: var(--card); border: 1px solid var(--border); border-radius: var(--radius);
  }
  .deck { grid-auto-flow: row; grid-auto-columns: auto; gap: 16px; }
  .topic-col { background: none; border: 0; border-radius: 0; }
  .tc-head { padding: 0 2px 8px; border-bottom: 0; background: none; }
  /* `display: flex` is restated, and `.tc-grid` is named alongside, because the desktop
     rules now make this a grid — and a wall of tiles, when the reader holds one or two
     topics. Neither idea survives a 375px screen: the phone turns every topic on its side
     into one row you swipe. Both have to be undone by name, because `.tc-list.tc-grid`
     out-specifies a bare `.tc-list` and a media query adds no specificity of its own. */
  .tc-list, .tc-list.tc-grid {
    display: flex; flex-direction: row; gap: 9px; overflow-x: auto;
    column-count: initial; padding: 0 0 4px;
    scroll-snap-type: x mandatory;
    -webkit-overflow-scrolling: touch; scrollbar-width: none;
  }
  .tc-list::-webkit-scrollbar { display: none; }
  .tile, .tc-grid .tile {
    flex: 0 0 152px; width: 152px; scroll-snap-align: start; margin-bottom: 0;
    padding: 9px;
    background: var(--card); border: 1px solid var(--border); border-radius: var(--radius);
  }
  .tc-more { border-top: 0; padding: 8px 2px 0; text-align: left; }
  .tc-empty { text-align: left; padding: 12px 2px; }
}

/* ---------- topic picker in the composer ---------- */
.pc-topics { margin-top: 12px; }
.pc-topics-label {
  display: block; font-size: 11px; font-weight: 700; letter-spacing: 0.07em;
  text-transform: uppercase; color: var(--text-3); margin-bottom: 7px;
}
.pc-topic-picks { display: flex; flex-wrap: wrap; gap: 6px; }
.pc-topic {
  font-size: 12px; font-weight: 600; padding: 5px 12px; border-radius: 20px;
  border: 1px solid var(--border); color: var(--text-2);
}
.pc-topic:hover { background: var(--hover); color: var(--text); }
.pc-topic[aria-pressed="true"] {
  border-color: var(--topic-hue); color: var(--topic-hue);
  background: color-mix(in srgb, var(--topic-hue) 12%, transparent);
}
.pc-topics-hint { font-size: 11.5px; margin-top: 7px; }

/* Rail tabs and the deck header button are hidden on a phone; the strip is the way in.
   Kept last so it cannot be undone by anything above it. */
@media (max-width: 820px) {
  .pc-topic { padding: 7px 13px; }
}
