/* ============================================================================
   LinePod Newsroom — design system
   Dark-first "broadcast control room": deep blue-black grounds, depth from
   TONAL ELEVATION + soft glow (never drop shadows, which die on dark), a cyan
   signal line, and red reserved strictly for ON AIR. All styling lives here —
   the CSP is `style-src 'self'`, so inline <style> and style="" are blocked.
   ========================================================================== */

:root {
  /* grounds — tonal steps, slight blue bias so neutrals read as chosen */
  --bg:         #070A0E;
  --bg-2:       #0A0E13;
  --surface:    #0E141B;   /* cards */
  --surface-2:  #131A22;   /* inputs, insets */
  --surface-3:  #1A222C;   /* hover / active */
  --glass:      rgba(255,255,255,.045);
  --glass-line: rgba(255,255,255,.08);

  --border:        #1D2732;
  --border-strong: #2A3542;

  /* ink — muted passes 4.5:1 on --bg */
  --text:   #EDF1F7;
  --muted:  #9AA7B7;
  /* Was #667382, which measured 4.10:1 on --bg and 3.82:1 on --surface —
     under the 4.5:1 WCAG 1.4.3 needs for normal text, and it is used for real
     content (character counters, section labels, the "starred by" heading,
     timestamps). Lifted to the DIMMEST value that clears 4.5:1 against the
     worst background it ever sits on, so it still reads as the quiet tone it
     was meant to be: 4.53:1 on --surface, 4.86:1 on --bg. Every other token
     already passed. (Measured 2026-07-28 — recompute before changing it.) */
  --faint:  #727F8E;

  /* brand */
  --accent:      #22D3EE;   /* the line */
  --accent-2:    #38BDF8;   /* gradient partner */
  --accent-ink:  #04171C;   /* text on a cyan fill */
  --accent-soft: rgba(34,211,238,.12);
  --accent-glow: rgba(34,211,238,.30);

  --on-air: #EF4444;        /* red = ON AIR, only */
  --good:   #34D399;
  --warn:   #FBBF24;
  --danger: #F87171;

  --radius:    14px;
  --radius-sm: 10px;
  --radius-pill: 999px;

  --shadow:      0 12px 34px rgba(0,0,0,.55);
  --shadow-soft: 0 6px 20px rgba(0,0,0,.40);
  --ring: 0 0 0 3px var(--accent-soft);

  --font: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial,
          'Apple Color Emoji', 'Segoe UI Emoji', sans-serif;
  --maxw: 900px;
}

* { box-sizing: border-box; margin: 0; padding: 0; }

html { -webkit-text-size-adjust: 100%; }

/* ---- Dynamic Type, in the iPhone app only ---------------------------------
   The app turns OFF pinch-to-zoom (it should feel like an app, not a web
   page), and that is only defensible if something replaces it. This is that
   something: `-apple-system-body` resolves to the reader's own iOS text-size
   setting, so the root size — and every rem in this sheet with it — follows
   Accessibility ▸ Display & Text Size. Set it once, get it everywhere, with
   no pinching on each screen. That is how a native app meets WCAG 1.4.4
   ("resize text up to 200%") without a zoom gesture.

   SCOPED to .ios-app on purpose. On the open web nobody's zoom is taken away,
   so the browser's own zoom already satisfies the same requirement, and
   forcing the macOS system body size on desktop visitors would shrink the
   whole site for no gain. The class is added by shared.js when it sees the
   app's cookie.

   font: sets family AND size, so the family is restored on the next line —
   the brand face must not change with the text size. */
html.ios-app {
  font: -apple-system-body;
  font-family: var(--font);
}

body {
  font-family: var(--font);
  color: var(--text);
  background: var(--bg);
  /* Depth without shadows: two faint cyan light sources up top + a floor. */
  background-image:
    radial-gradient(1200px 640px at 50% -8%, rgba(34,211,238,.10), transparent 60%),
    radial-gradient(760px 460px at 100% 4%, rgba(56,189,248,.06), transparent 55%),
    linear-gradient(180deg, var(--bg) 0%, var(--bg-2) 100%);
  background-attachment: fixed;
  min-height: 100vh;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }

:focus-visible { outline: none; box-shadow: var(--ring); border-radius: 6px; }

::selection { background: var(--accent-soft); color: var(--text); }

.wrap { max-width: var(--maxw); margin: 0 auto; padding: 22px 18px 72px; }

/* ---- top bar + left side rail (owner 2026-07-28, Reddit reference) ----
   The bar is a <body> child (shared.js moves it there) spanning the full
   viewport: wordmark left, votes badge + account chip right, nothing else.
   The pages live in the rail hugging the LEFT EDGE — the wrap-prone top tab
   row is gone. Desktop (≥900px): body.hasrail becomes a two-column grid,
   rail sticky in the first column, content centring in the second. Below
   that: the same rail is a drawer behind the ☰ button. Every item is always
   reachable — a scrolling tab strip once hid Studio/Admin off the edge, and
   visibility is never traded for tidiness again. */
nav {
  display: flex; align-items: center; gap: 8px; flex-wrap: nowrap;
  padding: 13px 18px;
  border-bottom: 1px solid var(--border);
  position: sticky; top: 0; z-index: 40;
  background: color-mix(in srgb, var(--bg) 82%, transparent);
  backdrop-filter: saturate(140%) blur(12px);
}
nav .brand, .railbrand {
  font-weight: 800; font-size: 1.06rem; margin-right: 16px; color: var(--text);
  letter-spacing: -.01em; display: inline-flex; align-items: center; gap: 8px;
  white-space: nowrap;
}
nav .brand span, .railbrand span { color: var(--accent); }
/* The wordmark is a link for signed-in patrons — it opens the Newsroom.
   It must still read as a wordmark, not as a nav link. */
a.brand, a.brand:hover { text-decoration: none; }
a.brand:hover span { color: var(--accent-2); }
/* the signed-out "Sign in" chip is the one .tab left */
nav a.tab {
  padding: 7px 13px; border-radius: var(--radius-sm); color: var(--muted);
  font-size: .93rem; font-weight: 500; transition: background .15s, color .15s;
  margin-left: auto;
}
nav a.tab:hover { color: var(--text); text-decoration: none; background: var(--glass); }
.navright { display: flex; align-items: center; gap: 10px; flex: none; margin-left: auto; }
/* HOST APP ONLY: the native show-switcher capsule floats over the top-right
   corner of this page (owner 2026-07-31 — "overlapping elements": it sat on
   the bell and the profile photo). 140px ≈ capsule + its inset, on the same
   measurement as the iPad rundown header's trailing padding. */
.ios-host .navright { margin-right: 140px; }
nav .who { color: var(--muted); font-size: .84rem; }

/* the rail itself — one list of pages, staff tools grouped under a label */
.siderail { display: flex; flex-direction: column; gap: 2px; padding: 16px 14px;
  overscroll-behavior: contain; }
.siderail .railbrand { margin: 2px 8px 12px; }
.ritem { display: flex; align-items: center; gap: 13px; padding: 11px 15px;
  border-radius: var(--radius-sm); color: var(--muted); font-weight: 600;
  font-size: 1rem; }
.ritem:hover { background: var(--glass); color: var(--text); text-decoration: none; }
.ritem.active { background: var(--surface-3); color: var(--text);
  box-shadow: inset 0 0 0 1px var(--border); }
/* Sections of the tool you are currently in, nested under it (owner
   2026-07-28). Indented and quieter than a top-level destination, with a rule
   running down the left so the group reads as "inside this", not as seven more
   places to go. The empty .rico still occupies its 24px, which is what keeps
   these labels aligned with the parent's text rather than its icon. */
.ritem.rsub { font-size: .92rem; font-weight: 550; padding: 8px 15px 8px 0;
  margin-left: 27px; gap: 0; border-left: 1px solid var(--border);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0; }
.ritem.rsub .rico { width: 16px; }
.ritem.rsub.active { background: var(--glass); box-shadow: none;
  border-left-color: var(--accent); color: var(--text); }
.rico { flex: none; width: 24px; text-align: center; font-size: 1.1rem; line-height: 1; }
.siderail .railgroup { padding: 16px 13px 6px; }
/* the tutorial, pinned to the foot of the rail under a line */
.railfoot { margin-top: auto; padding-top: 12px; border-top: 1px solid var(--border); }
.railfoot .ritem { color: var(--faint); }
.railfoot .ritem:hover, .railfoot .ritem.active { color: var(--text); }

/* ☰ — drawer button; the desktop grid hides it */
.navburger { background: none; border: 0; color: var(--text); font-size: 1.12rem;
  cursor: pointer; padding: 5px 10px; border-radius: 8px; line-height: 1;
  margin-left: -6px; }
.navburger:hover { background: var(--glass); }
body.noscroll { overflow: hidden; }

@media (min-width: 900px) {
  body.hasrail { display: grid; grid-template-columns: 264px minmax(0, 1fr);
    grid-template-rows: auto 1fr; }
  /* Collapsed (owner 2026-07-30): the rail folds away and the content takes
     the full width. The ☰ phones already use is the same control here —
     one menu button everywhere, remembered per device. */
  body.hasrail.railmini { grid-template-columns: 0 minmax(0, 1fr); }
  body.hasrail.railmini > .siderail { display: none; }
  body.hasrail > nav { grid-area: 1 / 1 / 2 / 3; }
  body.hasrail > .wrap { grid-area: 2 / 2; width: 100%; }
  /* sticky under the measured bar height (--navh, set by shared.js), so the
     rail rides the viewport while the content scrolls */
  body.hasrail > .siderail { grid-area: 2 / 1; position: sticky;
    top: var(--navh, 59px); height: calc(100vh - var(--navh, 59px));
    overflow-y: auto; border-right: 1px solid var(--border); }
  /* the burger stays visible on desktop now — it's the collapse toggle */
  .railback, .siderail .railbrand { display: none; }
}
@media (max-width: 899.98px) {
  /* the drawer: same element, slid off the left edge until ☰ */
  .siderail { position: fixed; top: 0; bottom: 0; left: 0;
    width: min(78vw, 300px); z-index: 130; padding-top: 16px;
    background: var(--bg-2); border-right: 1px solid var(--border-strong);
    transform: translateX(-103%); transition: transform .22s ease;
    overflow-y: auto; box-shadow: var(--shadow); }
  .siderail.open { transform: none; }
  .railback { display: none; position: fixed; inset: 0;
    background: rgba(3, 7, 12, .62); z-index: 120; }
  .railback.open { display: block; }
}

/* ---- type ------------------------------------------------------------- */
h1 { font-size: 1.5rem; font-weight: 750; letter-spacing: -.02em; margin-bottom: 6px; line-height: 1.2; text-wrap: balance; }
h2 { font-size: 1.12rem; font-weight: 700; letter-spacing: -.01em; margin: 30px 0 12px; }
.hint { color: var(--muted); font-size: .9rem; margin-bottom: 18px; max-width: 60ch; }

/* Page header: name and blurb SIDE BY SIDE, split by a hairline (owner
   2026-07-27). Baseline-aligned so the two type sizes sit on one line
   together; the blurb keeps its reading measure instead of running the whole
   width. Under 720px it stacks — beside a narrow column it reads worse. */
.pagehead { display: flex; align-items: baseline; gap: 18px; flex-wrap: wrap;
  margin-bottom: 16px; }
.pagehead h1 { flex: none; margin-bottom: 0; }
.pagehead .hint { flex: 1 1 340px; min-width: 0; margin-bottom: 0;
  border-left: 1px solid var(--border-strong); padding-left: 18px;
  max-width: 64ch; text-wrap: pretty; }
@media (max-width: 720px) {
  .pagehead { display: block; margin-bottom: 14px; }
  .pagehead h1 { margin-bottom: 5px; }
  .pagehead .hint { border-left: 0; padding-left: 0; }
}

.eyebrow {
  color: var(--accent); font-size: .72rem; font-weight: 700;
  letter-spacing: .18em; text-transform: uppercase;
}

/* ---- cards / feed ----------------------------------------------------- */
.card {
  background: linear-gradient(180deg, var(--surface) 0%, var(--bg-2) 140%);
  border: 1px solid var(--border); border-radius: var(--radius);
  padding: 16px 18px; margin-bottom: 12px;
  box-shadow: inset 0 1px 0 var(--glass-line);
  transition: border-color .15s, transform .15s;
}
.card:hover { border-color: var(--border-strong); }

.story { display: flex; gap: 16px; }
.story img.thumb {
  width: 104px; height: 72px; object-fit: cover; border-radius: var(--radius-sm);
  flex: none; background: var(--surface-2); border: 1px solid var(--border);
}
.story .body { flex: 1; min-width: 0; }
.story .title { font-weight: 650; line-height: 1.35; font-size: 1.02rem; }
.story .title a { color: var(--text); }
.story .title a:hover { color: var(--accent); text-decoration: none; }
/* On the thread page the headline IS the way to the story (owner 2026-08-03).
   It has to still read as a headline, so it keeps the title's weight and colour
   and earns its underline on hover/focus rather than wearing one by default —
   a permanently underlined headline reads as a mistake, not an invitation. */
a.title.titlelink {
  display: block; color: var(--text); text-decoration: none; cursor: pointer;
}
a.title.titlelink:hover { color: var(--accent); text-decoration: underline; }
a.title.titlelink:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 3px;
}
.story .meta { color: var(--muted); font-size: .8rem; margin-top: 6px; display: flex; gap: 10px; flex-wrap: wrap; align-items: center; }

.pill {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: .72rem; font-weight: 650; padding: 2px 9px;
  border-radius: var(--radius-pill); border: 1px solid var(--border); color: var(--muted);
  letter-spacing: .01em;
}
.pill.topic  { border-color: color-mix(in srgb, var(--accent) 40%, transparent); color: var(--accent); background: var(--accent-soft); }
.pill.staff  { border-color: color-mix(in srgb, var(--warn) 45%, transparent); color: var(--warn); }
.pill.warn   { border-color: color-mix(in srgb, var(--warn) 45%, transparent); color: var(--warn); }
.pill.question { border-color: color-mix(in srgb, var(--accent) 45%, transparent); color: var(--accent); }
.pill.question.answered { border-color: color-mix(in srgb, var(--good) 50%, transparent); color: var(--good); }
/* Repetition killer: the "N outlets" pill is a BUTTON (it opens the
   also-covered-by popover), so it needs the pill look re-stated over the
   browser's button chrome, plus a cursor and the popover anchor. */
.pill.outlets {
  position: relative; cursor: pointer; background: none;
  font: inherit; font-size: .72rem; font-weight: 650;
}
.pill.outlets .starpop { text-transform: none; letter-spacing: normal; }
/* Found in the 2026-07-30 screenshot pass: .starpop's default direction
   (bottom: 100%, opens UPWARD) exists for the star pile, which sits near a
   card's bottom action row — opening up lands it in the card's own free
   space. The outlet pill sits at the TOP of a card, right under the title,
   so the same upward-opening popover walked straight onto the title text it
   belongs to. Opens DOWNWARD here instead — same logic, opposite direction:
   move toward the card's own free space, not over content someone's reading. */
.pill.outlets .starpop { bottom: auto; top: calc(100% + 8px); right: auto; left: 0; }
/* Admin activity stream + presence (2026-07-29). */
.presencecount b { font-size: 1.6rem; }
.actline {
  display: flex; gap: 10px; align-items: flex-start;
  padding: 8px 0; border-bottom: 1px solid var(--border);
}
.actico { flex: none; width: 1.4em; text-align: center; }
.actbody { min-width: 0; flex: 1; }
.actbody a { overflow-wrap: anywhere; }
/* Mod actions ride the right edge of a line and never squeeze the text. */
.actacts { flex: none; display: flex; gap: 6px; align-items: center; }

/* The bell (2026-07-29): a link to /notifications carrying an unread dot.
   It WAS a dropdown panel; the panel styles are gone with it — see the
   comment at its build site in shared.js for why a page won. */
.bellbtn {
  position: relative; display: inline-flex; align-items: center;
  background: none; border: none; cursor: pointer; text-decoration: none;
  font-size: 1.05rem; padding: 4px 6px; line-height: 1;
}
.belldot {
  position: absolute; top: -2px; right: -2px;
  min-width: 15px; height: 15px; padding: 0 3px; border-radius: 8px;
  background: var(--accent); color: #fff;
  font-size: .62rem; font-weight: 800; line-height: 15px; text-align: center;
}
.belldot.hidden { display: none; }

/* The notifications PAGE. One column at every width — the whole reason it
   replaced the dropdown, so it must not grow width-dependent behaviour. */
.notifrow {
  display: flex; gap: 11px; align-items: flex-start;
  padding: 12px 14px; border-bottom: 1px solid var(--border);
  color: var(--ink); text-decoration: none;
}
a.notifrow:hover { background: var(--accent-soft); }
.notifrow.unread { border-left: 3px solid var(--accent); padding-left: 11px; }
.notifico { flex: none; width: 1.4em; text-align: center; }
.notifbody { min-width: 0; }

/* Phase 3 badges: 'Developing' pulses gently (news still moving); 'new'
   marks what appeared since your last visit. Both respect reduced motion. */
.pill.developing {
  border-color: color-mix(in srgb, var(--accent) 50%, transparent);
  color: var(--accent);
  animation: devpulse 2.4s ease-in-out infinite;
}
@keyframes devpulse { 0%, 100% { opacity: 1; } 50% { opacity: .55; } }
@media (prefers-reduced-motion: reduce) { .pill.developing { animation: none; } }
.pill.fresh {
  border-color: color-mix(in srgb, var(--good) 50%, transparent);
  color: var(--good); text-transform: uppercase; font-size: .62rem;
}
/* Scroll-back day dividers (Phase 2): a quiet date line between the live
   feed and each archived day, so "why did the dates jump" never needs
   asking. Sticky-free on purpose — it's a shelf label, not a header. */
.dayline {
  margin: 22px 0 10px; padding-top: 14px;
  border-top: 1px solid var(--border);
  font-size: .78rem; font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; color: var(--muted);
}
/* The thread page's ⚑ flag: quiet by default, colored once you've flagged. */
.act.flagact { position: relative; }
.act.flagact.on { color: var(--warn); }
.act.flagact .starpop { font-weight: 500; }
/* Found in the same 2026-07-30 pass, and it went deeper than direction: both
   this and .flagact live inside .actbox, whose `overflow: hidden` (needed
   to clip its buttons' square corners into the row's rounded "connected
   box" look) also clips any CSS-positioned popover inside it to actual
   invisibility — geometry math read fine, elementFromPoint proved nothing
   was painted there. openEscapedPopover() in shared.js now positions both
   via position:fixed with JS-computed coordinates, which escapes that
   clipping regardless of container. These rules are the fallback if JS
   ever fails to run — still better than the original upward-off-viewport
   default, never load-bearing for the normal path. */
.act.editionmove { position: relative; }
.act.editionmove .starpop { bottom: auto; top: calc(100% + 8px); right: 0; left: auto; }

/* footer links — flex with a real gap: the links are appended as bare <a>
   siblings with no separator text, so without this they run together
   (owner's first live sign-in, 2026-07-26) */
.footlinks { margin: 32px 0 12px; display: flex; gap: 16px; justify-content: center;
  flex-wrap: wrap; font-size: .86rem; }
.footlinks a { color: var(--muted); }
.footlinks a:hover { color: var(--accent); }

/* checkbox rows (profile 18+ attestation, mod invite forms). `.row > *` sets
   flex:1 which stretched the CHECKBOX itself half the row wide — pin it. */
input[type=checkbox] { accent-color: var(--accent); width: 17px; height: 17px; }
/* Spacing utilities. These exist because `style: '...'` attributes are
   STRIPPED by the CSP (style-src 'self') — a dozen admin rows were quietly
   rendering with no spacing at all, and one "reason" input was going full
   width instead of 120px because its inline width never applied
   (found 2026-07-27 while checking the staff tools before invites). */
.mt6 { margin-top: 6px; }
.mt8 { margin-top: 8px; }
.mt10 { margin-top: 10px; }
.mt12 { margin-top: 12px; }
.mt14 { margin-top: 14px; }
.r2 { margin: 2px 0; }
.quoted { margin: 8px 0; }
.subline { font-size: .82em; margin: 4px 0 10px; }
.btnrow { margin-top: 12px; display: flex; gap: 8px; flex-wrap: wrap; }
.hint.tight { margin: 6px 0 0; }
/* flex:none, or `.row > *` stretches it back across the row */
.w120 { width: 120px; flex: none !important; }
.row.tight { margin-top: 10px; gap: 8px; align-items: center; justify-content: flex-start; }
.row.tight > input[type=checkbox] { flex: none; }
/* 0 1 auto — the label still HUGS the box (owner 2026-07-26: it must not
   stretch across the row, hence no grow) but it MAY shrink and wrap.
   It was `flex: none`, which forbids shrinking below content width: on a
   375px screen that pushed the whole signup page into horizontal scroll,
   both for the new consent sentence and — already, before it — for "Email me
   when I'm approved (one-time)". Found on the 2026-07-30 mobile pass; the
   email label had been overflowing on the first screen every new patron sees.
   min-width:0 is what actually lets a flex item wrap rather than hold its
   intrinsic width. */
.row.tight > label { margin: 0; flex: 0 1 auto; min-width: 0; }
/* The consent line is deliberately a sentence, so it wraps to 2-3 lines:
   top-align the box with the FIRST line rather than centring it against the
   middle of a paragraph. */
.row.tight.consentrow { align-items: flex-start; }
.row.tight.consentrow > label { line-height: 1.4; }

/* default avatar: the classic grey silhouette on a member-chosen color.
   Color classes only — the key is allowlisted server-side, never free text. */
.avatar.default { display: inline-block; background-repeat: no-repeat; background-position: center;
  background-size: cover;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Ccircle cx='32' cy='25' r='11' fill='%23E9EDF2' fill-opacity='.92'/%3E%3Cpath d='M10 58c2.5-13 11.5-19 22-19s19.5 6 22 19z' fill='%23E9EDF2' fill-opacity='.92'/%3E%3C/svg%3E"); }
/* default = THE LinePod blue (the brand accent), per the owner */
.avc-cyan { background-color: #22D3EE; } .avc-blue { background-color: #1D4ED8; }
.avc-violet { background-color: #6D28D9; } .avc-pink { background-color: #BE185D; }
.avc-red { background-color: #B91C1C; } .avc-amber { background-color: #B45309; }
.avc-green { background-color: #047857; } .avc-slate { background-color: #475569; }

/* setup-page avatar header, per the owner's sketch: big avatar left with
   swatches + photo button stacked under it, name/pronoun fields beside it.
   `.avatar.setup-av` (two classes) so the generic `.avatar` size — which is
   defined LATER in this file — can never shrink it again. */
.setup-avwrap { display: flex; gap: 22px; align-items: stretch; margin-bottom: 10px; flex-wrap: wrap; }
/* one-third / two-thirds split (owner 2026-07-26): grow ratios 1:2 with
   sensible bases so phones still wrap to stacked */
.avcol { display: flex; flex-direction: column; align-items: center; gap: 8px;
  flex: 1 1 170px; min-width: 0; }
.avatar.setup-av { width: 148px; height: 148px; border-radius: 50%; flex: none;
  border: 1px solid var(--border-strong); background-position: center; background-size: cover; }
/* the setup intro runs wider than the default hero paragraph so it sits at
   two lines, not three (owner 2026-07-26) */
.hero .setup-intro { max-width: 700px; }

/* bigger field labels on the setup card only (owner 2026-07-26);
   Display name alone gets the largest size. text-align resets the hero's
   centering — labels sit over their fields, not floating mid-card
   (2026-07-27 screenshot pass). */
.setup-card { text-align: left; }
.setup-card label { font-size: .98rem; font-weight: 650; color: var(--text); }
.setup-card label.label-lg { font-size: 1.18rem; font-weight: 750; letter-spacing: -.01em; }
.setup-card .pron-custom { flex: 1 1 120px; font-size: .92rem; }

/* required markers + the note explaining them */
.req { color: var(--danger); font-weight: 700; }
.reqnote { margin-top: 12px; }

/* faded character counters pinned to the end of each capped field */
.field-wrap { position: relative; }
.field-wrap > input, .field-wrap > textarea { padding-right: 58px; }
.char-count { position: absolute; right: 11px; top: 50%; transform: translateY(-50%);
  color: var(--faint); font-size: .72rem; pointer-events: none;
  font-variant-numeric: tabular-nums; }
.field-wrap.ta .char-count { top: auto; bottom: 9px; transform: none; }
.char-count.near { color: var(--warn); }
.pron-chips .field-wrap { flex: 1 1 120px; display: flex; }
.pron-chips .field-wrap > .pron-custom { flex: 1; padding-right: 44px; }
.avcol .swatches { justify-content: center; margin: 0; }
.avfields { flex: 2 1 260px; min-width: 0; display: flex; flex-direction: column; }
.avfields label:first-child { margin-top: 2px; }
/* tighter rhythm so name+handle+pronouns stand level with the avatar column */
.avfields label { margin-top: 10px; margin-bottom: 5px; }
/* both columns share top AND bottom lines: stretch to the avatar column's
   height and let the pronouns block anchor to the bottom edge (measured
   2026-07-26: fields ran 32px short; the slack absorbs here) */
.avfields label:last-of-type { margin-top: auto; padding-top: 10px; }
.avfields .chips { margin-bottom: 0; }
.avfields .pron-custom { flex: 1 1 90px; }
.swatches { display: flex; gap: 8px; flex-wrap: wrap; margin: 8px 0 10px; }
.sw { width: 26px; height: 26px; border-radius: 50%; border: 2px solid transparent; cursor: pointer; padding: 0; }
.sw.active { border-color: #fff; box-shadow: 0 0 0 2px var(--accent); }
.file-hidden { display: none; }
.small { font-size: .84rem; }

/* display name + pronouns share a row; columns stack on narrow phones */
.row.split { align-items: flex-start; flex-wrap: wrap; }
.row.split > div { flex: 1 1 220px; min-width: 0; }
.row.split label { margin-top: 14px; }

/* pronoun checklist chips + the one 10-char custom entry */
.pron-chips { margin-top: 2px; margin-bottom: 4px; }
.pron-custom { width: 118px; flex: none; padding: 5px 13px; font-size: .85rem;
  border-radius: 999px; background: var(--surface); border: 1px solid var(--border);
  color: var(--text); }
.pron-custom:focus { border-color: var(--accent); box-shadow: var(--ring); outline: none; }
nav .who { display: inline-flex; align-items: center; gap: 7px;
  padding: 5px 10px 5px 6px; border-radius: var(--radius-pill); }
/* It's a link now (owner 2026-07-28) — reads as a chip, not as blue text. */
nav a.who:hover { color: var(--text); background: var(--glass); text-decoration: none; }
nav a.who.active { color: var(--text); background: var(--surface-3);
  box-shadow: inset 0 0 0 1px var(--border); }
@media (max-width: 640px) {
  /* the 1:1 scroll-away header (shared.js applies the transform) */
  nav { will-change: transform; }
}

/* votes-left badge (nav, beside the account chip) */
/* Inside .navright now — the group owns the alignment, not the badge. */
.votesbadge { background: var(--accent-soft); color: var(--accent);
  border: 1px solid color-mix(in srgb, var(--accent) 45%, transparent);
  border-radius: 999px; padding: 4px 12px; font-size: .8rem; font-weight: 800;
  font-variant-numeric: tabular-nums; cursor: default; white-space: nowrap; }

/* Feed search (owner 2026-07-27) — narrows what's already on the page. */
.searchrow { display: flex; align-items: center; gap: 12px; margin: 0 0 12px; }
.searchbox { flex: 1; min-width: 0; border-radius: var(--radius-pill);
  padding: 9px 16px; font-size: .93rem; }
.searchrow .small { flex: none; white-space: nowrap;
  font-variant-numeric: tabular-nums; }

/* collapsible sort/topic pickers on the feed */
.fbar { display: flex; gap: 8px; margin: 2px 0 10px; }
.fpill { font-weight: 700; }
.chip.hidden { display: none; }
.fpanel { display: none; }
.fpanel.open { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 12px; }

/* toasts (U7): bottom-center, above the safe area */
#toasts { position: fixed; left: 0; right: 0; bottom: calc(14px + env(safe-area-inset-bottom));
  display: flex; flex-direction: column; align-items: center; gap: 8px;
  z-index: 90; pointer-events: none; }
.toast { background: var(--surface); border: 1px solid var(--border-strong);
  border-radius: 999px; padding: 9px 18px; font-size: .9rem; font-weight: 650;
  box-shadow: var(--shadow); opacity: 0; transform: translateY(10px);
  transition: opacity .25s, transform .25s; max-width: 92vw; }
.toast.show { opacity: 1; transform: none; }
.toast.good { border-color: rgba(52,211,153,.5); }
.toast.warn { border-color: rgba(251,191,36,.5); }

/* U4: on phones the votes badge floats free of the sliding nav */
.votesbadge.float { position: fixed; top: 10px; right: 12px; z-index: 70;
  margin-left: 0; box-shadow: var(--shadow-soft);
  background: color-mix(in srgb, var(--bg) 80%, transparent);
  backdrop-filter: blur(8px); }
/* the one-row bar parks the account chip exactly where the badge floats —
   clear the bar's right end for it (the old wrapped bar dodged this by
   luck: the chip usually landed on a lower row) */
body.votefloat nav { padding-right: 72px; }
/* phones: the face alone is the chip — burger + wordmark + full name +
   badge cannot share one row, and the avatar is the tap target anyway */
@media (max-width: 640px) {
  .whoname, nav .who .pill.badge { display: none; }
  nav .who .avatar { width: 30px; height: 30px; }
}

/* workbench-only persona switcher (never renders in production) */
.whowrap { position: relative; display: inline-flex; align-items: center; gap: 2px; }
.whowrap .who { margin-left: 0; }
.devcaret { background: none; border: 0; cursor: pointer; color: var(--muted);
  font-size: .8rem; padding: 4px 6px; border-radius: 8px; line-height: 1; }
.devcaret:hover { color: var(--text); background: var(--glass); }
.devswitch { display: none; position: absolute; right: 0; top: calc(100% + 8px);
  background: var(--surface); border: 1px solid var(--border-strong); border-radius: 12px;
  padding: 6px; min-width: 190px; z-index: 60; box-shadow: var(--shadow); }
.devswitch.open { display: block; }
.devswitch-h { color: var(--faint); font-size: .68rem; text-transform: uppercase;
  letter-spacing: .1em; padding: 4px 10px 6px; }
.devswitch a { display: block; padding: 7px 10px; border-radius: 8px;
  color: var(--text); font-size: .88rem; }
.devswitch a:hover { background: var(--surface-3); text-decoration: none; }

/* issue triage */
.report-video { width: 100%; max-width: 480px; border-radius: var(--radius-sm); margin: 10px 0; border: 1px solid var(--border); }
.pill.beta { border-color: color-mix(in srgb, var(--warn) 45%, transparent); color: var(--warn); }

/* ---- polls ------------------------------------------------------------ */
.polloptions { display: flex; flex-direction: column; gap: 8px; margin: 12px 0 6px; }
.pollopt { text-align: left; }
.pollrow { position: relative; }
.pollbar {
  width: 100%; text-align: left; padding: 9px 12px; border-radius: var(--radius-sm);
  border: 1px solid var(--border); background: var(--surface-2); color: var(--text);
  font: inherit; font-size: .93rem; cursor: pointer; transition: border-color .15s, background .15s;
}
.pollbar:hover:not(:disabled) { border-color: var(--border-strong); background: var(--surface-3); }
.pollbar:disabled { cursor: default; }
.pollrow.mine .pollbar { border-color: var(--accent); color: var(--accent); background: var(--accent-soft); }

/* ---- upvote / cover-this --------------------------------------------- */
button.upvote {
  flex: none; align-self: flex-start;
  background: var(--surface-2); color: var(--muted); border: 1px solid var(--border);
  border-radius: var(--radius-sm); padding: 9px 12px; font-size: .88rem; font-weight: 700;
  cursor: pointer; min-width: 60px; line-height: 1.1; transition: all .15s;
}
button.upvote:hover { border-color: var(--accent); color: var(--text); background: var(--surface-3); }
button.upvote.on {
  background: var(--accent-soft); border-color: var(--accent); color: var(--accent);
  box-shadow: 0 0 0 1px var(--accent-soft), 0 0 18px -6px var(--accent-glow);
}

@media (max-width: 520px) {
  /* TEXT WRAPS UNDER THE PICTURE on phones (owner 2026-07-28). A flex row
     gives the text its own narrow column, so a long headline stacks into a
     sliver beside the thumbnail while the space under the picture sits empty.
     Floating the picture instead lets the headline run alongside it and then
     continue full-width underneath — the newspaper behaviour, and far more
     words per screen. Desktop keeps the flex row, where the column is wide
     enough that wrapping under would just look ragged. */
  .story .rtop { display: block; }
  .story .rtop > .thumbwrap,
  .story .rtop > img.thumb { float: left; margin: 0 12px 6px 0; }
  /* Contain the float so the action bar below starts on a clean line rather
     than tucking itself into the gap beside the picture. */
  .story .rtop::after { content: ''; display: block; clear: both; }
  .story .rtop > .body { min-width: 0; }

  .story img.thumb { width: 80px; height: 56px; }
  button.upvote { order: 3; margin-left: auto; }
  /* ...but never inside a connected action box: the reorder pushed the star
     out of the box's first cell and the auto margin tore a gap in it. */
  .actbox button.upvote { order: 0; margin-left: 0; }
}

/* forms: a button directly after an input/textarea gets breathing room
   (the Submit page's crammed button, 2026-07-27 screenshot pass) */
.card > input + .btn, .card > textarea + .btn, .card > .btn { margin-top: 12px; }

/* left-rail Admin (owner 2026-07-27) */
.adminwrap { display: flex; gap: 22px; align-items: flex-start; }
.adminnav { flex: none; width: 190px; position: sticky;
  /* anchored under the measured top bar (was a hard 120px tuned to the old
     in-column bar) */
  top: calc(var(--navh, 59px) + 22px);
  display: flex; flex-direction: column; gap: 4px; }
.adminnav a { padding: 8px 13px; border-radius: 10px; color: var(--muted);
  font-weight: 600; font-size: .92rem; cursor: pointer; text-align: left;
  display: flex; justify-content: space-between; align-items: center; gap: 8px; }
.railgroup { color: var(--faint); font-size: .66rem; font-weight: 800;
  letter-spacing: .12em; text-transform: uppercase; padding: 14px 13px 5px; }
.adminnav .railgroup:first-child { padding-top: 2px; }
.railbadge { display: none; }
.paircode { font-size: 1.9rem; font-weight: 800; letter-spacing: .18em;
  font-variant-numeric: tabular-nums; margin: 12px 0 4px; min-height: 1em;
  color: var(--accent); }
/* Staff-only "add to show", stacked directly above the vote pill. */
.addrundown { display: block; width: 100%; margin-bottom: 6px; cursor: pointer;
  background: var(--surface-2); color: var(--text); border: 1px solid var(--border-strong);
  border-radius: var(--radius-pill); padding: 6px 14px; font-size: .82rem; font-weight: 700;
  white-space: nowrap; }
.addrundown:hover { background: var(--surface-3); border-color: var(--accent); color: var(--accent); }
.addrundown.on { background: var(--accent-soft); border-color: var(--accent); color: var(--accent); }
/* Finished, not broken: a disabled add button must still read as a STATE
   ("it's on the show") rather than as something the browser greyed out. */
.addrundown:disabled { cursor: default; opacity: 1; }
.addrundown:disabled:hover { background: var(--accent-soft); border-color: var(--accent); color: var(--accent); }
/* The patron-facing version — a badge, not a control. */
.onshow { display: block; width: 100%; margin-bottom: 6px; text-align: center;
  padding: 7px 10px; border-radius: 8px; font-size: .82rem; font-weight: 600;
  background: var(--accent-soft); border: 1px solid var(--accent); color: var(--accent); }
.addrundown:disabled { opacity: .55; cursor: default; }

/* End of a finite list: a way onward, not a dead stop. */
.endrail { display: flex; flex-direction: column; align-items: center; gap: 10px;
  text-align: center; padding: 34px 18px 10px; margin-top: 8px;
  border-top: 1px solid var(--border); }
.endrail .btn { text-decoration: none; }

.profcolor { display: flex; align-items: center; gap: 10px; margin-top: 8px; flex-wrap: wrap; }
.profcolor .swatches { margin: 0; }

/* Account edit panel: two groups — instant vs producer-reviewed. */
.acct-sub { font-size: .72rem; font-weight: 800; letter-spacing: .12em;
  text-transform: uppercase; color: var(--faint); margin: 20px 0 2px; }
details > .acct-sub:first-of-type { margin-top: 14px; }

/* Avatar cropper: frame the photo inside the circle everyone will see. */
.cropback { position: fixed; inset: 0; background: rgba(3, 7, 12, .78); z-index: 300;
  display: flex; align-items: center; justify-content: center; padding: 18px; }
.cropsheet { background: var(--surface); border: 1px solid var(--border);
  border-radius: 16px; padding: 18px 20px 20px; max-width: 400px; width: 100%;
  display: flex; flex-direction: column; align-items: center; }
.cropsheet h2 { margin: 0 0 14px; font-size: 1.05rem; }
.cropstage { position: relative; border-radius: 12px; overflow: hidden;
  touch-action: none; cursor: grab; background: var(--bg); }
.cropstage:active { cursor: grabbing; }
.cropcanvas { display: block; }
/* The ring dims everything outside the circle without covering the drag area. */
.cropring { position: absolute; inset: 0; border-radius: 50%; pointer-events: none;
  box-shadow: 0 0 0 9999px rgba(7, 10, 14, .62), inset 0 0 0 2px rgba(255, 255, 255, .85); }
.cropzoom { width: 100%; max-width: 320px; margin: 16px 0 2px; accent-color: var(--accent); }
.cropsheet .hint { margin-bottom: 14px; text-align: center; }
.cropactions { gap: 10px; width: 100%; justify-content: flex-end; }

/* Person sheet: the user-management panel behind a name in People. */
.sheetback { position: fixed; inset: 0; background: rgba(3, 7, 12, .72);
  z-index: 200; overflow-y: auto; padding: 24px 14px 60px; }
.sheet { max-width: 640px; margin: 0 auto; background: var(--surface);
  border: 1px solid var(--border); border-radius: 16px; padding: 18px 20px 24px; }
.sheethead { display: flex; align-items: center; gap: 13px; margin-bottom: 8px; }
.sheetname { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.sheetx { margin-left: auto; }
.avatar.big { width: 54px; height: 54px; }
.sheet h3 { font-size: .78rem; letter-spacing: .1em; text-transform: uppercase;
  color: var(--faint); margin: 22px 0 7px; font-weight: 800; }
.stats.small .stat-n { font-size: 1.35rem; }
.linkish { background: none; border: 0; padding: 0; font: inherit; font-weight: 700;
  color: var(--text); cursor: pointer; text-align: left; }
.linkish:hover { color: var(--accent); text-decoration: underline; }
.btn.danger { background: var(--danger); color: #21070A; border-color: var(--danger); }
.sortth { background: none; border: 0; padding: 0; font: inherit; color: inherit;
  cursor: pointer; letter-spacing: inherit; text-transform: inherit; }
.sortth:hover { color: var(--text); }
.sortth.on { color: var(--accent); }
.backlink { display: inline-block; margin-bottom: 10px; color: var(--muted);
  font-weight: 600; font-size: .92rem; }
.backlink:hover { color: var(--text); }
.railbadge.on { display: inline-block; background: var(--accent); color: var(--accent-ink);
  border-radius: 999px; font-size: .68rem; font-weight: 800; padding: 1px 8px;
  font-variant-numeric: tabular-nums; }
.adminnav a:hover { background: var(--glass); color: var(--text); text-decoration: none; }
.adminnav a.active { background: var(--surface-3); color: var(--text);
  box-shadow: inset 0 0 0 1px var(--border); }
/* readable content column (Stripe-style), calmer section rhythm */
.adminpane { flex: 1; min-width: 0; max-width: 740px; }
.adminpane h2 { margin: 22px 0 6px; }
.adminpane > h2:first-child, .adminpane > div:first-child h2 { margin-top: 2px; }
.adminpane .card { margin-bottom: 14px; }
@media (max-width: 760px) {
  .adminwrap { flex-direction: column; }
  .adminnav { position: static; width: 100%; flex-direction: row;
    overflow-x: auto; gap: 6px; padding-bottom: 4px; }
  .adminnav a { white-space: nowrap; }
}

/* rundown items that have a community thread */
.story.rlink { cursor: pointer; }
.story.rlink:hover { border-color: var(--border-strong); }
.rthread { flex: none; align-self: center; white-space: nowrap; }

/* account stat tiles: the two numbers the economy hangs on */
.stats { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-bottom: 14px; }
/* one tile fills the card rather than leaving a hole where its twin would be */
.stats.one { grid-template-columns: 1fr; }
.stat { background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 13px 15px; }
.stat-n { font-size: 1.75rem; font-weight: 800; letter-spacing: -.02em; font-variant-numeric: tabular-nums; }
.stat-k { color: var(--accent); font-weight: 700; font-size: .78rem; letter-spacing: .1em; text-transform: uppercase; margin: 1px 0 6px; }
.stat-d { color: var(--muted); font-size: .83rem; line-height: 1.45; }
@media (max-width: 520px) { .stats { grid-template-columns: 1fr; } }
.r4 { margin: 4px 0; }
.tar { text-align: right; }
.nowrap { white-space: nowrap; }

/* account profile header: avatar stays a circle, text fills the rest */
.profhead { display: flex; gap: 14px; align-items: center; }
.profhead > .avatar { flex: none; }
.profwho { flex: 1; min-width: 0; }

/* ---- Reddit-style feed cards (owner 2026-07-26) -----------------------
   Card mode: content on top, action bar underneath the image with the
   connected [▲ Cover this · N ⎮ ▼] vote pill. */
/* Feed rows, not bubbles (owner 2026-07-28). A boxed card per story is the
   2015 look; Reddit and Ground News both run a continuous column with a
   hairline between items, which reads faster and puts far more stories on a
   screen. The card chrome is stripped here rather than in newsCard, so the
   markup stays identical everywhere it's used. */
/* The row's own dividing line doubles as the bottom edge of the action boxes
   sitting on it, so it uses the SAME weight they do — a lighter line there
   made the boxes look like they had three sides and a shadow. */
.story.rrow { cursor: pointer; flex-direction: column; align-items: stretch;
  gap: 9px; padding: 15px 8px 14px;
  background: none; border: 0; border-radius: 0; box-shadow: none;
  margin-bottom: 0; border-bottom: 1px solid var(--border-strong); }
/* Hover tints the whole row instead of lighting up a border — the row is the
   target, and it bleeds slightly wider than the text so it feels like a row. */
.story.rrow:hover { background: var(--glass); border-color: var(--border-strong);
  box-shadow: 0 0 0 8px var(--glass); border-radius: 4px; }
/* The last row shouldn't draw a line into whatever follows it. */
.story.rrow:last-of-type { border-bottom: 0; }
.rtop { display: flex; gap: 12px; }
.rrow .title { font-size: 1.02rem; }

/* The picture opens the original story: it's a link, with the ↗ badge in its
   corner saying so (owner 2026-07-27). Sharper corners to match the buttons. */
/* align-self, or the link stretches to the whole row's height and drops the
   corner badge somewhere under the summary text */
.thumbwrap { position: relative; flex: none; align-self: flex-start;
  display: block; line-height: 0; border-radius: 6px; overflow: hidden; }
.thumbwrap .thumb, .thumbwrap .thumb.tile { border-radius: 0; }
.thumbwrap:hover .thumb { filter: brightness(1.1); }
.thumbgo { position: absolute; right: 4px; bottom: 4px; z-index: 1;
  display: flex; align-items: center; justify-content: center;
  width: 20px; height: 20px; border-radius: 5px; font-size: .74rem;
  line-height: 1; font-weight: 800; color: var(--text);
  background: rgba(7, 10, 14, .78); border: 1px solid var(--border-strong);
  backdrop-filter: blur(4px); }
.thumbwrap:hover .thumbgo { background: var(--accent); color: var(--accent-ink);
  border-color: var(--accent); }

/* the source's name under the headline, above the summary */
.srclink { display: inline-block; color: var(--muted); font-size: .8rem;
  font-weight: 650; margin-top: 3px; }
.srclink:hover { color: var(--accent); text-decoration: none; }
/* the one-line editorial take under the title, clamped to two lines */
.rrow .take { color: var(--muted); font-size: .87rem; line-height: 1.45; margin-top: 3px;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
/* The thread page IS the full view — the feed's 2-line clamp stops making
   sense the moment someone opened the story to read more (owner 2026-07-30:
   the "…" survived the tap that was supposed to reveal the rest). */
.rrow.threadcard .take { display: block; -webkit-line-clamp: unset; overflow: visible; }
.rrow .thumb.ph { display: flex; align-items: center; justify-content: center;
  color: var(--faint); font-size: 1.35rem; width: 104px; height: 72px; flex: none;
  background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--radius-sm); }

/* rung-3 topic tiles: branded placeholder when no source image exists */
.thumb.tile { display: flex; flex-direction: column; align-items: center;
  justify-content: center; gap: 3px; width: 104px; height: 72px; flex: none;
  border-radius: var(--radius-sm); border: 1px solid rgba(255,255,255,.14); }
/* glyph FILLS the box (owner 2026-07-26) — these get rarer once og:image
   sourcing lands, but while they're here they should own their space */
.thumb.tile .tglyph { font-size: 2.4rem; line-height: 1; }
.thumb.tile .tname { font-size: .56rem; font-weight: 800; letter-spacing: .08em;
  text-transform: uppercase; color: rgba(255,255,255,.85); max-width: 94px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* tiles use the DEEP teal — the bright brand cyan stays for avatars */
.thumb.tile.avc-cyan { background-color: #0E7490; }
@media (max-width: 520px) {
  .thumb.tile { width: 84px; height: 60px; }
  .thumb.tile .tglyph { font-size: 1.9rem; }
}

.votepill { display: inline-flex; align-items: stretch; border: 1px solid var(--border);
  border-radius: 999px; overflow: hidden; background: var(--surface-2); flex: none; }
.vp-up, .vp-down { background: none; border: none; color: var(--muted); font-weight: 700;
  font-size: .85rem; padding: 7px 14px; cursor: pointer; line-height: 1.1;
  transition: background .12s, color .12s; }
.vp-up:hover, .vp-down:hover { background: var(--surface-3); color: var(--text); }
.vp-up.on { color: var(--accent); }
/* the dividing line between ▲ and ▼ — one connected block, visibly split */
.vp-down { border-left: 1px solid var(--border-strong); padding: 7px 12px; }
.vp-down.on { color: var(--danger); }
/* Staff-only downvote tally, riding in the same pill. Muted on purpose: it is
   a diagnostic for the hosts, not a scoreboard, and it must never read as
/* The staff downvote tally now rides INSIDE the ▼ button (see shared.js) —
   as its own chip beside it, it read as a SECOND downvote control, which is
   exactly how it looked in the 2026-08-01 screenshot review. Tabular figures
   so the number doesn't jitter as it changes. */
.vp-down { font-variant-numeric: tabular-nums; }

/* The action bar sits ON the row's dividing line: the negative margin pulls
   the boxes down through the row's bottom padding so that line becomes their
   bottom edge (owner 2026-07-27, "connected and modern"). */
.ractions { display: flex; gap: 12px; align-items: flex-end; flex-wrap: nowrap;
  margin-bottom: -14px; }
/* one-handed split: reading actions left, thumb actions right */
.r-left { display: flex; gap: 12px; align-items: flex-end; flex-wrap: wrap; flex: 1; min-width: 0; }
.r-right { margin-left: auto; display: flex; flex-direction: column; align-items: flex-end; gap: 7px; flex: none; }

/* The connected boxes themselves — open-bottomed rectangles, split by lines
   you can actually see. Sharper corners than the rest of the app on purpose
   (owner 2026-07-27): these read as panel edges, not as pills. */
.actbox, .actstack .votepill, .actstack .addrundown {
  border: 1px solid var(--border-strong); border-bottom: 0; background: var(--surface-2);
}
/* flex:none — without it the box shrinks below its own contents on a narrow
   phone and the cell labels overprint each other instead of the bar wrapping
   (caught on the guide page at 500px, 2026-07-27). */
.actbox { display: flex; align-items: stretch; overflow: hidden; flex: none;
  border-radius: 5px 5px 0 0; }
/* every child is a cell with a visible line between, like the ▲/▼ split */
.actbox > * { display: flex; align-items: center; justify-content: center;
  padding: 8px 14px; border-right: 1px solid var(--border-strong); border-radius: 0;
  background: none; margin: 0; min-width: 0; white-space: nowrap; }
.actbox > *:last-child { border-right: 0; }
.actbox > *:hover { background: var(--surface-3); text-decoration: none; }
/* The star is a <button>, and button.upvote carries align-self:flex-start and
   its own padding from the old stacked layout — inside a box that pinned the
   star to the top of its cell and left the count sitting off-centre. Every
   cell here is the same height and centres its own contents. */
/* border-right survives the border reset — it is the cell divider, and
   zeroing all four sides silently erased the line after the star (owner
   2026-07-28, from a phone screenshot). :last-child still clears it. */
.ractions .actbox .upvote.star { border: 0;
  border-right: 1px solid var(--border-strong); min-width: 0; font-size: .82rem;
  align-self: stretch; padding: 8px 14px; line-height: 1; gap: 5px; }
.ractions .actbox .upvote.star.on { box-shadow: none; }
/* the glyph and its number as two centred items, not one lopsided string */
.starnum { font-variant-numeric: tabular-nums; }

/* right stack: "+ Add to show" rests on the vote box with a line between them */
.actstack { display: flex; flex-direction: column; align-items: stretch; }
.actstack .addrundown { width: 100%; margin-bottom: 0; border-radius: 5px 5px 0 0;
  border-bottom: 1px solid var(--border-strong); }
.actstack .votepill { border-radius: 0; overflow: hidden; }
/* the shared line is drawn ONCE, by the box above */
.actstack .addrundown + .votepill { border-top: 0; }
.actstack > .votepill:only-child { border-radius: 5px 5px 0 0; }
.actstack .vp-up, .actstack .vp-down { padding: 9px 14px; }

/* The LAST row draws no dividing line (and the thread page's single card
   never does), so there is nothing for an open-bottomed box to rest on —
   those boxes close up and round instead. A box always has four sides: its
   own, or three plus the line. */
.story.rrow:last-of-type .actbox,
.story.rrow:last-of-type .actstack .votepill { border-bottom: 1px solid var(--border-strong); }
.story.rrow:last-of-type .ractions { margin-bottom: 0; }
.story.rrow:last-of-type .actbox,
.story.rrow:last-of-type .actstack > .votepill:only-child { border-radius: 5px; }
.story.rrow:last-of-type .actstack .addrundown + .votepill { border-radius: 0 0 5px 5px; }

/* phones: the action bar must WRAP or the card overflows the viewport
   (caught by the 2026-07-27 phone screenshot pass). A wrapped box can't sit
   on the dividing line, so below this width the boxes close up and round
   instead — the same shape, standing on its own. */
@media (max-width: 520px) {
  .ractions { flex-wrap: wrap; align-items: stretch; margin-bottom: 0; row-gap: 10px; }
  /* THE PHONE SHAPE (owner 2026-07-28, replacing the content-sized bar that
     overflowed the card once all three reading buttons were present):
       — the bottom box spans the FULL card width, one consistent length on
         every card, and its buttons split that width between them;
       — the vote block above keeps its natural size but is pushed to the
         right, so its right edge and the bottom box's right edge meet on the
         same line — the two blocks read as one aligned unit;
       — with no "no-paywall" button, Read source doesn't just inherit the
         spare space — it takes a deliberately LARGER share than Comment,
         a big target with real negative space, per the owner's spec. */
  .r-left { flex: 1 1 100%; min-width: 0; }
  .r-left .actbox { width: 100%; }
  /* The row's economy, per the owner's phone pass (2026-07-28):
       star     — content-sized; a glyph and a digit need no equal share
       comment  — LOCKED width, identical on every card, so the bar reads as
                  one consistent unit down the feed
       the rest — Source / no-paywall split whatever remains; when no-paywall
                  is absent, Source inherits ALL of it (the extra-big button)
     Equal thirds — the first attempt — clipped label ends mid-word the
     moment three cells shared a phone card. */
  .r-left .actbox > * { flex: 1 1 0; min-width: 0; padding: 10px 6px; }
  .r-left .actbox > .upvote.star { flex: 0 0 auto; padding: 10px 13px; }
  .r-left .actbox > .cmt { flex: 0 0 118px; }
  /* order, not DOM order (owner 2026-07-27, from a phone): the VOTE block
     belongs on top — voting is why the row exists. `order` keeps the markup
     reading left-to-right for screen readers. margin-left:auto is the
     right-edge alignment with the box below. */
  .r-right { flex-direction: row; align-items: center; gap: 8px; order: -1;
    margin-left: auto; max-width: 100%; min-width: 0; }
  .actbox, .actstack .votepill { border-bottom: 1px solid var(--border-strong); }
  .actbox, .actstack > .votepill:only-child { border-radius: 5px; }
  .actstack .addrundown { border-radius: 5px 5px 0 0; }
  .actstack .addrundown + .votepill { border-radius: 0 0 5px 5px; }
}
.r-right .starrers { margin-left: 0; }
/* starrer face-pile: overlapping circles bottom-right; hosts ringed cyan,
   always leftmost (server sorts hosts first) */
.starrers { display: flex; margin-left: auto; align-items: center;
  background: none; border: none; padding: 0; cursor: pointer; position: relative; }
/* the tap-to-see "Starred by" popover — opens upward from the pile */
.starpop { display: none; position: absolute; right: 0; bottom: calc(100% + 8px);
  z-index: 40; background: var(--surface); border: 1px solid var(--border-strong);
  border-radius: 12px; padding: 8px; min-width: 200px; box-shadow: var(--shadow);
  text-align: left; cursor: default; }
.starpop.open { display: block; }
.starpop-h { color: var(--faint); font-size: .68rem; text-transform: uppercase;
  letter-spacing: .1em; padding: 2px 6px 6px; }
.starpop-row { display: flex; align-items: center; gap: 9px; padding: 5px 6px;
  font-size: .9rem; color: var(--text); }
.starpop-row .avatar { width: 22px; height: 22px; }
.savatar { width: 24px; height: 24px; border-radius: 50%; object-fit: cover;
  border: 2px solid var(--surface); margin-left: -8px; display: inline-block;
  background-position: center; background-size: cover; }
.starrers .savatar:first-child { margin-left: 0; }
.savatar.host { border-color: var(--accent); z-index: 1; position: relative; }
.ractions .upvote.star { min-width: 0; padding: 6px 12px; font-size: .8rem; border-radius: 999px; }
.ractions .act { color: var(--muted); font-size: .84rem; font-weight: 600; }
.ractions .act:hover { color: var(--accent); text-decoration: none; }
@media (max-width: 520px) {
  .rrow .thumb.ph, .story.rrow img.thumb { width: 84px; height: 60px; }
}

/* ---- buttons + forms -------------------------------------------------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  background: linear-gradient(180deg, var(--accent) 0%, var(--accent-2) 100%);
  color: var(--accent-ink); border: none; border-radius: var(--radius-sm);
  padding: 10px 20px; font-weight: 750; cursor: pointer; font-size: .93rem;
  box-shadow: 0 0 0 1px rgba(34,211,238,.35), 0 8px 22px -10px var(--accent-glow);
  transition: filter .15s, transform .05s, box-shadow .15s;
}
.btn:hover { filter: brightness(1.06); text-decoration: none; box-shadow: 0 0 0 1px rgba(34,211,238,.5), 0 10px 26px -8px var(--accent-glow); }
.btn:active { transform: translateY(1px); }
.btn.ghost {
  background: var(--glass); color: var(--text); border: 1px solid var(--border-strong);
  box-shadow: none; font-weight: 600;
}
.btn.ghost:hover { background: var(--surface-3); filter: none; border-color: var(--accent); }
.btn.danger {
  background: transparent; color: var(--danger); border: 1px solid color-mix(in srgb, var(--danger) 55%, transparent);
  box-shadow: none; font-weight: 650;
}
.btn.danger:hover { background: color-mix(in srgb, var(--danger) 12%, transparent); filter: none; }
.btn.small { padding: 6px 13px; font-size: .82rem; }

/* `time` was missing from this list, so the air-time box on the shows pane
   rendered as a raw browser control — light background, system font — right
   beside properly styled dark inputs (owner 2026-07-31: "the calendar area
   for adding and removing shows looks like crap"). `search` and `tel` are
   here too so the next one added doesn't repeat it. */
input[type=text], input[type=url], input[type=date], input[type=time], input[type=datetime-local], input[type=email], input[type=number], input[type=search], input[type=tel], textarea, select {
  /* color-scheme: native widgets (calendar popups, pickers) render dark */
  color-scheme: dark;
  width: 100%; background: var(--surface-2); border: 1px solid var(--border); color: var(--text);
  border-radius: var(--radius-sm); padding: 10px 13px; font-size: .95rem; font-family: inherit;
  transition: border-color .15s, box-shadow .15s;
}
input::placeholder, textarea::placeholder { color: var(--faint); }
input:focus, textarea:focus, select:focus { outline: none; border-color: var(--accent); box-shadow: var(--ring); }
label { display: block; color: var(--muted); font-size: .84rem; font-weight: 600; margin: 14px 0 6px; }
/* Form fields as GROUPS. Labels and inputs used to be flat siblings in a
   plain div, so the only thing separating "Date" from the box below it was
   the label's own margin — and two fields that belong together (a date and
   its air time) stacked as though they were unrelated questions. */
/* A card that is a FORM needs a heading — "Schedule a show" — or it reads as
   a loose pile of inputs under whatever section title happens to be above. */
.cardhead { margin: 0 0 4px; font-size: .98rem; font-weight: 650; letter-spacing: -.01em; }
.field { display: flex; flex-direction: column; min-width: 0; }
.field > label { margin: 0 0 6px; }
.fieldrow { display: flex; gap: 12px; align-items: flex-start; flex-wrap: wrap; margin-top: 14px; }
.fieldrow > .field { flex: 1 1 180px; }
/* A date and a time are one decision, and neither needs the width of the card. */
.fieldrow > .field.narrow { flex: 0 1 200px; }
/* The hidden ATTRIBUTE loses to any class that sets a display, and .row sets
   display:flex — so an element with both stays visible. This makes `hidden`
   mean hidden, whatever else is on the element. (2026-08-06: the show-date
   editor shipped its fields visible because of exactly this.) */
[hidden] { display: none !important; }
.row { display: flex; gap: 10px; align-items: center; }
.row > * { flex: 1; }
.row > .btn, .row > button { flex: none; }
/* A pill stretched to an equal share of the row reads as an empty text field
   — which is exactly what "$20 · Contributor" looked like on the front door.
   Badges hug their text and give the names beside them the room instead
   (owner 2026-07-27 staff-tools review). */
.row > .pill, .row > .status, .row > .votesbadge { flex: none; }

/* ---- chips ------------------------------------------------------------ */
.chips { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 18px; }
.chip {
  background: var(--surface); border: 1px solid var(--border); color: var(--muted);
  border-radius: var(--radius-pill); padding: 6px 15px; font-size: .85rem; cursor: pointer;
  transition: all .15s;
}
.chip:hover { border-color: var(--border-strong); color: var(--text); }
.chip.active { border-color: var(--accent); color: var(--accent); background: var(--accent-soft); }

/* ---- notices + status ------------------------------------------------- */
.notice {
  border: 1px solid var(--border); border-left: 3px solid var(--accent); border-radius: var(--radius-sm);
  background: var(--surface); padding: 13px 15px; margin-bottom: 16px; font-size: .92rem;
}
.notice.warn { border-left-color: var(--warn); }
.notice.bad  { border-left-color: var(--danger); }
.status { font-size: .76rem; font-weight: 700; padding: 3px 10px; border-radius: var(--radius-pill); }
.status.pending  { color: var(--warn); border: 1px solid color-mix(in srgb, var(--warn) 50%, transparent); }
.status.approved { color: var(--good); border: 1px solid color-mix(in srgb, var(--good) 50%, transparent); }
.status.rejected { color: var(--muted); border: 1px solid var(--border); }

/* ---- tables (mod) ----------------------------------------------------- */
table { width: 100%; border-collapse: collapse; font-size: .88rem; }
th {
  text-align: left; color: var(--muted); font-weight: 700; font-size: .74rem;
  text-transform: uppercase; letter-spacing: .6px; padding: 8px; border-bottom: 1px solid var(--border-strong);
}
td { padding: 10px 8px; border-bottom: 1px solid var(--border); vertical-align: top; }
tr:hover td { background: var(--glass); }
td .sub { color: var(--muted); font-size: .85rem; }

.muted { color: var(--muted); }
.done { text-decoration: line-through; color: var(--muted); }
.center { text-align: center; padding: 34px 0; color: var(--muted); }

/* vote stack (cover-this + editor star) */
.votes { display: flex; flex-direction: column; gap: 6px; flex: none; align-self: flex-start; }
button.upvote.star.on {
  background: color-mix(in srgb, var(--warn) 14%, transparent); border-color: var(--warn); color: var(--warn);
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--warn) 30%, transparent), 0 0 18px -6px color-mix(in srgb, var(--warn) 60%, transparent);
}

/* badges */
.pill.badge { margin-left: 6px; }
.pill.badge.insider { border-color: color-mix(in srgb, var(--warn) 50%, transparent); color: var(--warn); }
.pill.badge.mod, .pill.badge.owner { border-color: var(--accent); color: var(--accent); background: var(--accent-soft); }
/* Producer: staff, same authority as a host, but not on air — so it reads as
   its own thing rather than a second kind of host (owner 2026-07-28). */
.pill.badge.producer { border-color: color-mix(in srgb, #A78BFA 55%, transparent);
  color: #C4B5FD; background: rgba(167,139,250,.10); }

/* ---- comments --------------------------------------------------------- */
.comment { border-left: 2px solid var(--border); padding: 9px 0 4px 14px; margin: 8px 0; }
.comment.d1 { margin-left: 18px; } .comment.d2 { margin-left: 36px; }
.comment.d3 { margin-left: 54px; } .comment.d4 { margin-left: 72px; }
.comment.d5 { margin-left: 90px; } .comment.d6 { margin-left: 108px; }
.comment.d7 { margin-left: 126px; } .comment.d8 { margin-left: 144px; }
@media (max-width: 520px) {
  .comment.d1 { margin-left: 10px; } .comment.d2 { margin-left: 20px; }
  .comment.d3 { margin-left: 30px; } .comment.d4 { margin-left: 40px; }
  .comment.d5 { margin-left: 50px; } .comment.d6 { margin-left: 60px; }
  .comment.d7 { margin-left: 70px; } .comment.d8 { margin-left: 80px; }
}
.comment .chead { display: flex; gap: 8px; align-items: center; font-size: .85rem; flex-wrap: wrap; }
.comment .cbody { margin: 5px 0; white-space: pre-wrap; word-break: break-word; }
.comment .cactions { display: flex; gap: 14px; font-size: .8rem; }
.comment .cactions a { color: var(--muted); }
.comment .cactions a:hover { color: var(--text); }
.comment .cactions a.danger-link { color: var(--danger); }
button.cvote {
  background: none; border: 1px solid var(--border); color: var(--muted);
  border-radius: var(--radius-pill); padding: 2px 11px; font-size: .82rem; cursor: pointer; transition: all .15s;
}
button.cvote:hover { border-color: var(--border-strong); color: var(--text); }
button.cvote.on { border-color: var(--accent); color: var(--accent); background: var(--accent-soft); }
.creply-box { margin: 8px 0; }

/* ---- tier ladder ------------------------------------------------------ */
/* Full width like every other card — the old 480px centre made it read as a
   different, narrower page element on the account screen (2026-07-28 pass).
   The under-tier ladder screen still centres it via .hero. */
.ladder { text-align: left; margin-bottom: 22px; }
.hero .ladder { max-width: 480px; margin-left: auto; margin-right: auto; }
.ladder-row { padding: 9px 0; border-bottom: 1px solid var(--border); opacity: .5; display: flex; align-items: center; }
.ladder-row:last-child { border-bottom: none; }
.ladder-row.unlocked { opacity: 1; }
.ladder-price { flex: none !important; width: 54px; font-weight: 800; color: var(--accent); }
.ladder-row > span:last-child { flex: none; color: var(--good); }

/* ---- avatars ---------------------------------------------------------- */
.avatar { width: 24px; height: 24px; border-radius: 50%; object-fit: cover; vertical-align: middle; border: 1px solid var(--border); }
.avatar.big { width: 60px; height: 60px; }
.avatar.empty {
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--surface-3); color: var(--muted); font-weight: 700; border: 1px solid var(--border);
}
.chead .avatar { margin-right: 2px; }
.profrev-imgs img { max-width: 96px; max-height: 96px; border-radius: var(--radius-sm); border: 1px solid var(--border); }

/* profile review: what they have now vs what they're asking for, stacked and
   labelled, so a paragraph-long bio stays readable */
.prchange { margin: 10px 0; }
.prlabel { color: var(--faint); font-size: .7rem; font-weight: 800;
  letter-spacing: .1em; text-transform: uppercase; margin-bottom: 4px; }
.prfrom, .prto { display: flex; gap: 9px; align-items: baseline; padding: 4px 0;
  font-size: .92rem; line-height: 1.5; }
.prtag { flex: none; width: 44px; font-size: .66rem; font-weight: 800;
  letter-spacing: .08em; text-transform: uppercase; color: var(--faint);
  border: 1px solid var(--border); border-radius: 999px; padding: 1px 0;
  text-align: center; }
.prtag.new { color: var(--accent); border-color: color-mix(in srgb, var(--accent) 45%, transparent); }

/* ======================================================================
   Sign-in hero + landing (front door for signed-out visitors)
   ====================================================================== */
.hero { text-align: center; padding: 64px 20px; }
.hero h1 { font-size: clamp(1.8rem, 5vw, 2.4rem); margin-bottom: 12px; }
.hero p { color: var(--muted); max-width: 460px; margin: 0 auto 26px; }

.patreon-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  background: #F1465A; color: #fff; font-weight: 750;
  border-radius: var(--radius-sm); padding: 12px 24px; font-size: 1rem;
  box-shadow: 0 10px 26px -12px rgba(241,70,90,.8);
  transition: filter .15s, transform .05s;
}
.patreon-btn:hover { text-decoration: none; filter: brightness(1.07); }
.patreon-btn:active { transform: translateY(1px); }
.devbar { margin-top: 28px; font-size: .85rem; color: var(--muted); }
.devbar a { margin: 0 6px; }

.landing { max-width: var(--maxw); margin: 0 auto; }
.lp-hero { text-align: center; padding: 48px 16px 26px; }
/* Landing hero in the holding-page's visual language (owner 2026-07-26: "I
   like the look of the coming soon page") — same glass mark, same status
   chip; only the copy and the sign-in CTA differ. */
.lp-hero-soon { padding-top: 34px; }
.lp-hero-soon .soon-mark { margin-left: auto; margin-right: auto; }
.lp-eyebrow { color: var(--accent); font-size: .72rem; letter-spacing: .18em; font-weight: 700; text-transform: uppercase; margin-bottom: 16px; }
.lp-hero h1 { font-size: clamp(2.1rem, 6vw, 3.4rem); line-height: 1.06; margin: 0 0 16px; letter-spacing: -.03em; text-wrap: balance; }
.lp-brand { color: var(--accent); }
.lp-lead { color: var(--muted); font-size: 1.1rem; line-height: 1.6; max-width: 46rem; margin: 0 auto 28px; text-wrap: pretty; }
.lp-cta { display: flex; gap: 12px; justify-content: center; flex-wrap: wrap; }
.lp-note { font-size: .82rem; margin-top: 18px; color: var(--muted); }
.btn.big, .patreon-btn.big { font-size: 1.02rem; padding: 13px 26px; }
.btn.ghost.big { border: 1px solid var(--border-strong); border-radius: var(--radius-sm); color: var(--text); background: var(--glass); }
.btn.ghost.big:hover { background: var(--surface-3); }

.lp-features { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; margin: 34px 0 8px; }
.lp-feature {
  display: flex; gap: 14px; align-items: flex-start;
  background: linear-gradient(180deg, var(--surface) 0%, var(--bg-2) 140%);
  border: 1px solid var(--border); border-radius: var(--radius); padding: 18px;
  box-shadow: inset 0 1px 0 var(--glass-line);
}
.lp-ico {
  flex: none; width: 38px; height: 38px; border-radius: var(--radius-sm);
  background: var(--accent-soft); color: var(--accent); display: flex; align-items: center; justify-content: center;
  font-size: 1.15rem; box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 30%, transparent);
}
.lp-feature b { display: block; margin-bottom: 4px; font-size: 1rem; }
.lp-feature .muted { font-size: .9rem; line-height: 1.5; }

.lp-how { margin: 40px 0 10px; text-align: center; }
.lp-how h2 { margin-bottom: 20px; }
.lp-steps { display: flex; flex-direction: column; gap: 12px; max-width: 42rem; margin: 0 auto 24px; text-align: left; }
.lp-step {
  display: flex; gap: 16px; align-items: flex-start;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 18px;
}
.lp-num {
  flex: none; width: 32px; height: 32px; border-radius: 50%;
  background: linear-gradient(180deg, var(--accent), var(--accent-2)); color: var(--accent-ink);
  font-weight: 800; display: flex; align-items: center; justify-content: center; font-size: .95rem;
}
.lp-step .muted { font-size: .92rem; line-height: 1.5; margin-top: 3px; }

@media (max-width: 640px) { .lp-features { grid-template-columns: 1fr; } }

/* ======================================================================
   Holding page (/soon) — cinematic launch hero. All classes, no inline CSS.
   ====================================================================== */
.soon {
  min-height: 100vh; display: flex; flex-direction: column;
  align-items: center; justify-content: center; text-align: center;
  padding: 40px 20px; position: relative; overflow: hidden;
}
/* broadcast atmosphere: layered glows + a faint signal grid behind everything */
.soon::before {
  content: ""; position: absolute; inset: 0; z-index: 0; pointer-events: none;
  background:
    radial-gradient(600px 340px at 50% 26%, rgba(34,211,238,.16), transparent 62%),
    radial-gradient(700px 500px at 50% 120%, rgba(56,189,248,.08), transparent 60%);
}
.soon::after {
  content: ""; position: absolute; inset: 0; z-index: 0; pointer-events: none; opacity: .5;
  background-image:
    linear-gradient(color-mix(in srgb, var(--accent) 6%, transparent) 1px, transparent 1px),
    linear-gradient(90deg, color-mix(in srgb, var(--accent) 6%, transparent) 1px, transparent 1px);
  background-size: 46px 46px;
  -webkit-mask-image: radial-gradient(circle at 50% 40%, #000 0%, transparent 72%);
          mask-image: radial-gradient(circle at 50% 40%, #000 0%, transparent 72%);
}
.soon > * { position: relative; z-index: 1; }

/* the mark on a glass tile so the black logo plate reads against the black page */
.soon-mark {
  width: 132px; height: 132px; border-radius: 30px; margin-bottom: 26px;
  display: grid; place-items: center;
  background: var(--glass); border: 1px solid var(--glass-line);
  box-shadow: var(--shadow), inset 0 1px 0 var(--glass-line), 0 0 60px -12px var(--accent-glow);
  animation: rise .7s cubic-bezier(.2,.7,.2,1) both;
}
.soon-mark img { width: 100px; height: 100px; border-radius: 20px; display: block; }

.soon-status {
  display: inline-flex; align-items: center; gap: 8px;
  font-size: .74rem; font-weight: 700; letter-spacing: .14em; text-transform: uppercase;
  color: var(--muted); border: 1px solid var(--border); border-radius: var(--radius-pill);
  padding: 6px 14px; margin-bottom: 22px; background: var(--surface);
  animation: rise .7s .05s cubic-bezier(.2,.7,.2,1) both;
}
.soon-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--on-air);
  box-shadow: 0 0 0 0 rgba(239,68,68,.6); animation: onair 2.2s ease-out infinite; }

.soon h1 {
  font-size: clamp(2.2rem, 7vw, 3.6rem); line-height: 1.04; letter-spacing: -.03em;
  margin: 0 0 16px; max-width: 16ch; text-wrap: balance;
  animation: rise .7s .1s cubic-bezier(.2,.7,.2,1) both;
}
.soon h1 .lp-brand { color: var(--accent); }
.soon-lead {
  color: var(--muted); max-width: 40rem; margin: 0 auto 8px; font-size: 1.1rem; line-height: 1.6;
  text-wrap: pretty; animation: rise .7s .16s cubic-bezier(.2,.7,.2,1) both;
}
.soon-sub { color: var(--faint); font-size: .95rem; margin: 0 auto 26px; animation: rise .7s .2s cubic-bezier(.2,.7,.2,1) both; }
.soon-cta { animation: rise .7s .26s cubic-bezier(.2,.7,.2,1) both; }
.soon-foot {
  margin-top: 34px; color: var(--faint); font-size: .84rem;
  display: flex; gap: 10px; align-items: center; justify-content: center; flex-wrap: wrap;
  animation: rise .7s .32s cubic-bezier(.2,.7,.2,1) both;
}
.soon-foot a { color: var(--muted); }
.soon-foot a:hover { color: var(--accent); }
.soon-foot .sep { color: var(--border-strong); }

/* ======================================================================
   How it works — the tutorial page. Built from the REAL controls, so it
   restyles itself whenever the feed does and can never show a stale
   screenshot of a button that has since changed.
   ====================================================================== */
.gsub { font-size: .9rem; margin: -4px 0 14px; max-width: 62ch; }

/* the inert example row, framed so it reads as an exhibit, not the feed */
.gstage { border: 1px dashed var(--border-strong); border-radius: var(--radius);
  padding: 4px 14px; margin-bottom: 22px; background: var(--bg-2);
  position: relative; }
.gstage::before { content: "EXAMPLE"; position: absolute; top: -8px; left: 14px;
  background: var(--bg-2); padding: 0 8px; color: var(--faint);
  font-size: .62rem; font-weight: 800; letter-spacing: .14em; }
/* nothing in the exhibit is clickable — say so with the cursor too */
.gstage * { cursor: default !important; }
.gstage .story.rrow:hover { background: none; box-shadow: none; }

/* control → meaning, with the arrow between them */
.gcalls { display: flex; flex-direction: column; gap: 2px; margin-bottom: 8px; }
.gcall { display: flex; align-items: center; gap: 16px; padding: 14px 4px;
  border-bottom: 1px solid var(--border); }
.gcall:last-child { border-bottom: 0; }
.gcall-ctrl { flex: none; width: 190px; display: flex; justify-content: center;
  pointer-events: none; }
.gcall-arrow { flex: none; color: var(--accent); font-size: 1.15rem; font-weight: 700; }
.gcall-what { flex: 1; min-width: 0; }
.gcall-what .muted { font-size: .9rem; line-height: 1.5; margin-top: 2px; }
/* the demo controls need their feed context faked in miniature */
.gcall .actbox, .gcall .votepill { border-bottom: 1px solid var(--border-strong);
  border-radius: 5px; }
.gcall .upvote.star { align-self: stretch; padding: 8px 14px; line-height: 1;
  gap: 5px; border: 1px solid var(--border-strong); border-radius: 5px;
  background: var(--surface-2); }
.gdemo-thumb { align-self: center; }
.gdemo-pile { margin-left: 0; }
.gtitle-demo { font-weight: 650; font-size: 1.02rem; color: var(--text);
  border-bottom: 2px solid var(--accent-soft); }

/* the everyone-gets-this grid */
.gtiles { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-bottom: 10px; }
.gtile { display: flex; gap: 13px; align-items: flex-start;
  background: linear-gradient(180deg, var(--surface) 0%, var(--bg-2) 140%);
  border: 1px solid var(--border); border-radius: var(--radius); padding: 15px 16px;
  box-shadow: inset 0 1px 0 var(--glass-line); }
.gtile-ico { flex: none; width: 34px; height: 34px; border-radius: 8px;
  background: var(--accent-soft); color: var(--accent); display: flex;
  align-items: center; justify-content: center; font-size: 1.02rem;
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 30%, transparent); }
.gtile b { display: block; margin-bottom: 3px; }
.gtile .muted { font-size: .88rem; line-height: 1.5; }

/* three signals, side by side so the difference is impossible to miss */
.gsignals { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; margin-bottom: 10px; }
.gsig { background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 15px 16px; text-align: center; }
.gsig-mark { font-size: 1.5rem; font-weight: 800; color: var(--accent);
  line-height: 1; margin-bottom: 8px; }
.gsig:nth-child(2) .gsig-mark { color: var(--warn); }
.gsig:nth-child(3) .gsig-mark { color: var(--muted); }
.gsig .muted { font-size: .86rem; line-height: 1.45; margin-top: 4px; }

/* the rungs above — dimmed until you have them */
.gladder { display: flex; flex-direction: column; gap: 10px; }
.grung { border: 1px solid var(--border); border-left: 3px solid var(--border-strong);
  border-radius: var(--radius); padding: 14px 16px; background: var(--surface); opacity: .62; }
.grung.has { opacity: 1; border-left-color: var(--accent); }
.grung-head { display: flex; align-items: center; gap: 10px; margin-bottom: 5px; }
.grung-price { font-weight: 800; color: var(--accent); font-size: 1.05rem; }
.grung-tier { font-size: .72rem; font-weight: 800; letter-spacing: .12em;
  text-transform: uppercase; color: var(--faint); }
.good-pill { border-color: color-mix(in srgb, var(--good) 50%, transparent);
  color: var(--good); margin-left: auto; }
.grung .muted { font-size: .89rem; line-height: 1.5; margin-top: 2px; }

.gend { text-align: center; padding: 34px 0 8px; display: flex;
  flex-direction: column; align-items: center; gap: 12px; }

@media (max-width: 760px) {
  .gtiles, .gsignals { grid-template-columns: 1fr; }
  /* the control stacks above its explanation rather than shrinking away */
  .gcall { flex-direction: column; align-items: flex-start; gap: 9px; }
  .gcall-ctrl { width: auto; justify-content: flex-start; }
  .gcall-arrow { transform: rotate(90deg); margin-left: 26px; }
}

/* ---- legal pages ------------------------------------------------------ */
.legal { max-width: 720px; margin: 0 auto; padding: 28px 0 56px; }
.legal h1 { font-size: 1.9rem; }
.legal h2 { margin-top: 26px; font-size: 1.08rem; }
.legal p { line-height: 1.65; margin-bottom: 8px; }
.legal ul { margin: 8px 0 8px 22px; line-height: 1.6; }
.legal .muted { display: block; }
.legal .fineprint { margin-top: 26px; }

/* ---- Feed | Newsroom tabs (owner 2026-07-28) ----------------------------
   Two rectangles, half and half across the top, active one underlined.

   Underline rather than a filled iOS-style segment on purpose: these two
   pages swipe between each other, and a travelling underline is the standard
   indicator for swipeable tabs. It also keeps the active label at full
   contrast — a filled segment would either dim the inactive label or force a
   second text colour to stay readable.

   PHONES ONLY. On desktop the side rail already lists both pages; repeating
   them immediately below it is noise, not navigation. */
.pagetabs { display: none; }

/* 899.98px, not 900px — the exact width at which the rail becomes a drawer
   (see the rail's own rules above). Rounding to 900 would leave a hair's
   width where the rail is still pinned open AND the tabs are showing. */
@media (max-width: 899.98px) {
  .pagetabs {
    display: grid;
    grid-template-columns: 1fr 1fr;   /* half and half, exactly */
    position: relative;               /* the moving underline anchors to this */
    margin: 0 0 14px;
    border-bottom: 1px solid var(--border);
  }
  .ptab {
    /* 46px clears the 44pt touch floor with the line sitting inside it. */
    min-height: 46px;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: .95rem;
    font-weight: 650;
    letter-spacing: .01em;
    color: var(--muted);
    text-decoration: none;
    transition: color .12s ease-out;
  }
  .ptab.on { color: var(--text); }
  .ptab:focus-visible { outline: 2px solid var(--accent); outline-offset: -3px; }

  /* ONE bar that moves, rather than a border blinking from tab to tab: the
     swipe drags it across as a fraction of the screen, so it lands on the
     destination exactly as the page does. The labels themselves never move
     (owner 2026-07-28). --tabx is set from JS via CSSOM, which the CSP allows
     — a style="" attribute would be stripped. */
  .ptabline {
    position: absolute;
    left: 0; bottom: -1px;            /* sits ON the bar's own hairline */
    width: 50%; height: 2px;
    background: var(--accent);
    transform: translateX(var(--tabx, 0%));
    pointer-events: none;
  }
}

/* Only the content below the tabs travels with the thumb. */
.swipepane { will-change: transform; }

/* ---- chameleon: the "you are pretending" bar (owner 2026-07-28) ----------
   Warm, not alarming: this is a deliberate staff tool, not an error. It uses
   the warning hue rather than the accent so it can never be mistaken for
   ordinary product chrome, and it appears above everything on every page —
   forgetting you are in a lens is the whole failure mode. */
.previewbar { display: flex; align-items: center; gap: 11px; flex-wrap: wrap;
  background: color-mix(in srgb, var(--warn) 12%, var(--surface));
  border: 1px solid color-mix(in srgb, var(--warn) 45%, transparent);
  border-radius: var(--radius-sm); padding: 10px 14px; margin: 0 0 16px;
  font-size: .9rem; color: var(--text); }
.previewbar span:nth-of-type(2) { flex: 1 1 260px; min-width: 0; }
.previewdot { flex: none; width: 9px; height: 9px; border-radius: 50%;
  background: var(--warn); box-shadow: 0 0 0 3px color-mix(in srgb, var(--warn) 25%, transparent); }
/* The caret glows while a lens is on, so the state is visible from the bar
   even after the banner has scrolled away. */
.devcaret.on { color: var(--warn); }
.devswitch a.on { color: var(--accent); font-weight: 700; }
.devswitch a.stop { border-top: 1px solid var(--border); margin-top: 4px; padding-top: 8px;
  color: var(--warn); }

/* ---- the show-date picker (owner 2026-07-28) -----------------------------
   Each show has its own edition of the Newsroom; this chooses which one you
   are reading. A native <select> on purpose: on a phone it opens the system
   picker, which is faster to use one-handed and needs no custom popover to
   maintain. */
.edrow { display: flex; align-items: center; gap: 10px; margin: 0 0 16px;
  flex-wrap: wrap; }
.edlabel { font-size: .72rem; font-weight: 700; letter-spacing: .12em;
  text-transform: uppercase; color: var(--muted); flex: none; }
.edsel { flex: 1 1 auto; min-width: 0; max-width: 26rem; min-height: 44px;
  padding: 9px 12px; border-radius: var(--radius-sm);
  border: 1px solid var(--border-strong); background: var(--surface-2);
  color: var(--text); font-family: inherit; font-size: .95rem; font-weight: 600; }
.edsel:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* ---- the two tabbed pages' header (owner 2026-07-28) --------------------
   Desktop keeps what was there: title beside the explainer, search below.
   Phone drops the <h1> entirely — the tab bar directly above already says
   "Feed" or "Newsroom", so a heading repeating it is the same word three
   times on one screen — and lifts the search above the explainer, because
   the search is what you came to use and the explainer is read once. */
.feedhead { display: flex; align-items: baseline; gap: 18px; flex-wrap: wrap;
  margin-bottom: 16px; }
.feedhead h1 { flex: none; margin-bottom: 0; }
.feedhead .hint { flex: 1 1 340px; min-width: 0; margin-bottom: 0;
  border-left: 1px solid var(--border-strong); padding-left: 18px;
  max-width: 64ch; text-wrap: pretty; }
.feedhead .searchrow { flex: 1 1 100%; margin-top: 14px; }

@media (max-width: 899.98px) {
  .feedhead { flex-direction: column; align-items: stretch; gap: 0;
    margin-bottom: 14px; }
  .feedhead h1 { display: none; }
  .feedhead .searchrow { order: 1; margin-top: 0; }
  /* flex: none matters: the desktop rule's `flex: 1 1 340px` uses 340px as a
     WIDTH basis, but in this stacked column flex-basis becomes HEIGHT — one
     sentence of text stretched into a 340px-tall box (the "ton of space"
     the owner hit, 2026-07-28). */
  .feedhead .hint { flex: none; order: 2; border-left: 0; padding-left: 0;
    margin-top: 10px; }
}

/* ---- touch targets ------------------------------------------------------
   Apple's HIG asks for 44x44pt of tappable area, and WCAG 2.5.8 for at least
   24x24 CSS px. Several controls here were built for a mouse and land under
   both — the ▲/▼ vote pill at ~30px, the comment vote at ~24px, and the
   comment action links (reply / edit / delete) which are bare text.

   Scoped to `pointer: coarse` so this is a FINGER rule, not a design change:
   desktop keeps the compact look it was drawn for, phones and tablets get
   targets a thumb can actually hit. Height is added as padding around the
   same text, so nothing moves visually except the room around it. */
@media (pointer: coarse) {
  .vp-up, .vp-down,
  .actbox > *,
  button.cvote,
  .chip,
  .cactions a,
  .footlinks a,
  nav a.tab {
    min-height: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
  }
  /* The comment actions are a text row; give them width as well as height so
     "edit" and "delete" can't sit a couple of pixels apart under a thumb. */
  .comment .cactions { gap: 18px; }
  .comment .cactions a { min-width: 44px; }
  button.cvote { min-width: 44px; }
  /* The rail's own rows are already 44px+ via their padding; the drawer's
     close-the-drawer button and the burger get the same floor. */
  .navburger { min-width: 44px; min-height: 44px; }
}

/* ---- motion ----------------------------------------------------------- */
@keyframes rise { from { opacity: 0; transform: translateY(14px); } to { opacity: 1; transform: none; } }
@keyframes onair {
  0%   { box-shadow: 0 0 0 0 rgba(239,68,68,.55); }
  70%  { box-shadow: 0 0 0 9px rgba(239,68,68,0); }
  100% { box-shadow: 0 0 0 0 rgba(239,68,68,0); }
}
@media (prefers-reduced-motion: reduce) {
  * { animation: none !important; transition: none !important; }
}

/* A pinned item — deliberately NOT shaped like a story card. It is the show
   talking, not the news, and it should not be mistaken for something to vote
   on (owner 2026-07-31). */
.pinned { border: 1px solid var(--accent); background: var(--accent-soft);
  border-radius: var(--radius); padding: 14px 16px; margin-bottom: 14px;
  display: flex; flex-direction: column; gap: 6px; align-items: flex-start; }
.pinhead { display: flex; align-items: center; gap: 8px; }
.pinmark { font-size: .9rem; }
.pinkind { font-size: .7rem; letter-spacing: .1em; text-transform: uppercase;
  font-weight: 700; color: var(--accent); }
.pintitle { font-size: 1.05rem; font-weight: 650; line-height: 1.3; color: var(--text); }
.pinbody { font-size: .93rem; color: var(--text-secondary, var(--muted)); white-space: pre-wrap; }
