/*
 * Shared ViewComponent styles (Package 2b · FE-304…FE-317) — the class rules the
 * v3 mockup (documents/mockups/web-app-ui-v3.html) declares inline, ported into
 * the shipped pipeline so the ViewComponents under app/components/web/ resolve.
 * tokens.css owns the palette + shell; cover_art.css owns the .art-* / .mag-* plates
 * + .pc-thumb/.dropcap; THIS file owns the reusable chrome + card atoms.
 *
 * Rule: colors resolve against the v3 tokens (no hardcoded hex). Translucent
 * scrim/overlay tints on the dark editorial covers stay as rgba() (not hex),
 * exactly as the mockup layers them.
 */

/* ── Shared button atoms ─────────────────────────────────────────────────── */
.btn-primary {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--space-sm);
  background: var(--accent); color: var(--on-primary);
  font-family: var(--font-body); font-weight: 700; font-size: var(--fs-body-sm);
  border: none; border-radius: var(--r-pill); padding: 10px 22px; cursor: pointer;
  text-decoration: none; line-height: 1;
}
.btn-primary:hover { background: var(--primary-bright); }
.btn-primary.lg { padding: 13px 26px; font-size: 15.5px; }
.btn-primary.sm { padding: 8px 16px; font-size: var(--fs-caption); }
.btn-primary:disabled { opacity: .5; cursor: not-allowed; }
.btn-primary:disabled:hover { background: var(--accent); }
.btn-ghost {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--space-sm);
  background: transparent; color: var(--text);
  font-family: var(--font-body); font-weight: 700; font-size: var(--fs-body-sm);
  border: 1.5px solid var(--border); border-radius: var(--r-pill); padding: 10px 20px; cursor: pointer;
  text-decoration: none; line-height: 1;
}
.btn-ghost.lg { padding: 13px 26px; font-size: 15.5px; }

/* The thread reply CTA (logged-in only) — a btn-primary link into the shared
   compose reply surface; this rule only spaces it below the focal post. */
.thread-reply { margin: 14px 0 4px; }

/* ── Form controls — global themed inputs / textareas / selects / checks ──────
   The layout links THIS stylesheet on EVERY web surface, so styling the bare
   element selectors gives every native control the Shiijak brand automatically —
   inputs, textareas, <select> (custom caret), checkboxes + radios — across every
   state: default / hover / focus-visible / placeholder / disabled / invalid. Links
   and buttons are deliberately left alone (they own their own atoms above /
   tokens.css). Every color resolves against the v3 tokens — no one-off hex (the
   light/dark <select> caret data-URIs are the one unavoidable exception: an inline
   SVG background can't read a CSS var, so its stroke mirrors --ink-2 light/dark).

   Specificity: the RESTING rules wrap their type list in :where() so they sit at
   bare-element weight (0,0,1). Every per-page class rule (.compose-input,
   .dm-*__input, .auth__form input, .onboard__chip-input, …) therefore still wins
   and layers its own shape/size/surface on top — no double borders, and the
   visually-hidden onboarding checkbox keeps its 1px geometry untouched. */
input:where([type="text"], [type="email"], [type="password"], [type="url"],
            [type="search"], [type="tel"], [type="number"], [type="date"],
            [type="datetime-local"], [type="month"], [type="week"], [type="time"]),
textarea,
select {
  font-family: var(--font-body);
  font-size: var(--fs-body);
  line-height: 1.4;
  color: var(--text);
  background: var(--surface-elevated);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  padding: 11px 14px;
  max-width: 100%;
  transition: border-color 0.12s ease, box-shadow 0.12s ease;
  -webkit-appearance: none;
     -moz-appearance: none;
          appearance: none;
}
textarea { line-height: 1.5; resize: vertical; }

/* Muted, engine-consistent placeholders (Firefox otherwise dims to ~0.54). */
:where(input, textarea)::placeholder { color: var(--text-muted); opacity: 1; }

/* Hover — a touch stronger hairline (skip while focused / disabled). */
input:where([type="text"], [type="email"], [type="password"], [type="url"],
            [type="search"], [type="tel"], [type="number"], [type="date"],
            [type="datetime-local"], [type="month"], [type="week"], [type="time"]):hover:not(:focus):not(:disabled),
textarea:hover:not(:focus):not(:disabled),
select:hover:not(:disabled) { border-color: var(--outline); }

/* Focus — the brand-violet ring (box-shadow, so it follows any border-radius,
   incl. the pill DM / search inputs). A visible indicator, never a bare
   outline:none-with-nothing. */
input:where([type="text"], [type="email"], [type="password"], [type="url"],
            [type="search"], [type="tel"], [type="number"], [type="date"],
            [type="datetime-local"], [type="month"], [type="week"], [type="time"]):focus-visible,
textarea:focus-visible,
select:focus-visible {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--primary-container);
}

/* Invalid — only once the field has been touched (:user-invalid), plus an explicit
   aria-invalid hook: crimson hairline + a tint ring while focused. */
input:user-invalid,
textarea:user-invalid,
input[aria-invalid="true"],
textarea[aria-invalid="true"] { border-color: var(--tertiary); }
input:user-invalid:focus-visible,
textarea:user-invalid:focus-visible,
input[aria-invalid="true"]:focus-visible,
textarea[aria-invalid="true"]:focus-visible { box-shadow: 0 0 0 3px var(--tertiary-container); }

/* Disabled — dimmed + non-interactive. */
input:disabled,
textarea:disabled,
select:disabled {
  background: var(--surface-muted);
  color: var(--text-muted);
  cursor: not-allowed;
  opacity: 0.7;
}

/* <select> — themed like an input + a custom caret (native arrows can't be styled
   cross-browser, hence appearance:none above + this inline-SVG chevron). The option
   list stays native (unstyleable cross-browser) — that's fine. */
select {
  padding-right: 40px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='none' stroke='%23474556' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 6l4 4 4-4'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 14px center;
  background-size: 16px 16px;
  cursor: pointer;
}
/* Dark palette — swap the caret stroke to the dark --ink-2 (both no-JS default and
   the explicit toggle), mirroring how tokens.css applies the dark theme. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]):not(.theme-light) select {
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='none' stroke='%239AA4B6' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 6l4 4 4-4'/%3E%3C/svg%3E");
  }
}
:root[data-theme="dark"] select,
.theme-dark select {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='none' stroke='%239AA4B6' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 6l4 4 4-4'/%3E%3C/svg%3E");
}

/* <input type="date"> — theme the field (above) + the calendar-picker button so it
   stays visible on the dark palette (the default glyph is near-black). */
input[type="date"]::-webkit-calendar-picker-indicator { cursor: pointer; opacity: 0.6; }
input[type="date"]:hover::-webkit-calendar-picker-indicator { opacity: 0.9; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]):not(.theme-light) input[type="date"]::-webkit-calendar-picker-indicator { filter: invert(1); opacity: 0.75; }
}
:root[data-theme="dark"] input[type="date"]::-webkit-calendar-picker-indicator,
.theme-dark input[type="date"]::-webkit-calendar-picker-indicator { filter: invert(1); opacity: 0.75; }

/* Checkboxes + radios — kept NATIVE (accessible, and Capybara check/choose still
   toggles them) and simply tinted to the brand via accent-color, with a slightly
   larger hit target + the same focus-visible ring. No hidden-input/label swap
   (that once broke a system spec). :where() keeps the size at bare-element weight
   so the visually-hidden .onboard__chip-input keeps its own 1px geometry. */
input:where([type="checkbox"], [type="radio"]) {
  accent-color: var(--accent);
  inline-size: 1.1rem;
  block-size: 1.1rem;
  cursor: pointer;
}
input[type="checkbox"]:focus-visible,
input[type="radio"]:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--r-sm);
}
input[type="checkbox"]:disabled,
input[type="radio"]:disabled { cursor: not-allowed; opacity: 0.6; }

/* ── Shared page chrome — masthead brand mark + footer ───────────────────────
   The layout's <header> (content_for :header) and the shared web/pages/_footer
   render on EVERY surface, so their styles live here (the globally-linked
   stylesheet) rather than web/pages.css (marketing-only). The masthead only
   appears on nav-less surfaces (see layout comment); on app surfaces the nav
   rail's .rail-logo is the single brand mark. */
.site-mark {
  max-width: var(--measure-wide); margin: 0 auto; padding: var(--space-lg);
  font: 600 var(--fs-title) / 1 var(--font-display); letter-spacing: -0.01em;
}
.site-mark a { color: var(--ink); text-decoration: none; }
.site-foot {
  max-width: var(--measure-wide); margin: 0 auto; padding: var(--space-xl) var(--space-lg);
  border-top: 1px solid var(--border); display: flex; gap: var(--space-lg);
  justify-content: space-between; flex-wrap: wrap;
  font: 400 var(--fs-caption) / 1.4 var(--font-body); color: var(--text-muted);
}
.site-foot a { color: var(--text-muted); text-decoration: none; }
.site-foot a:hover { color: var(--accent); }
.site-foot nav { display: flex; gap: var(--space-lg); flex-wrap: wrap; }

/* ── Discovery shelves — "People to follow" rows (search + explore) ───────────
   Sibling of the .pick-row topic row: one clean horizontal row per person —
   avatar left, bold display-name in normal ink (NOT a blue underlined link) +
   muted @handle, a hairline divider, and any Follow pill pushed to the edge. */
.explore-shelf { margin: 4px 0 18px; }
.person-row {
  display: flex; align-items: center; gap: 12px; padding: 10px 0;
  border-bottom: 1px solid var(--border);
}
.person-row:last-child { border-bottom: none; }
.person-row__id {
  display: flex; align-items: center; gap: 12px; min-width: 0; flex: 1 1 auto;
  text-decoration: none; color: var(--text);
}
.person-row__name { display: flex; flex-direction: column; min-width: 0; line-height: 1.3; }
.person-row__name b {
  font-family: var(--font-body); font-size: var(--fs-body-sm); font-weight: 700;
  color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.person-row__handle { font-size: 12.5px; color: var(--text-muted); }
.person-row__id:hover .person-row__name b { color: var(--accent); }
/* Inline follow (search rows) — the actionable profile-actions pill + ⋯ menu sits
   at the row's trailing edge, never squeezing the identity block. */
.person-row .profile-actions { flex: 0 0 auto; margin-left: auto; }

/* ── Avatar (FE-306) ─────────────────────────────────────────────────────── */
.av-pv {
  border-radius: 50%; overflow: hidden; flex: 0 0 auto;
  width: 44px; height: 44px; display: block;
  box-shadow: inset 0 0 0 1px rgba(20, 15, 10, .06); background: var(--cover);
}
.av-pv svg, .av-pv .av-img { width: 100%; height: 100%; display: block; }
.av-pv .av-img { object-fit: cover; }
.theme-dark .av-pv { box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .08); }
/* Default (no-photo) fallback: deterministic initials on a tinted disc (.av-1…7),
   not a bare dark circle. Fills + centers inside the .av-pv frame; .av-pv's
   overflow:hidden + radius clip it round, its inset ring stays on top. */
.av-pv__mono {
  width: 100%; height: 100%; display: flex; align-items: center; justify-content: center;
  color: var(--on-primary); font-family: var(--font-display); font-weight: 600; line-height: 1;
  text-transform: uppercase; letter-spacing: .02em; user-select: none;
}
/* The uploaded-avatar image-error fallback: the <img> and the initials tile each
   set their own display, so the plain `hidden` attribute (which only sets
   display:none at low specificity) is overridden — force it here so the broken
   <img> truly hides and its pre-rendered initials tile stays hidden until the
   avatar controller reveals it (mirrors .lp-card__img[hidden]). */
.av-pv .av-img[hidden], .av-pv__mono[hidden] { display: none; }
.av {
  border-radius: 50%; display: flex; align-items: center; justify-content: center;
  color: var(--on-primary); font-family: var(--font-display); font-weight: 600;
  flex: 0 0 auto; width: 44px; height: 44px; font-size: 16px;
}
.av-1 { background: linear-gradient(135deg, #6B4EFF, #3D2E8C); }
.av-2 { background: linear-gradient(135deg, #B98A4B, #7A5A2E); }
.av-3 { background: linear-gradient(135deg, #7A5CFF, #3A2A88); }
.av-4 { background: linear-gradient(135deg, #4A5568, #2D3748); }
.av-5 { background: linear-gradient(135deg, #8C3B7A, #5C2560); }
.av-6 { background: linear-gradient(135deg, #9C8B5E, #6B5D3D); }
.av-7 { background: linear-gradient(135deg, #4E4A9E, #2A2860); }

/* Verified badge — the ONE violet circular check beside a verified account's name
   (post cards + profile header, via web/shared/_verified_badge). fill=currentColor
   resolves --verified (no one-off hex); the single "Verified account" aria-label
   lives in the shared partial. .vchk stays for any legacy inline check. */
.vbadge { flex: 0 0 auto; color: var(--verified); vertical-align: middle; }
.vchk { flex: 0 0 auto; }
.vlabel {
  display: inline-flex; align-items: center; gap: 4px;
  font-family: var(--font-body); font-size: 12px; font-weight: 600; color: var(--violet-600);
}

/* ── Mobile top bar (FE — the <768px nav; the rail is desktop-only) ───────────
   Below 768px the desktop rail is hidden and this compact bar (logo + hamburger)
   takes over, opening a right-edge drawer with the SAME destinations. The drawer
   is a native <details> so it toggles with JS OFF; the lazy `mobile-nav` Stimulus
   controller layers Esc / outside-click close + focus-trap + aria-expanded sync
   on top. Shown < 768px, hidden ≥ 768px (where .rail-full returns). */
.mnav {
  display: flex; align-items: center; justify-content: space-between; gap: var(--space-md);
  padding: 10px 16px; background: var(--surface); border-bottom: 1px solid var(--border);
  position: sticky; top: 0; z-index: 50;
}
.mnav__logo {
  display: inline-flex; align-items: center; gap: 9px; text-decoration: none;
  font-family: var(--font-display); font-weight: 600; font-size: var(--fs-subtitle);
  color: var(--text); letter-spacing: -.01em;
}
.mnav__logo em { color: var(--accent); font-style: normal; }
.mnav__disc { position: static; margin: 0; }
.mnav__toggle {
  display: inline-flex; align-items: center; justify-content: center; width: 42px; height: 42px;
  border-radius: var(--r-md); cursor: pointer; color: var(--text);
  background: transparent; border: 1px solid var(--border); list-style: none;
}
.mnav__toggle::-webkit-details-marker { display: none; }
.mnav__toggle::marker { content: ""; }
.mnav__toggle:hover { background: var(--accent-subtle); color: var(--accent); border-color: var(--accent-subtle); }
/* Scrim + drawer are shown ONLY while the <details> is [open] — author-controlled,
   so we never depend on the UA's (version-varying) hiding of closed <details>
   content. This is also what makes JS `details.open = false` (Esc / scrim click)
   reliably re-hide them. */
.mnav__scrim { display: none; position: fixed; inset: 0; z-index: 55; background: rgba(10, 10, 12, .5); }
.mnav__drawer {
  display: none; position: fixed; top: 0; right: 0; z-index: 56; height: 100%; width: min(84vw, 320px);
  overflow-y: auto; flex-direction: column; gap: 2px;
  padding: 18px 14px calc(18px + env(safe-area-inset-bottom, 0px));
  background: var(--surface); border-left: 1px solid var(--border);
  box-shadow: -18px 0 40px -24px rgba(10, 10, 12, .5);
}
.mnav__disc[open] .mnav__scrim { display: block; }
.mnav__disc[open] .mnav__drawer { display: flex; }
.mnav__item {
  display: flex; align-items: center; gap: 14px; padding: 12px; border-radius: var(--r-md);
  font-size: 16px; font-weight: 600; color: var(--text); text-decoration: none;
}
.mnav__item svg { flex: 0 0 auto; color: var(--text-muted); }
.mnav__item.on { background: var(--accent-subtle); color: var(--accent); }
.mnav__item.on svg { color: var(--accent); }
.mnav__item .rail-badge { margin-left: auto; }
.mnav__compose, .mnav__join {
  display: flex; align-items: center; justify-content: center; gap: var(--space-sm);
  background: var(--accent); color: var(--on-primary); font-weight: 700; font-size: var(--fs-body-sm);
  border: none; border-radius: var(--r-pill); padding: 13px 0; margin-top: 12px; text-decoration: none;
}
.mnav__ghost {
  display: flex; align-items: center; justify-content: center; background: transparent; color: var(--text);
  font-weight: 700; font-size: var(--fs-body-sm); border: 1.5px solid var(--border);
  border-radius: var(--r-pill); padding: 11px 0; margin-top: 12px; text-decoration: none;
}
/* Sign out in the authed mobile drawer — a red-inked ghost so logout is reachable
   on a phone without digging through Settings. */
.mnav__signout-form { margin: 12px 0 0; }
.mnav__signout {
  display: flex; align-items: center; justify-content: center; gap: var(--space-sm); width: 100%;
  background: transparent; color: var(--heart-red); font-weight: 700; font-size: var(--fs-body-sm);
  font-family: var(--font-body); border: 1.5px solid var(--border); border-radius: var(--r-pill);
  padding: 11px 0; cursor: pointer;
}
.mnav__signout svg { flex: 0 0 auto; }
.mnav__signout:hover { border-color: var(--heart-red); background: rgba(255, 48, 64, .08); }
/* Slide/fade in only when the visitor hasn't asked for reduced motion (CSS
   animations replay when <details> reveals the drawer — display can't transition). */
@media (prefers-reduced-motion: no-preference) {
  .mnav__drawer { animation: mnav-slide-in .22s ease both; }
  .mnav__scrim  { animation: mnav-fade-in .22s ease both; }
}
@keyframes mnav-slide-in { from { transform: translateX(100%); } to { transform: translateX(0); } }
@keyframes mnav-fade-in  { from { opacity: 0; } to { opacity: 1; } }

/* ── Nav rail (FE-304) ───────────────────────────────────────────────────── */
.rail-full {
  width: 224px; flex: 0 0 auto; background: var(--surface);
  border-right: 1px solid var(--border);
  display: none; flex-direction: column; padding: 22px 14px; gap: 2px; min-height: 100%;
}
/* The rail ↔ mobile-bar swap: the bar owns < 768px, the rail owns ≥ 768px. */
@media (min-width: 768px) {
  .mnav { display: none; }
  .rail-full { display: flex; }
}
.rail-logo {
  display: inline-flex; align-items: center; gap: 9px;
  font-family: var(--font-display); font-weight: 600; font-size: var(--fs-subtitle);
  color: var(--text); padding: 6px 10px 24px; letter-spacing: -.01em; text-decoration: none;
}
.rail-logo__mark { flex: none; display: block; }
.rail-logo__mark-bg { fill: var(--accent); }
.rail-logo__word { display: inline-block; }
.rail-logo em { color: var(--accent); font-style: normal; }
.rail-item {
  display: flex; align-items: center; gap: 14px; padding: 11px 12px; border-radius: var(--r-md);
  font-size: 15.5px; font-weight: 600; color: var(--text); text-decoration: none; cursor: pointer;
}
.rail-item svg { flex: 0 0 auto; color: var(--text-muted); }
.rail-item.on { background: var(--accent-subtle); color: var(--accent); }
.rail-item.on svg { color: var(--accent); }
.rail-badge {
  margin-left: auto; background: var(--badge-red); color: var(--on-primary);
  font-size: 11px; font-weight: 700; border-radius: var(--r-pill);
  min-width: 18px; height: 18px; display: flex; align-items: center; justify-content: center; padding: 0 5px;
}
.rail-spacer { flex: 1; }
.rail-user {
  display: flex; align-items: center; gap: 12px; padding: 12px;
  border-top: 1px solid var(--border); margin-top: 4px; font-size: 14.5px; font-weight: 600;
  color: var(--text); text-decoration: none; border-radius: var(--r-md);
}
a.rail-user:hover { background: var(--accent-subtle); color: var(--accent); }

/* ── Nav-rail account menu (profile chip → View profile / Settings / Sign out) ──
   A native <details> popover: the summary IS the .rail-user chip; the menu opens
   downward, absolutely positioned so it never reflows the rail. Shown ONLY while
   [open] (author-controlled, so the user-menu controller's `open = false` reliably
   re-hides it). */
.rail-menu { position: relative; }
.rail-user__name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rail-menu__toggle { list-style: none; cursor: pointer; }
.rail-menu__toggle::-webkit-details-marker { display: none; }
.rail-menu__toggle::marker { content: ""; }
.rail-menu__toggle:hover { background: var(--accent-subtle); color: var(--accent); }
.rail-menu__chev { flex: 0 0 auto; margin-left: auto; color: var(--text-muted); transition: transform 160ms ease; }
.rail-menu[open] .rail-menu__chev { transform: rotate(180deg); }
.rail-menu__pop {
  display: none; position: absolute; top: calc(100% + 6px); left: 0; right: 0; z-index: 60;
  flex-direction: column; gap: 2px; padding: 6px;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-md);
  box-shadow: 0 12px 32px -12px rgba(10, 10, 12, .45);
}
.rail-menu[open] .rail-menu__pop { display: flex; }
.rail-menu__item {
  display: flex; align-items: center; gap: 10px; padding: 10px 12px; border-radius: var(--r-sm);
  font: 600 var(--fs-body-sm) / 1 var(--font-body); color: var(--text); text-decoration: none;
  background: transparent; border: none; cursor: pointer; text-align: left; width: 100%; box-sizing: border-box;
}
.rail-menu__item:hover, .rail-menu__item:focus-visible { background: var(--accent-subtle); color: var(--accent); }
.rail-menu__item--danger { color: var(--heart-red); }
.rail-menu__item--danger:hover, .rail-menu__item--danger:focus-visible { background: rgba(255, 48, 64, .1); color: var(--heart-red); }
.rail-menu__form { margin: 0; display: block; }
@media (prefers-reduced-motion: no-preference) {
  .rail-menu[open] .rail-menu__pop { animation: rail-menu-in .16s ease both; }
}
@keyframes rail-menu-in { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: none; } }
.rail-compose {
  display: flex; align-items: center; justify-content: center; gap: var(--space-sm);
  background: var(--accent); color: var(--on-primary); font-weight: 700; font-size: var(--fs-body-sm);
  border: none; border-radius: var(--r-pill); padding: 13px 0; margin-top: 10px; cursor: pointer;
  font-family: var(--font-body); text-decoration: none;
}
.rail-ghost {
  display: flex; align-items: center; justify-content: center; background: transparent; color: var(--text);
  font-weight: 700; font-size: var(--fs-body-sm); border: 1.5px solid var(--border);
  border-radius: var(--r-pill); padding: 11px 0; margin-top: 8px; cursor: pointer;
  font-family: var(--font-body); text-decoration: none;
}
.rail-join {
  display: flex; align-items: center; justify-content: center; background: var(--accent); color: var(--on-primary);
  font-weight: 700; font-size: var(--fs-body-sm); border: none; border-radius: var(--r-pill);
  padding: 13px 0; margin-top: 8px; cursor: pointer; font-family: var(--font-body); text-decoration: none;
}
.rail-qual {
  font-family: var(--font-body); font-size: 11.5px; line-height: 1.4; color: var(--text-muted);
  text-align: center; margin-top: 9px; display: flex; align-items: center; justify-content: center; gap: 5px;
}
.rail-qual svg { color: var(--accent); flex: 0 0 auto; }

/* ── Newsstand rail (FE-305) ─────────────────────────────────────────────── */
.newsstand-rail {
  width: 296px; flex: 0 0 auto; border-left: 1px solid var(--border);
  padding: 28px 22px; min-height: 100%;
}
.rail-label {
  font-family: var(--font-body); font-size: 12.5px; font-weight: 700; letter-spacing: .14em;
  text-transform: uppercase; color: var(--text-muted); margin: 20px 0 10px;
}
.rail-label:first-child { margin-top: 0; }
.cover-mini {
  color: var(--cover-text); border-radius: var(--r-lg); padding: 22px 18px; margin-bottom: 4px;
  position: relative; overflow: hidden; display: block; text-decoration: none;
}
.cover-mini .ce { font-size: 11px; letter-spacing: .16em; text-transform: uppercase; color: var(--primary-tint); font-weight: 700; }
.cover-mini .ct { font-family: var(--font-display); font-weight: 600; font-size: var(--fs-title); margin: 6px 0 4px; }
.cover-mini .cs { font-size: var(--fs-caption); color: rgba(245, 245, 245, .72); line-height: 1.35; }
.cover-mini .cc { margin-top: 10px; font-size: 12.5px; font-weight: 600; color: rgba(245, 245, 245, .62); }
.pick-row {
  display: flex; justify-content: space-between; align-items: center; gap: 10px; padding: 9px 0;
  border-bottom: 1px solid var(--border); font-size: var(--fs-body-sm); font-weight: 600;
  color: var(--text); text-decoration: none;
}
.pick-row .pr-l { display: flex; flex-direction: column; min-width: 0; }
.pick-row .pr-l span { font-size: 12.5px; font-weight: 500; color: var(--text-muted); }
/* When the topic name is promoted to an <h3> (Explore / Newsstand outline), keep
   it visually identical to the <span> row: inherit the .pick-row font, no UA
   heading size or margin. */
.pick-row h3.pr-l { font: inherit; margin: 0; }
.pick-row .pr-meta { font-size: 12.5px; color: var(--text-muted); }
.rail-seeall {
  display: inline-flex; align-items: center; gap: 6px; margin-top: 12px; font-size: 13.5px;
  font-weight: 700; color: var(--accent); text-decoration: none;
}
.rail-seeall span { color: var(--text-muted); font-weight: 500; }
.syt-spot {
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-md);
  padding: 14px 16px; font-size: 13.5px; color: var(--text); line-height: 1.4;
}
.syt-spot b { display: block; font-family: var(--font-display); font-size: var(--fs-body-sm); margin-bottom: 1px; font-weight: 600; }
.syt-spot .cred { display: block; font-family: var(--font-body); font-size: 12px; color: var(--violet-600); font-weight: 600; margin-bottom: 7px; }
.syt-spot .desc { color: var(--text-muted); }
.syt-spot .syt-makeown { display: inline-flex; align-items: center; gap: 5px; margin-top: 10px; font-size: 12.5px; font-weight: 700; color: var(--accent); text-decoration: none; }
.rail-foot {
  margin-top: 22px; padding-top: 16px; border-top: 1px solid var(--border);
  display: flex; flex-wrap: wrap; gap: 12px; font-size: 12.5px; color: var(--text-muted);
}
.rail-foot a { color: var(--text-muted); text-decoration: none; }
.rail-foot a:hover { color: var(--accent); }

/* ── Follow pill (FE-307/FE-317) ─────────────────────────────────────────── */
.follow-pill {
  display: inline-flex; align-items: center; gap: 5px; font-family: var(--font-body); font-size: var(--fs-caption);
  font-weight: 700; line-height: 1; color: var(--violet-600); background: var(--surface-elevated);
  border: 1.5px solid var(--violet-500); border-radius: var(--r-pill); padding: 6px 13px 6px 10px;
  cursor: pointer; white-space: nowrap; text-decoration: none;
  transition: background .14s, color .14s, transform .1s;
}
.follow-pill svg { flex: 0 0 auto; }
.follow-pill:hover { background: var(--accent-subtle); }
.follow-pill:active { transform: scale(.96); }
.follow-pill.following { background: var(--violet-500); color: var(--on-primary); border-color: var(--violet-500); }
.follow-pill.staged { border-style: dashed; color: var(--violet-600); background: var(--surface-elevated); }
.follow-pill.sm { font-size: 12px; padding: 5px 11px 5px 9px; }
/* Explore discovery shelves are served from the SHARED anonymous page cache, so a
   viewer-personalized Follow control there is a dead control (it bounced logged-in
   members to /login and never followed). It renders as this neutral "View" link to
   the profile instead — muted, NOT the violet follow CTA, so it reads as a plain
   navigation that works for anon + authed alike. */
.follow-pill--view { color: var(--text-muted); border-color: var(--border); background: var(--surface); }
.follow-pill--view:hover { background: var(--accent-subtle); color: var(--text); }
.stage-cap { display: block; font-family: var(--font-body); font-size: 11.5px; line-height: 1.35; color: var(--text-muted); margin-top: 5px; }
.stage-cap b { color: var(--violet-600); font-weight: 600; }
.follow-pill-wrap { display: inline-block; }

/* ── Topic action row — a tidy SECONDARY Follow + Pin-to-Home group ───────────
   The topic Follow and "Pin to Home" pills are SECONDARY calls-to-action: full
   brand-violet is reserved for the PRIMARY CTAs (compose, save profile). So the
   two pills sit in one spaced group (never touching) and take the smaller, more
   restrained `.follow-pill--secondary` scale — a neutral outline when inactive
   and a soft violet TINT (not a full-saturation fill) when active. The profile
   Follow pill and the L1 staged pill keep the stronger base `.follow-pill`.

   Rules are scoped under `.topic-actions` so their specificity (0,2,0) reliably
   beats the `button.follow-pill { font: inherit }` reset below (0,1,1) — that
   reset otherwise pulls the button up to the 19px body font. */
.topic-actions {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-md);
  margin: 14px 0 6px;
}
.topic-actions .topic-follow { margin: 0; }
.topic-actions .pf-action-form { display: inline; margin: 0; }

.topic-actions .follow-pill--secondary {
  font-size: 12.5px; font-weight: 600; min-height: 32px; padding: 6px 13px 6px 11px;
  color: var(--text-muted); background: var(--surface-elevated);
  border: 1px solid var(--border);
}
.topic-actions .follow-pill--secondary:hover { background: var(--accent-subtle); color: var(--accent); border-color: var(--accent); }
.topic-actions .follow-pill--secondary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* Active ("Following" / "Pinned to Home") — a soft violet tint, not the primary
   fill: reads as ON without shouting for the primary-CTA color budget. */
.topic-actions .follow-pill--secondary.following {
  background: var(--primary-container); color: var(--on-primary-container);
  border-color: var(--primary-container);
}
.topic-actions .follow-pill--secondary.following:hover { background: var(--primary-container); border-color: var(--accent); }

/* ── L2 profile actions — interactive follow pill + block/mute overflow menu ── */
.profile-actions { display: flex; align-items: center; gap: 8px; }
.profile-actions .pf-action-form { display: inline; margin: 0; }
button.follow-pill { font: inherit; }
.pf-menu { position: relative; }
.pf-menu__trigger {
  display: inline-flex; align-items: center; justify-content: center; width: 32px; height: 32px;
  border-radius: 50%; cursor: pointer; list-style: none; color: var(--text-muted);
  background: var(--surface-elevated); border: 1.5px solid var(--border);
  transition: background .14s, color .14s;
}
.pf-menu__trigger::-webkit-details-marker { display: none; }
.pf-menu__trigger:hover, .pf-menu[open] .pf-menu__trigger { color: var(--text); background: var(--accent-subtle); }
.pf-menu__panel {
  position: absolute; right: 0; top: calc(100% + 6px); z-index: 20; min-width: 152px;
  display: flex; flex-direction: column; padding: 6px; background: var(--surface-elevated);
  border: 1px solid var(--border); border-radius: var(--r-md); box-shadow: 0 6px 20px -8px rgba(20, 15, 10, .12);
}
.pf-menu__form { display: block; margin: 0; }
.pf-menu__item {
  display: block; width: 100%; text-align: left; cursor: pointer; font-family: var(--font-body);
  font-size: var(--fs-body-sm); font-weight: 500; color: var(--text); background: transparent;
  border: 0; padding: 8px 10px; border-radius: var(--r-sm);
}
.pf-menu__item:hover { background: var(--accent-subtle); }

/* ── Post owner overflow menu (⋯ → Edit/Delete) — L2, author-only ─────────── */
.pc-menu { position: relative; margin-left: auto; }
.pc-menu__trigger {
  display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px;
  border-radius: 50%; cursor: pointer; list-style: none; color: var(--text-muted);
  background: transparent; border: 0; transition: background .14s, color .14s;
}
.pc-menu__trigger::-webkit-details-marker { display: none; }
.pc-menu__trigger:hover, .pc-menu[open] .pc-menu__trigger { color: var(--text); background: var(--accent-subtle); }
.pc-menu__panel {
  position: absolute; right: 0; top: calc(100% + 4px); z-index: 20; min-width: 140px;
  display: flex; flex-direction: column; padding: 6px; background: var(--surface-elevated);
  border: 1px solid var(--border); border-radius: var(--r-md); box-shadow: 0 6px 20px -8px rgba(20, 15, 10, .12);
}
.pc-menu__form { display: block; margin: 0; }
.pc-menu__item {
  display: block; width: 100%; text-align: left; text-decoration: none; cursor: pointer;
  font-family: var(--font-body); font-size: var(--fs-body-sm); font-weight: 500; color: var(--text);
  background: transparent; border: 0; padding: 8px 10px; border-radius: var(--r-sm);
}
.pc-menu__item:hover { background: var(--accent-subtle); }
.pc-menu__item--danger { color: var(--heart-red); }

/* ── Breadcrumb (FE-315) — no exact v3 markup; semantic, token-styled ─────── */
.breadcrumb { font-family: var(--font-body); font-size: var(--fs-caption); }
.breadcrumb__list { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
.breadcrumb__item { display: inline-flex; align-items: center; gap: 6px; color: var(--text-muted); }
.breadcrumb__item + .breadcrumb__item::before { content: "\203A"; color: var(--outline-variant); }
.breadcrumb__item a { color: var(--text-muted); text-decoration: none; font-weight: 600; }
.breadcrumb__item a:hover { color: var(--accent); }
.breadcrumb__current { color: var(--text); font-weight: 700; }

/* ── Pagination (FE-316) — no exact v3 markup; semantic, token-styled ─────── */
.pagination { margin: var(--space-xl) 0 var(--space-sm); }
.pagination__list { list-style: none; margin: 0; padding: 0; display: flex; align-items: center; gap: 6px; justify-content: center; flex-wrap: wrap; }
.pagination__link {
  display: inline-flex; align-items: center; justify-content: center; min-width: 38px; height: 38px; padding: 0 12px;
  border-radius: var(--r-md); border: 1px solid var(--border); background: var(--surface-elevated);
  color: var(--text); font-family: var(--font-body); font-size: var(--fs-body-sm); font-weight: 600; text-decoration: none;
}
.pagination__link:hover { background: var(--accent-subtle); color: var(--accent); border-color: var(--accent-subtle); }
.pagination__current {
  display: inline-flex; align-items: center; justify-content: center; min-width: 38px; height: 38px; padding: 0 12px;
  border-radius: var(--r-md); background: var(--accent); color: var(--on-primary);
  font-family: var(--font-body); font-size: var(--fs-body-sm); font-weight: 700;
}
.pagination__gap { color: var(--text-muted); padding: 0 4px; }

/* ── Join bar (FE-308) ───────────────────────────────────────────────────── */
.join-bar {
  display: flex; align-items: center; gap: 16px; background: var(--surface-elevated);
  border: 1px solid var(--border); border-radius: var(--r-lg); padding: 15px 18px; margin-top: 16px;
  box-shadow: 0 6px 20px -8px rgba(20, 15, 10, .12); flex-wrap: wrap;
}
.join-proof { display: flex; align-items: center; gap: 12px; flex: 1; min-width: 230px; }
.join-proof .avs { display: flex; flex: 0 0 auto; }
.join-proof .avs .av-pv { width: 30px; height: 30px; margin-left: -8px; box-shadow: 0 0 0 2px var(--surface-elevated); }
.join-proof .avs .av-pv:first-child { margin-left: 0; }
.join-proof .jtxt { font-family: var(--font-body); font-size: 13.5px; line-height: 1.4; color: var(--text-muted); }
.join-proof .jtxt b { color: var(--text); font-weight: 700; }
.join-proof .jtxt .vel { color: var(--violet-600); font-weight: 600; }
.join-bar .jact { display: flex; gap: 10px; flex: 0 0 auto; }
.join-bar .jact a, .join-bar .jact button { white-space: nowrap; }

/* ── Join nudge (FE-308) ─────────────────────────────────────────────────── */
.nudge {
  display: flex; align-items: center; gap: 16px; border: 1px solid var(--accent-subtle);
  background: var(--primary-container); border-radius: var(--r-lg); padding: 16px 18px; margin: 18px 0;
}
.nudge .nx {
  flex: 0 0 auto; width: 42px; height: 42px; border-radius: var(--r-md); background: var(--accent);
  color: var(--on-primary); display: flex; align-items: center; justify-content: center;
}
.nudge .nbody { flex: 1; min-width: 0; }
.nudge .nbody .nt { font-family: var(--font-body); font-size: var(--fs-body-sm); font-weight: 700; color: var(--text); line-height: 1.35; }
.nudge .nbody .ns { font-size: 13.5px; color: var(--text-muted); margin-top: 2px; }
.nudge .nbody .ns .vel { color: var(--violet-600); font-weight: 600; }

/* ── Post card (FE-311/FE-312) ───────────────────────────────────────────── */
.post-card { padding: 18px 0; border-bottom: 1px solid var(--border); display: flex; gap: 14px; }
.post-card:first-of-type { padding-top: 0; }
.post-card--flat { border-bottom: none; }
/* An interactive card opens its thread on click (post_card_controller) — signal it. */
.post-card--open { cursor: pointer; }
.post-card--open:hover { background: rgba(20, 15, 10, 0.02); }
.theme-dark .post-card--open:hover { background: rgba(255, 255, 255, 0.03); }
.pc-body { flex: 1; min-width: 0; }
/* "Reposted by @X" attribution (Following feed) — a muted, small caption above
   the author row, with the repeat glyph. Parity with the mobile card. */
.pc-repost-attr {
  display: flex; align-items: center; gap: 6px; margin: 0 0 4px;
  font-size: var(--fs-caption); font-weight: 600; color: var(--text-muted);
}
.pc-repost-attr svg { stroke: currentColor; fill: none; flex: none; }
.pc-meta { display: flex; align-items: center; gap: 6px; font-size: var(--fs-body-sm); flex-wrap: wrap; }
.pc-name { font-weight: 700; color: var(--text); text-decoration: none; }
a.pc-name:hover { color: var(--accent); }
.pc-hd { color: var(--text-muted); font-size: 14px; text-decoration: none; }
.pc-edited { color: var(--text-muted); font-size: 14px; }
.pc-role { color: var(--text-muted); font-size: 13.5px; margin-top: 1px; }
.pc-text { font-size: 18.5px; line-height: 1.58; color: var(--text); margin: 6px 0 0; white-space: pre-wrap; overflow-wrap: anywhere; }
/* The thread focal post's body is the page's single <h1> (SEO/a11y) but must READ
   as the editorial drop-cap body lead — reset the UA heading weight so it doesn't
   render as a big bold title. Font-size/line-height/margin already come from .pc-text
   (which beats the bare h1); .dropcap (cover_art.css) styles ::first-letter as before. */
h1.pc-text { font-weight: 400; }
/* Inline auto-linked URLs in a post body (mobile parity) */
.pc-link { color: var(--accent); text-decoration: none; overflow-wrap: anywhere; }
.pc-link:hover { text-decoration: underline; }
/* Long-post truncation (non-lead cards) — clamp the body to ~8 lines; the
   showmore controller un-hides the toggle only when the text actually overflows,
   and toggles .pc-clamp--open to reveal the full body. Full text stays in the DOM
   (SEO-safe); JS-off leaves it clamped (the whole card still opens the thread). */
.pc-clamp__text { display: -webkit-box; -webkit-line-clamp: 8; -webkit-box-orient: vertical; overflow: hidden; }
.pc-clamp--open .pc-clamp__text { display: block; -webkit-line-clamp: none; overflow: visible; }
.pc-clamp__toggle {
  margin-top: 3px; padding: 0; border: none; background: none; cursor: pointer;
  font-family: var(--font-body); font-size: var(--fs-caption); font-weight: 600; color: var(--accent);
}
.pc-clamp__toggle:hover { text-decoration: underline; }
.pc-topic-row { display: flex; align-items: center; gap: 10px; margin-top: 10px; flex-wrap: wrap; }
.pc-topic { display: inline-flex; font-size: var(--fs-caption); font-weight: 600; color: var(--topic-text); background: var(--topic-tint); border-radius: var(--r-pill); padding: 4px 12px; text-decoration: none; }
/* Secondary ("also relevant") topic pill — the same pill, dimmed, + a muted note. */
.pc-topic--secondary { opacity: 0.72; }
.pc-topic-note { font-size: var(--fs-caption); font-weight: 600; color: var(--text-muted); margin-left: -4px; }
.pc-photo { margin-top: 12px; border-radius: var(--r-lg); height: 184px; overflow: hidden; position: relative; background: var(--cover); }
.pc-photo img, .pc-photo svg { width: 100%; height: 100%; display: block; object-fit: cover; }
/* Click-to-zoom trigger — a block anchor over the thumbnail. JS-off falls back to
   opening the full image in a new tab; JS opens the focus-trapped lightbox. */
.pc-photo__zoom { display: block; width: 100%; height: 100%; cursor: zoom-in; }
/* Full-screen photo lightbox — a native <dialog> (focus-trap + Esc for free). */
.pc-lightbox { border: none; padding: 0; max-width: 100vw; max-height: 100vh; width: 100%; height: 100%; background: transparent; overflow: hidden; }
.pc-lightbox::backdrop { background: rgba(8, 8, 12, 0.92); }
.pc-lightbox[open] { display: flex; align-items: center; justify-content: center; }
.pc-lightbox__img { max-width: 94vw; max-height: 90vh; width: auto; height: auto; object-fit: contain; border-radius: var(--r-md); }
.pc-lightbox__close {
  position: fixed; top: 18px; right: 18px; z-index: 1; width: 44px; height: 44px;
  display: inline-flex; align-items: center; justify-content: center; cursor: pointer;
  border: 1px solid rgba(255, 255, 255, 0.28); border-radius: var(--r-pill);
  background: rgba(255, 255, 255, 0.14); color: #fff;
}
.pc-lightbox__close:hover { background: rgba(255, 255, 255, 0.24); }
.pc-photo .cap {
  position: absolute; left: 14px; bottom: 12px; font-family: var(--font-body); font-size: 12px; font-weight: 600;
  letter-spacing: .02em; color: var(--cover-text); text-shadow: 0 1px 6px rgba(0, 0, 0, .6);
  display: flex; align-items: center; gap: 6px;
}
.pc-unavailable { color: var(--text-muted); font-style: italic; font-size: 16.5px; margin: 6px 0 0; }

/* ── Quoted embed (a quote post's embedded original — feed / thread / profile) ── */
.pc-quote { margin-top: 12px; }
.pc-quote__card {
  display: block; border: 1px solid var(--border); border-radius: var(--r-lg);
  padding: 12px 14px; text-decoration: none; color: var(--text); background: var(--surface-elevated);
}
.pc-quote__card:hover { border-color: var(--accent); }
.pc-quote__author { display: block; font-weight: 700; font-size: var(--fs-body-sm); color: var(--text); }
.pc-quote__body {
  display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden;
  margin-top: 3px; font-size: 15.5px; line-height: 1.45; color: var(--text-muted); white-space: pre-wrap;
}
.pc-quote__unavailable { color: var(--text-muted); font-style: italic; font-size: 15.5px; margin-top: 12px; }

/* ── Reaction bar (FE-313, L1 display-only) ──────────────────────────────── */
.pc-actions { display: flex; gap: 6px; margin-top: 10px; flex-wrap: wrap; margin-left: -6px; }
.rx-btn {
  display: inline-flex; align-items: center; gap: 6px; font-family: var(--font-body); font-size: 13.5px;
  font-weight: 600; color: var(--text-muted); background: transparent; border: none; border-radius: var(--r-pill);
  padding: 6px 11px; cursor: default;
}
.rx-btn svg { stroke: currentColor; fill: none; }
.rx-btn.liked { color: var(--heart-red); }
.rx-btn.liked svg { fill: var(--heart-red); stroke: var(--heart-red); }
.rx-btn.saved { color: var(--violet-600); }
.rx-btn.saved svg { fill: var(--violet-600); stroke: var(--violet-600); }

/* ── React gate (FE-314, logged-out signup CTA) ──────────────────────────── */
.react-gate {
  border: 1px solid var(--border); background: var(--cover); color: var(--cover-text);
  border-radius: var(--r-lg); padding: 16px 18px; margin: 14px 0; max-width: 320px;
}
.react-gate .gp-t { font-family: var(--font-display); font-weight: 600; font-size: var(--fs-subtitle); margin-bottom: 3px; }
.react-gate .gp-s { font-size: var(--fs-body-sm); color: rgba(245, 245, 245, .78); line-height: 1.4; margin-bottom: 12px; }
.react-gate .btn-primary { width: 100%; }

/* ── Link preview card (FE-309) — renders click_url ONLY, never requested_url ─ */
.lp-card {
  display: flex; margin-top: 12px; border: 1px solid var(--border); border-radius: var(--r-lg);
  overflow: hidden; text-decoration: none; color: var(--text); background: var(--surface-elevated);
}
.lp-card__media { flex: 0 0 auto; width: 128px; min-height: 84px; position: relative; background: var(--surface-muted); }
.lp-card__media img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* Branded fallback tile — a magazine-cover-style tinted panel carrying the
   domain monogram (or a neutral link glyph), shown when the publisher thumbnail
   is missing OR fails to load. Reuses the M3 container/on-container token pair so
   contrast is designed-in for both themes. Absolutely fills the media box so it
   overlays a broken <img> once the controller reveals it. No motion (reduced-
   motion safe by construction). */
.lp-card__fallback {
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center;
  background: var(--accent-subtle); color: var(--on-primary-container);
}
/* The `hidden` attribute must win over the author display rules above/on the img
   (a bare [hidden] would otherwise be overridden). Toggled by the controller. */
.lp-card__img[hidden], .lp-card__fallback[hidden] { display: none; }
.lp-card__monogram { font-family: var(--font-display); font-weight: 600; font-size: var(--fs-heading); line-height: 1; text-transform: uppercase; }
.lp-card__glyph { opacity: .75; }
.lp-card__body { padding: 12px 14px; min-width: 0; }
.lp-card__title { font-family: var(--font-body); font-weight: 700; font-size: var(--fs-body-sm); color: var(--text); line-height: 1.3; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.lp-card__desc { font-size: 13.5px; color: var(--text-muted); margin-top: 3px; line-height: 1.4; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.lp-card__domain { font-size: 12px; color: var(--text-muted); margin-top: 6px; text-transform: lowercase; }
.lp-card:hover { border-color: var(--accent); }

/* ── Cover masthead (topic/section pages consume these later; used by rail) ── */
.cover-mast {
  color: var(--cover-text); border-radius: var(--r-xl); padding: 36px 32px; margin-bottom: 22px;
  position: relative; overflow: hidden;
}
.cover-mast .ce { font-family: var(--font-body); font-size: 12.5px; letter-spacing: .2em; text-transform: uppercase; color: var(--primary-tint); font-weight: 700; }
.cover-mast .ct { font-family: var(--font-display); font-weight: 600; font-size: 42px; line-height: 1.05; margin: 10px 0 8px; letter-spacing: -.01em; }
.cover-mast .cd { font-size: 15.5px; color: rgba(245, 245, 245, .78); max-width: 440px; line-height: 1.5; }
.cover-mast .cm { margin-top: 16px; font-size: 13.5px; color: rgba(245, 245, 245, .62); }

/* ── Index-page heading (Newsstand / Topics / Sections / Explore / Search hubs) ──
   A proper display H1 — the discovery hubs' page title. Sits ABOVE the smaller
   .rail-label section subheads so the hierarchy reads (an index H1 must not share
   the tiny eyebrow scale its own subheads use). */
.page-heading {
  font-family: var(--font-display); font-weight: 600; font-size: var(--fs-heading);
  line-height: 1.1; letter-spacing: -.01em; color: var(--text); margin: 4px 0 18px;
}

/* ── Sections index — a responsive cover-mini grid (BE-113) ───────────────────
   The masthead lists every section as an editorial cover-mini card. auto-fill /
   minmax gives 1 column on a phone and 2–3 across the boxed desktop column
   (never a single stack pinned far-left with an empty right half). */
.section-index {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(232px, 1fr));
  gap: 14px; margin-top: 4px;
}

/* ── Sibling-topic chip row (BE-112 reflow) ───────────────────────────────────
   The "More in {Section}" siblings live in the desktop Newsstand aside (≥1100px).
   Below that the aside is hidden, so this inline horizontal chip row carries the
   same links — and is itself hidden exactly where the aside returns, so the two
   never render at once. */
.sibling-chips { margin: 26px 0 4px; }
.sibling-chips__label {
  display: block; font-family: var(--font-body); font-size: 12.5px; font-weight: 700;
  letter-spacing: .14em; text-transform: uppercase; color: var(--text-muted); margin-bottom: 10px;
}
.sibling-chips__row {
  display: flex; gap: 8px; overflow-x: auto; padding-bottom: 4px;
  scrollbar-width: none; -webkit-overflow-scrolling: touch;
}
.sibling-chips__row::-webkit-scrollbar { display: none; }
.sibling-chip {
  flex: 0 0 auto; font-family: var(--font-body); font-weight: 600; font-size: var(--fs-body-sm);
  color: var(--text); background: var(--surface-elevated); border: 1px solid var(--border);
  border-radius: var(--r-pill); padding: 7px 14px; text-decoration: none; white-space: nowrap;
}
.sibling-chip:hover { border-color: var(--accent); color: var(--accent); }
@media (min-width: 1100px) { .sibling-chips { display: none; } }

/* ── Profile header (FE-320/FE-321) — /@username identity block ─────────────── */
.profile-header { padding: 4px 0 18px; border-bottom: 1px solid var(--border); margin-bottom: 4px; }
.profile-header__top { display: flex; align-items: center; gap: 16px; }
.profile-header__id { min-width: 0; flex: 1 1 auto; }
.profile-header__name {
  font-family: var(--font-display); font-weight: 600; font-size: 26px; line-height: 1.1;
  letter-spacing: -.01em; color: var(--text); display: flex; align-items: center; gap: 7px;
}
.profile-header__handle { font-size: var(--fs-body-sm); color: var(--text-muted); margin-top: 3px; }
.profile-header__action { flex: 0 0 auto; }
.profile-header__bio { margin-top: 14px; font-size: 15.5px; line-height: 1.5; color: var(--text); max-width: 48ch; }
.profile-header__counts { display: flex; gap: 26px; margin-top: 16px; }
.profile-header__counts .pf-count { display: flex; align-items: baseline; gap: 6px; }
.pf-count__value { font-family: var(--font-body); font-weight: 700; font-size: 16px; color: var(--text); }
.pf-count__label { font-size: var(--fs-body-sm); color: var(--text-muted); }

/* ── Profile tabs (posts / replies) ────────────────────────────────────────── */
.profile-tabs { display: flex; gap: 4px; border-bottom: 1px solid var(--border); margin: 6px 0 2px; }
.profile-tabs__tab {
  font-family: var(--font-body); font-weight: 600; font-size: var(--fs-body-sm); color: var(--text-muted);
  text-decoration: none; padding: 12px 16px; border-bottom: 2px solid transparent; margin-bottom: -1px;
}
.profile-tabs__tab:hover { color: var(--text); }
.profile-tabs__tab.is-active { color: var(--violet-600); border-bottom-color: var(--violet-500); }

/* ── Share Your Ten (FE-322) — the owner's curated lists ───────────────────── */
.share-ten { margin: 22px 0; }
.share-ten__heading {
  font-family: var(--font-display); font-weight: 600; font-size: var(--fs-subtitle);
  color: var(--text); margin-bottom: 14px;
}
.share-ten__list {
  background: var(--surface-elevated); border: 1px solid var(--border); border-radius: var(--r-lg);
  padding: 16px 18px; margin-bottom: 12px;
}
.share-ten__category {
  font-family: var(--font-body); font-size: 12px; font-weight: 700; letter-spacing: .08em;
  text-transform: uppercase; color: var(--violet-600); margin-bottom: 10px;
}
.share-ten__items { list-style: none; counter-reset: syt; margin: 0; padding: 0; }
.share-ten__item { counter-increment: syt; display: flex; gap: 10px; padding: 7px 0; }
.share-ten__item::before { content: counter(syt); flex: 0 0 auto; width: 20px; font-weight: 700; color: var(--text-muted); font-size: 13px; }
.share-ten__text { font-size: 15px; color: var(--text); font-weight: 600; }
.share-ten__note { display: block; font-size: 13.5px; color: var(--text-muted); margin-top: 2px; line-height: 1.4; }

/* ── Share Your Ten — owner entry + editor (reuses the .settings-* form theme) ─ */
.syt-owner-actions { margin: 6px 0 18px; }
.syt-owner-actions__edit {
  display: inline-flex; align-items: center; gap: 6px;
  font-family: var(--font-body); font-weight: 700; font-size: var(--fs-body-sm);
  color: var(--violet-600); text-decoration: none;
  border: 1px solid var(--border); border-radius: var(--r-md); padding: 8px 14px;
}
.syt-owner-actions__edit:hover { border-color: var(--violet-600); }
.syt-edit__rows { list-style: none; counter-reset: syt-edit; margin: 0 0 14px; padding: 0; }
.syt-edit__row {
  counter-increment: syt-edit; display: grid; grid-template-columns: 1fr 1fr; gap: 12px;
  padding: 10px 0; border-bottom: 1px solid var(--border);
}
.syt-edit__row:last-child { border-bottom: none; }
.syt-edit__optional { color: var(--text-muted); font-weight: 400; }
@media (max-width: 560px) { .syt-edit__row { grid-template-columns: 1fr; gap: 7px; } }

/* ── Share-Your-Ten accordion (one category open at a time) ───────────────────
   Each category is a native <details>; the <summary> is the clickable heading.
   JS-off toggles natively; name="syt-cat" makes it exclusive in modern browsers. */
.syt-accordion { gap: 0; }
.syt-cat { padding: 0; }
.syt-cat > :not(summary) { padding-top: 14px; }
.syt-cat__summary {
  list-style: none; cursor: pointer; user-select: none;
  display: flex; align-items: center; gap: 10px;
  padding: 16px 4px; margin: 0;
  font-family: var(--font-display); font-weight: 600; font-size: var(--fs-subtitle); color: var(--text);
}
.syt-cat__summary::-webkit-details-marker { display: none; }
.syt-cat__summary::marker { content: ""; }
.syt-cat__summary::after {
  content: ""; flex: 0 0 auto; margin-left: auto;
  width: 9px; height: 9px; border-right: 2px solid var(--text-muted); border-bottom: 2px solid var(--text-muted);
  transform: rotate(45deg); transition: transform 160ms ease;
}
.syt-cat[open] > .syt-cat__summary::after { transform: rotate(-135deg); }
.syt-cat__summary:hover { color: var(--accent); }
.syt-cat__summary:hover::after { border-color: var(--accent); }
@media (prefers-reduced-motion: reduce) { .syt-cat__summary::after { transition: none; } }

/* ── Share-Your-Ten inline save confirmation + validation error (Turbo Stream) ──
   Both live regions sit empty on load next to a category's Save button; a save
   swaps their contents in place (no reload). The "Saved ✓" chip reuses the app's
   violet notice palette and auto-dismisses (syt-saved controller). */
.syt-saved { margin: 0; }
.syt-saved__msg {
  display: inline-flex; align-items: center; gap: 6px; padding: 5px 11px; border-radius: var(--r-pill);
  background: var(--primary-container); color: var(--on-primary-container);
  font: 700 var(--fs-body-sm) / 1 var(--font-body);
}
.syt-saved__check { flex: 0 0 auto; }
.syt-error { margin: 8px 0 0; }
.syt-error__msg {
  margin: 0; padding: 10px 12px; border-radius: var(--r-md);
  background: rgba(255, 48, 64, .1); color: var(--heart-red);
  font-size: var(--fs-body-sm); line-height: 1.4;
}
@media (prefers-reduced-motion: no-preference) {
  .syt-saved__msg { animation: syt-saved-in .18s ease both; }
  .syt-saved__msg.is-dismissing { animation: syt-saved-out .28s ease both; }
}
@keyframes syt-saved-in { from { opacity: 0; transform: translateY(2px); } to { opacity: 1; transform: none; } }
@keyframes syt-saved-out { from { opacity: 1; } to { opacity: 0; } }
/* ── Link tabs (BE-112, topic/section Top/New sort) — ported from the v3 mockup class name ── */
.link-tabs { display: flex; gap: 22px; border-bottom: 1px solid var(--border); margin: 22px 0 4px; }
.link-tabs a {
  font-family: var(--font-body); font-weight: 700; font-size: var(--fs-body-sm); color: var(--text-muted);
  text-decoration: none; padding: 0 2px 12px; border-bottom: 3px solid transparent;
}
/* Active tab: violet ink + a thicker violet underline (the 2px underline read as
   too subtle). The base reserves 3px transparent so toggling never shifts baseline. */
.link-tabs a.on { color: var(--accent); border-bottom-color: var(--accent); }
.link-tabs a:hover { color: var(--text); }
/* Per-type result count on a search tab (Posts 0 · People 1 · Topics 0) — subtle,
   so a glance shows which tab actually holds matches without switching. */
.link-tabs__count { font-weight: 700; font-size: 12px; color: var(--text-muted); margin-left: 1px; }
.link-tabs a.on .link-tabs__count { color: var(--accent); }

/* ── Blended "Top" search view — the non-empty People / Posts / Topics sections,
   each with a labelled header + a "See all N →" link to its full type tab. The
   rows reuse .explore-shelf / .person-row / .pick-row / .post-card above; only
   the section header is new here. ── */
.top-section { margin: 4px 0 22px; }
.top-section__head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 12px; margin: 18px 0 6px;
}
.top-section__label {
  font-family: var(--font-display); font-weight: 600; font-size: var(--fs-subtitle);
  color: var(--text); margin: 0; line-height: 1.2;
}
.top-section__seeall {
  flex: 0 0 auto; font: 700 var(--fs-body-sm) / 1 var(--font-body);
  color: var(--accent); text-decoration: none; white-space: nowrap;
}
.top-section__seeall:hover { text-decoration: underline; }

/* ── Search zero-state: recent searches (client-local, JS-rendered) + trending
   chips. Both live inside the #search_results frame's zero-state; JS-off the
   recents section stays empty+hidden and the chips + shelves carry the state. ── */
.search-recents { margin: 14px 0 4px; }
.search-recents[hidden] { display: none; }
.search-recents__head {
  display: flex; align-items: baseline; justify-content: space-between; margin-bottom: 4px;
}
.search-recents__title {
  font-family: var(--font-body); font-weight: 700; font-size: var(--fs-eyebrow);
  letter-spacing: .06em; text-transform: uppercase; color: var(--text-muted); margin: 0;
}
.search-recents__clear {
  border: none; background: none; cursor: pointer; padding: 4px 2px;
  font: 700 var(--fs-caption) / 1 var(--font-body); color: var(--accent);
}
.search-recents__clear:hover { text-decoration: underline; }
.search-recents__list { list-style: none; margin: 0; padding: 0; }
.search-recents__row {
  display: flex; align-items: center; gap: 8px;
  border-bottom: 1px solid var(--border);
}
.search-recents__row:last-child { border-bottom: none; }
.search-recents__link {
  flex: 1 1 auto; min-width: 0; padding: 11px 2px; color: var(--text);
  text-decoration: none; font-size: var(--fs-body-sm); font-weight: 600;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.search-recents__link:hover { color: var(--accent); }
.search-recents__x {
  flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center;
  width: 26px; height: 26px; border: none; border-radius: 50%; cursor: pointer;
  background: none; color: var(--text-muted); font-size: 12px; line-height: 1;
}
.search-recents__x:hover { background: var(--surface-3); color: var(--text); }

.search-trending {
  display: flex; flex-wrap: wrap; gap: 8px; margin: 12px 0 4px;
}
.topic-chip {
  display: inline-flex; align-items: center; text-decoration: none;
  padding: 7px 14px; border-radius: var(--r-pill);
  border: 1.5px solid var(--border); background: var(--surface-1);
  color: var(--text); font: 700 var(--fs-caption) / 1 var(--font-body);
}
.topic-chip:hover { border-color: var(--accent); color: var(--accent); background: var(--accent-subtle); }

/* ── L2 logged-in feed (BE-120) — the header + empty state; the tabs reuse
   .link-tabs and the rows reuse .post-card / .feed-list above. ── */
.feed-head { margin: 4px 0 2px; }
.feed-head__title {
  font-family: var(--font-display); font-weight: 600; font-size: var(--fs-title);
  color: var(--text); margin: 0; line-height: 1.15;
}
.feed-empty {
  color: var(--text-muted); font-size: var(--fs-body-lg); line-height: 1.5;
  padding: 40px 0; text-align: center;
}

/* Search zero-state dead-end (no curated picks) — a centred "Browse the Newsstand"
   CTA under calm copy, instead of two dead sentences + a gap. */
.search-zero-cta { text-align: center; padding: 8px 0 40px; }
.search-zero-cta .feed-empty { padding: 12px 0 18px; }

/* ── Saved (collections magazine-cover grid) — the tabs reuse .link-tabs, the
   header/empty state reuse .feed-head/.feed-empty above. ── */
.collection-back { margin: 0 0 6px; }
.collection-back a {
  color: var(--text-muted); text-decoration: none; font-weight: 700;
  font-size: var(--fs-body-sm);
}
.collection-back a:hover { color: var(--text); }
.collections-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: var(--space-lg); margin: 22px 0 4px;
}
.collection-card { display: block; text-decoration: none; color: inherit; }
.collection-card__cover {
  position: relative; aspect-ratio: 3 / 4; border-radius: var(--r-md); overflow: hidden;
  display: flex; align-items: flex-end; padding: 12px;
  background-color: var(--cover);
  background-image:
    linear-gradient(160deg, rgba(10,10,12,.20) 0%, rgba(10,10,12,.62) 100%),
    var(--grain),
    radial-gradient(130% 120% at 78% 14%, #2a2260 0%, #14122e 46%, #0a0a0c 84%);
  background-size: cover, 150px 150px, cover;
  background-repeat: no-repeat, repeat, no-repeat;
  background-position: center;
}
.collection-card__photo { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.collection-card__cover-text {
  position: relative; z-index: 1; color: #F3F1FA; font-family: var(--font-body);
  font-size: 12.5px; line-height: 1.35;
}
.collection-card__name {
  margin: 10px 0 1px; font-family: var(--font-body); font-weight: 700;
  font-size: var(--fs-body-sm); color: var(--text);
}
.collection-card__count { margin: 0; font-size: var(--fs-caption); color: var(--text-muted); }

/* ── Collections WRITE surface (create / add-to / rename / delete) — the write
   sibling of the read-only grid above. Reuses the .feed-head / .collection-back /
   .collections-grid / .btn-* atoms; these rules add only the create-cell, the
   forms, the collection-detail header row, and the add-to-collection sheet. ── */

/* The "New collection" grid cell — a dashed placeholder cover with a + glyph, the
   same 3:4 aspect + radius as a real cover so the grid stays even. */
.collection-card--new .collection-card__count { color: var(--text-muted); }
.collection-card__cover--new {
  background: none; border: 1.5px dashed var(--border);
  align-items: center; justify-content: center; color: var(--text-muted);
  transition: border-color .14s, color .14s;
}
.collection-card--new:hover .collection-card__cover--new { border-color: var(--accent); color: var(--accent); }

/* Empty-state CTA (no collections yet) — centres the create button under the copy. */
.collections-cta { display: flex; justify-content: center; margin: 4px 0 8px; }

/* Create / rename form — a single named field + the "always private" note, laid
   out inside the shared .compose-card. */
.collection-form__field { display: flex; flex-direction: column; gap: 8px; }
.collection-form__label {
  font-family: var(--font-body); font-weight: 700; font-size: var(--fs-body-sm); color: var(--text);
}
.collection-form__input { width: 100%; }
.collection-form__note {
  display: flex; align-items: center; gap: 7px; margin: 2px 0 0;
  font-size: var(--fs-caption); color: var(--text-muted); line-height: 1.4;
}

/* Report reason picker (report a post / a message) — a full-width, tappable
   vertical radio list inside the shared card treatment. Replaces the raw
   browser <fieldset> chrome + cramped inline radios. */
.report-reasons {
  margin: 16px 0 0; padding: 6px; border: 1px solid var(--outline-variant);
  border-radius: var(--r-lg); background: var(--surface-hi);
  display: flex; flex-direction: column; gap: 2px; min-inline-size: 0;
}
.report-reasons__legend {
  float: left; width: 100%; padding: 8px 10px 6px;
  font-family: var(--font-body); font-weight: 700; font-size: var(--fs-body-sm); color: var(--text);
}
.report-reason {
  display: flex; align-items: center; gap: 11px; width: 100%;
  padding: 13px 12px; border-radius: var(--r-md); cursor: pointer;
  transition: background-color .14s ease;
}
.report-reason:hover { background: var(--surface-muted); }
.report-reason:has(.report-reason__input:checked) { background: var(--surface-muted); }
.report-reason__input { flex: 0 0 auto; margin: 0; }
.report-reason__label {
  font-family: var(--font-body); font-size: var(--fs-body); color: var(--text); line-height: 1.35;
}
.report-reason:has(.report-reason__input:checked) .report-reason__label { font-weight: 650; }
.report-reason:has(.report-reason__input:focus-visible) {
  outline: 2px solid var(--accent); outline-offset: 1px;
}
.collection-form__note svg { flex: 0 0 auto; }
.collection-form__actions { display: flex; justify-content: flex-end; gap: var(--space-sm); }

/* Collection-detail header — the title block + the owner ⋯ menu (rename/delete)
   on a row. .collection-menu drops .pc-menu's margin-left:auto so the trigger
   sits at the header's right edge without stretching the title. */
.feed-head--collection {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: var(--space-md); margin-bottom: 18px; /* breathing room before the first post's ⋯ */
}
.feed-head__lead { min-width: 0; }
/* Item-count subtitle under the title — grounds the header + separates the two ⋯. */
.feed-head__count { margin: 4px 0 0; font-size: var(--fs-body-sm); color: var(--text-muted); }
.collection-menu { margin-left: 0; flex: 0 0 auto; margin-top: 4px; }

/* Add-to-collection sheet — a list of toggle rows (one per collection) + the
   read-only "All saves" row + a "New collection" affordance. Each row is a
   full-width button_to form; the whole row is the tap target. */
.collect-sheet__excerpt {
  margin: 14px 0 6px; padding: 12px 14px; border-radius: var(--r-md);
  background: var(--surface-hi); border: 1px solid var(--outline-variant);
  font-family: var(--font-body); font-size: var(--fs-body-sm); font-style: italic;
  color: var(--text-muted); line-height: 1.45;
}
.collect-sheet { list-style: none; margin: 12px 0 0; padding: 0; display: flex; flex-direction: column; }
.collect-row { border-bottom: 1px solid var(--border); }
.collect-row:last-child { border-bottom: 0; }
.collect-row__form { margin: 0; }
.collect-row__toggle, .collect-row--static {
  display: flex; align-items: center; justify-content: space-between; gap: var(--space-md);
  width: 100%; padding: 14px 4px; text-align: left;
  background: transparent; border: 0; cursor: pointer;
}
.collect-row--static { cursor: default; }
.collect-row__toggle:hover { background: var(--accent-subtle); }
.collect-row__name {
  font-family: var(--font-body); font-weight: 600; font-size: var(--fs-body); color: var(--text);
}
.collect-row__state {
  flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center;
  width: 26px; height: 26px; border-radius: 50%; color: var(--text-muted);
}
.collect-row__state--on { color: var(--accent); }
.collect-sheet__new { margin: 18px 0 4px; display: flex; }

/* ── Cookie-consent banner (LEG-903) — fixed to the viewport bottom, on every
   Web:: page (this file loads unconditionally from the layout, unlike pages.css
   which only some views link). Dismisses itself: once a choice is recorded the
   component just doesn't render on the next page. ── */
.consent-banner {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 60;
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-lg);
  justify-content: space-between;
  padding: var(--space-lg) var(--space-xl);
  background: var(--surface-elevated); border-top: 1px solid var(--outline-variant);
  box-shadow: 0 -12px 30px -20px rgba(10, 10, 12, .35);
}
.consent-banner__copy {
  margin: 0; flex: 1 1 320px; max-width: 60ch;
  font: 400 var(--fs-body-sm) / 1.5 var(--font-body); color: var(--ink-2);
}
.consent-banner__copy a { color: var(--primary); }
.consent-banner__actions { display: flex; gap: var(--space-sm); flex: 0 0 auto; margin: 0; }

/* Reserve room at the page bottom so a viewport-fixed banner (verify-email /
   consent) never overlaps the last content row or the footer. The layout adds
   .has-fixed-banner to <body> only when such a banner will actually render, so
   pages without one keep their normal spacing. Mobile reserves more (the banner
   wraps its copy + actions onto multiple rows). */
body.has-fixed-banner { padding-bottom: 152px; }
@media (min-width: 560px) { body.has-fixed-banner { padding-bottom: 92px; } }

/* ── "Verify your email" soft-gate banner — same viewport-bottom treatment as the
   consent banner, shown only to a logged-in visitor whose email is unverified.
   Dismissible for the session; NEVER blocks browsing. ── */
.verify-banner {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 60;
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-lg);
  justify-content: space-between;
  padding: var(--space-lg) var(--space-xl);
  background: var(--surface-elevated); border-top: 1px solid var(--outline-variant);
  box-shadow: 0 -12px 30px -20px rgba(10, 10, 12, .35);
}
.verify-banner__copy {
  margin: 0; flex: 1 1 320px; max-width: 60ch;
  font: 400 var(--fs-body-sm) / 1.5 var(--font-body); color: var(--ink-2);
}
.verify-banner__actions { display: flex; align-items: center; gap: var(--space-sm); flex: 0 0 auto; margin: 0; }
.verify-banner__resend { display: contents; }
/* ── L2 (logged-in) actionable reaction bar (BE-121) ─────────────────────────
   button_to wraps each control in a <form>; display:contents lets the button be
   the direct flex child so the row lays out exactly like the display-only L1 bar
   (whose inert spans above are unchanged). */
.pc-actions form.rx-form { display: contents; }
button.rx-btn { cursor: pointer; }
a.rx-btn { cursor: pointer; text-decoration: none; }
button.rx-btn:hover, a.rx-btn:hover { color: var(--text); }
.rx-btn.reposted { color: var(--violet-600); }
.rx-btn.reposted svg { fill: none; stroke: var(--violet-600); }

/* ── Reaction picker (endorsements, L2 interactive) ──────────────────────────
   A native <details> popover so the picker works with JS OFF (the summary toggles
   it; each kind is its own button_to form). The trigger sits inline as an rx-btn;
   the panel floats above the row. The viewer's current reaction is highlighted and
   its button submits DELETE (tap-to-remove); every other kind submits a PUT swap. */
.rx-reactions { position: relative; display: inline-flex; }
.rx-reactions__trigger { cursor: pointer; list-style: none; }
.rx-reactions__trigger::-webkit-details-marker { display: none; }
.rx-reactions__trigger:hover, .rx-reactions[open] .rx-reactions__trigger { color: var(--text); background: var(--accent-subtle); }
.rx-reactions__trigger.reacted { color: var(--violet-600); }
.rx-reactions__trigger.reacted svg { stroke: var(--violet-600); }
.rx-reactions__panel {
  position: absolute; left: 0; bottom: calc(100% + 6px); z-index: 20; display: flex; gap: 2px;
  padding: 6px; background: var(--surface-elevated); border: 1px solid var(--border);
  border-radius: var(--r-pill); box-shadow: 0 6px 20px -8px rgba(20, 15, 10, .12);
}
.rx-reactions__form { display: contents; }
.rx-reaction {
  display: inline-flex; flex-direction: column; align-items: center; gap: 3px; cursor: pointer;
  font-family: var(--font-body); font-size: 11.5px; font-weight: 600; color: var(--text-muted);
  background: transparent; border: 0; border-radius: var(--r-sm); padding: 6px 9px;
}
.rx-reaction svg { stroke: currentColor; fill: none; }
.rx-reaction:hover { color: var(--text); background: var(--accent-subtle); }
.rx-reaction--active { color: var(--violet-600); background: var(--accent-subtle); }
.rx-reaction--active svg { stroke: var(--violet-600); }

/* ── Compose surface (BE-121, redesigned) ─────────────────────────────────────
   The composer lives in ONE cohesive, elevated card (not bare fields on the
   canvas): a threaded surface (own avatar beside the input; a subtle connector
   from a reply's parent), an auto-growing borderless textarea, progressive media
   disclosure, and a live character-counter ring. Colors resolve against the v3
   tokens — no one-off hex. The `compose` Stimulus controller adds the shine on
   top of a form that works fully JS-off. */
.compose-error {
  padding: 12px 14px; margin: 16px 0; border-radius: var(--r-md);
  background: rgba(255, 48, 64, .1); color: var(--heart-red);
  font-size: var(--fs-body-sm); line-height: 1.4;
}

/* The elevated composer card. focus-within lifts it with a soft brand glow —
   the "refined glow, not a hard box" that replaces per-field rings inside. */
.compose-card {
  margin-top: 16px;
  background: var(--surface-hi);
  border: 1px solid var(--outline-variant);
  border-radius: var(--r-lg);
  padding: var(--space-lg);
  box-shadow: 0 1px 2px rgba(10, 10, 12, .04), 0 12px 30px -18px rgba(10, 10, 12, .16);
  transition: border-color .18s ease, box-shadow .18s ease;
}
.compose-card:focus-within {
  border-color: var(--primary-tint);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 20%, transparent),
              0 14px 34px -16px rgba(82, 45, 230, .22);
}
.theme-dark .compose-card {
  box-shadow: 0 1px 2px rgba(0, 0, 0, .4), 0 14px 34px -18px rgba(0, 0, 0, .55);
}

/* Give the composer a touch more room than the standard reading column so a
   500-char post has a comfortable, full-width writing space. Scoped to the
   compose surface via :has() — every other surface keeps the --measure-wide
   column, and a browser without :has() gracefully falls back to it too. */
.web-shell__main:has(.compose-card) { max-width: 688px; }

.compose-form { display: flex; flex-direction: column; gap: 16px; }

/* Threaded composer — parent context + connector + own avatar + field. */
.compose-thread { position: relative; }
.compose-thread__row { display: flex; gap: 14px; }
.compose-thread__gutter { flex: 0 0 auto; }
.compose-thread__field { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 10px; }

/* Parent/quoted context — compact + muted, reusing the shared post card. The
   muting is applied to the card itself so the connector rail stays crisp. */
.compose-thread__context { position: relative; margin-bottom: 2px; }
.compose-thread__context .post-card { opacity: .68; padding-bottom: 12px; }
.compose-thread__context .pc-text { font-size: var(--fs-body-sm); }

/* Reply connector — a subtle rail down the parent's empty left gutter (below its
   44px avatar) into the composer's own avatar. Anchored to the avatars + block
   edges, so it holds at any parent-body length (Threads-style). */
.compose-thread--reply .compose-thread__row { position: relative; padding-top: 18px; }
.compose-thread--reply .compose-thread__context::after {
  content: ""; position: absolute; left: 21px; top: 50px; bottom: -2px; width: 2px;
  background: var(--outline-variant); border-radius: var(--r-pill);
}
.compose-thread--reply .compose-thread__gutter::before {
  content: ""; position: absolute; left: 21px; top: 0; height: 18px; width: 2px;
  background: var(--outline-variant); border-radius: var(--r-pill);
}

/* Auto-growing, borderless textarea — blends into the card; the card owns the
   focus glow, so no hard per-field ring. Native field-sizing where supported,
   with the JS reset-first scrollHeight fallback (.compose-input--auto) otherwise.
   A generous ~6-line default height (176px) so a 500-char post opens onto a real
   writing space, not a cramped one-line slot — still auto-growing to the cap. */
.compose-input {
  width: 100%; box-sizing: border-box; resize: vertical; min-height: 176px;
  border: none; background: transparent; padding: 8px 2px; margin: 0;
  font: 400 var(--fs-body-lg) / 1.55 var(--font-body); color: var(--text);
  overflow-wrap: anywhere;
}
.compose-input:focus-visible { outline: none; box-shadow: none; }
.compose-input--auto { resize: none; overflow: hidden; }
@supports (field-sizing: content) {
  .compose-input { field-sizing: content; min-height: 176px; max-height: 60vh; resize: none; overflow-y: auto; }
}

/* Media block — the compact uploader + the alt field (revealed on attach). */
.compose-media { display: flex; flex-direction: column; gap: 10px; }

/* The "Describe the photo" alt field is shown by default (JS-off fallback); the
   controller hides it (.is-composing) until a photo is attached (.is-shown). */
.compose-alt { display: block; }
.compose-form.is-composing .compose-alt { display: none; }
.compose-form.is-composing .compose-alt.is-shown { display: block; }
.compose-alt__input { width: 100%; }

/* who-can-quote — a labelled inline SEGMENTED control (radio pills) that shows
   all three options at a glance: no dropdown, one tap to choose. Native radios
   keep it fully accessible — arrow-key nav + labels for free — while submitting
   the SAME `quotes_setting` param the old <select> did. */
.compose-quotes { display: flex; flex-direction: column; gap: 8px; }
.compose-quotes__label {
  display: inline-flex; align-items: center; gap: 6px;
  font: 600 var(--fs-body-sm) / 1.3 var(--font-body); color: var(--text-muted);
}
.compose-quotes__icon { flex: 0 0 auto; color: var(--text-muted); }

/* The pill group — one rounded track hugging its three segments (not stretched
   to the column width). */
.compose-seg {
  display: inline-flex; align-self: flex-start; align-items: stretch;
  flex-wrap: wrap; gap: 3px; padding: 3px; max-width: 100%;
  background: var(--surface-2); border: 1px solid var(--outline-variant);
  border-radius: var(--r-pill);
}
.compose-seg__option { display: inline-flex; margin: 0; cursor: pointer; }

/* The real radio is visually hidden (sr-only clip) yet keeps native semantics,
   focus + arrow-key group nav; the pill is the visible, clickable control. */
.compose-seg__input {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; border: 0;
}
.compose-seg__pill {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  padding: 8px 15px; border-radius: var(--r-pill); white-space: nowrap;
  font: 600 var(--fs-body-sm) / 1 var(--font-body); color: var(--text-muted);
  transition: background .16s ease, color .16s ease;
}
.compose-seg__pill svg { flex: 0 0 auto; display: block; }
.compose-seg__option:hover .compose-seg__pill { color: var(--text); background: var(--surface-3); }
.compose-seg__input:checked + .compose-seg__pill {
  background: var(--accent); color: var(--on-primary);
  box-shadow: 0 1px 2px rgba(10, 10, 12, .12);
}
.compose-seg__input:checked + .compose-seg__pill svg { color: var(--on-primary); }
.compose-seg__input:focus-visible + .compose-seg__pill {
  outline: 2px solid var(--primary); outline-offset: 2px;
}
@media (max-width: 420px) {
  .compose-seg { display: flex; align-self: stretch; }
  .compose-seg__option { flex: 1 1 auto; }
  .compose-seg__pill { width: 100%; padding-left: 10px; padding-right: 10px; }
}

/* A "Replying to @handle" chip — the threadline's context header. */
.compose-replying {
  display: inline-flex; align-items: center; gap: 6px; margin-bottom: 10px;
  padding: 5px 12px; border-radius: var(--r-pill);
  background: var(--primary-container); color: var(--on-primary-container);
  font: 600 var(--fs-caption) / 1 var(--font-body);
}
.compose-replying__handle { color: var(--on-primary-container); text-decoration: none; font-weight: 700; }
.compose-replying__handle:hover { text-decoration: underline; }

/* Restored-draft chip — offered, never forced. */
.compose-draft {
  display: inline-flex; align-items: center; gap: 8px; margin-bottom: 2px;
  padding: 6px 8px 6px 12px; border: 1px solid var(--outline-variant);
  border-radius: var(--r-pill); background: var(--surface-1); color: var(--text-muted);
  font: 600 var(--fs-caption) / 1 var(--font-body);
}
.compose-draft[hidden] { display: none; }
.compose-draft__discard {
  border: none; background: var(--surface-3); color: var(--accent);
  font: 700 var(--fs-caption) / 1 var(--font-body); cursor: pointer;
  padding: 5px 11px; border-radius: var(--r-pill);
}
.compose-draft__discard:hover { background: var(--primary-container); }

/* Action bar — the char-counter ring + a quiet ⌘↵ hint + Cancel + primary. */
.compose-bar {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding-top: 14px; border-top: 1px solid var(--outline-variant);
}
.compose-bar__actions { display: flex; align-items: center; gap: 10px; }
.compose-bar .btn-primary:disabled { opacity: .45; cursor: not-allowed; }
.compose-bar .btn-primary:disabled:hover { background: var(--accent); }
.compose-bar .btn-primary:not(:disabled):active { transform: scale(.97); }

.compose-hint { display: inline-flex; align-items: center; gap: 4px; color: var(--text-muted); font: 500 var(--fs-caption) / 1 var(--font-body); }
.compose-hint kbd {
  font-family: var(--font-body); font-size: 11px; line-height: 1; padding: 3px 5px;
  border-radius: var(--r-sm); background: var(--surface-3); color: var(--text-muted);
  border: 1px solid var(--outline-variant);
}
@media (max-width: 560px) { .compose-hint { display: none; } }

/* The circular character counter — a conic-gradient donut (no SVG math). JS-on
   only; the remaining NUMBER surfaces just for the last stretch (anti-anxiety). */
.compose-counter { display: none; position: relative; align-items: center; justify-content: center; width: 24px; height: 24px; flex: 0 0 auto; }
.compose-form.is-composing .compose-counter { display: inline-flex; }
.compose-counter__ring {
  --pct: 0; --ring: var(--accent);
  width: 22px; height: 22px; border-radius: 50%;
  background: conic-gradient(var(--ring) calc(var(--pct) * 1%), var(--outline-variant) 0);
  -webkit-mask: radial-gradient(circle 6.5px at center, transparent 98%, rgba(0, 0, 0, 1) 100%);
          mask: radial-gradient(circle 6.5px at center, transparent 98%, rgba(0, 0, 0, 1) 100%);
}
.compose-counter__num { position: absolute; font: 600 10.5px / 1 var(--font-body); color: var(--text-muted); }
.compose-counter.is-warn .compose-counter__ring { --ring: var(--tertiary); }
.compose-counter.is-warn .compose-counter__num { color: var(--tertiary); }
.compose-counter.is-over .compose-counter__ring { --ring: var(--like); }
.compose-counter.is-over .compose-counter__num { color: var(--like); }

/* ── Topic #autocomplete (JS-on) — a combobox listbox anchored under the writing
   area. Fed by Web::ComposeTopics (the SAME Topic.search scope the mobile topics
   search uses); selecting inserts a removable chip whose slug is submitted as
   topic_slug / secondary_topic_slug. Disabled in reply mode (no markup rendered). */
.compose-editor { position: relative; }
.compose-ac {
  position: absolute; left: 0; right: 0; top: calc(100% + 6px); z-index: 30;
  margin: 0; padding: 6px; list-style: none;
  background: var(--surface-hi); border: 1px solid var(--outline-variant);
  border-radius: var(--r-md); box-shadow: 0 14px 34px -16px rgba(10, 10, 12, .28);
  max-height: 280px; overflow-y: auto;
}
.compose-ac[hidden] { display: none; }
.compose-ac__opt {
  display: flex; align-items: baseline; gap: 9px; padding: 9px 11px;
  border-radius: var(--r-sm); cursor: pointer; color: var(--text);
}
.compose-ac__opt.is-active { background: var(--primary-container); color: var(--on-primary-container); }
.compose-ac__slug { flex: 0 0 auto; font: 700 var(--fs-body-sm) / 1.2 var(--font-body); }
.compose-ac__name {
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: var(--fs-caption); color: var(--text-muted);
}
.compose-ac__opt.is-active .compose-ac__name { color: inherit; opacity: .8; }

/* Selected-topic chips — brand pills with a ✕ remove; the second reads
   "also relevant" (the secondary topic). */
.compose-topics-chips { display: flex; flex-wrap: wrap; gap: 8px; }
.compose-topics-chips[hidden] { display: none; }
.compose-topic-chip {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 5px 6px 5px 12px; border-radius: var(--r-pill);
  background: var(--primary-container); color: var(--on-primary-container);
  font: 700 var(--fs-caption) / 1 var(--font-body);
}
.compose-topic-chip__also {
  font-weight: 600; opacity: .78; padding-left: 6px;
  border-left: 1px solid color-mix(in srgb, var(--on-primary-container) 30%, transparent);
}
.compose-topic-chip__remove {
  display: inline-flex; align-items: center; justify-content: center;
  width: 18px; height: 18px; border: none; border-radius: 50%; cursor: pointer;
  background: color-mix(in srgb, var(--on-primary-container) 14%, transparent);
  color: var(--on-primary-container); font-size: 10px; line-height: 1;
}
.compose-topic-chip__remove:hover { background: color-mix(in srgb, var(--on-primary-container) 26%, transparent); }

/* ── Live link-unfurl preview (JS-on) — the resolved card (reusing .lp-card) the
   post will carry, shown BEFORE posting, with a floating dismiss ✕ that suppresses
   it. A shimmer skeleton fills the gap while the crawl resolves. */
.compose-preview { position: relative; }
.compose-preview[hidden] { display: none; }
.compose-preview .lp-card { margin-top: 0; }
.compose-preview__dismiss {
  position: absolute; top: 8px; right: 8px;
  display: inline-flex; align-items: center; justify-content: center;
  width: 26px; height: 26px; border-radius: 50%; cursor: pointer;
  border: 1px solid var(--outline-variant); background: var(--surface-hi); color: var(--text-muted);
  font-size: 12px; line-height: 1; box-shadow: 0 2px 6px rgba(10, 10, 12, .14);
}
.compose-preview__dismiss:hover { color: var(--text); border-color: var(--accent); }

.lp-card--skeleton { pointer-events: none; }
.lp-card--skeleton .lp-card__media { min-height: 84px; }
.lp-skel {
  display: block; height: 12px; border-radius: var(--r-sm);
  background: linear-gradient(90deg, var(--surface-3) 0, var(--surface-2) 40%, var(--surface-3) 80%);
  background-size: 200% 100%; animation: compose-shimmer 1.3s ease-in-out infinite;
}
.lp-skel--title { width: 62%; height: 14px; margin: 2px 0 8px; }
.lp-skel--line { width: 88%; }
@keyframes compose-shimmer { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } }

@media (prefers-reduced-motion: reduce) {
  .compose-card { transition: none; }
  .compose-bar .btn-primary:active { transform: none; }
  .compose-seg__pill { transition: none; }
  .lp-skel { animation: none; }
}

/* ── Activity/notifications page — sticky Today / This week / Earlier date
   sections, one row per notification (avatar + reason + snippet), a subtle
   per-row "Why?" disclosure, and per-filter empty states. The filter chips
   reuse .link-tabs above. ── */
.notif-group { margin: 0; }
.notif-group__head {
  position: sticky; top: 0; z-index: 1;
  margin: 0; padding: 14px 4px 6px;
  background: var(--surface);
  font-family: var(--font-display); font-weight: 600;
  font-size: var(--fs-caption); letter-spacing: 0.04em; text-transform: uppercase;
  color: var(--text-muted);
}
.notif-list { display: flex; flex-direction: column; }
.notif-item { border-bottom: 1px solid var(--border); }
.notif-row {
  display: flex; align-items: flex-start; gap: 14px; padding: 14px 4px;
  text-decoration: none; color: inherit; position: relative;
}
.notif-row--unread { background: var(--accent-subtle); border-radius: var(--r-md); }
.nr-body { flex: 1 1 auto; min-width: 0; }
.nr-text { margin: 0; font-size: var(--fs-body-sm); color: var(--text); line-height: 1.4; }
.nr-snippet {
  margin: 4px 0 0; font-size: var(--fs-body-sm); color: var(--text-muted);
  font-style: italic; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.nr-time { display: block; margin-top: 4px; font-size: var(--fs-caption); color: var(--text-muted); }
.nr-dot {
  flex: 0 0 auto; width: 9px; height: 9px; border-radius: 50%;
  background: var(--accent); margin-top: 6px;
}

/* "Why am I seeing this?" — a native <details> disclosure (no JS, keyboard-
   accessible). Sits below the row, OUTSIDE the anchor, so the toggle never
   navigates. Aligned under the row text (avatar 44px + 14px gap). */
.notif-why { margin: -4px 0 8px; padding-left: 58px; }
.notif-why__summary {
  display: inline-block; list-style: none; cursor: pointer;
  padding: 2px 0; font-size: var(--fs-caption); font-weight: 700; color: var(--text-muted);
}
.notif-why__summary::-webkit-details-marker { display: none; }
.notif-why__summary::after { content: " \203A"; opacity: 0.7; }
.notif-why[open] .notif-why__summary::after { content: " \02C5"; }
.notif-why__summary:hover { color: var(--accent); }
.notif-why[open] .notif-why__summary { color: var(--text); }
.notif-why__reason {
  margin: 4px 0 2px; font-size: var(--fs-caption); color: var(--text-muted); line-height: 1.5;
}

/* Per-filter empty state — a calm glyph (a caught-up ring for All, a first-run
   bell for the per-type lanes) + warm copy. */
.notif-empty {
  display: flex; flex-direction: column; align-items: center; text-align: center;
  padding: 56px 0 40px; gap: 4px;
}
.notif-empty__glyph {
  display: inline-flex; align-items: center; justify-content: center; margin-bottom: 12px;
}
.notif-empty__glyph--caught { color: var(--accent); }
.notif-empty__glyph--bell {
  width: 56px; height: 56px; border-radius: var(--r-lg);
  background: var(--surface-muted); color: var(--text-muted);
}
.notif-empty__title {
  margin: 0; font-family: var(--font-display); font-weight: 600;
  font-size: var(--fs-body-lg); color: var(--text);
}
.notif-empty__body {
  margin: 0; font-size: var(--fs-body-sm); color: var(--text-muted);
  line-height: 1.5; max-width: 34ch;
}

/* ── Follow-requests inbox (/requests) — one row per incoming pending request:
   requester (avatar + name) on the left, Accept / Reject on the right. The
   header + empty state reuse .feed-head / .feed-empty above. ── */
.req-list { display: flex; flex-direction: column; }
.req-row {
  display: flex; align-items: center; justify-content: space-between; gap: 14px;
  padding: 14px 4px; border-bottom: 1px solid var(--border);
}
.req-who { display: flex; align-items: center; gap: 12px; min-width: 0; text-decoration: none; color: inherit; }
.req-id { display: flex; flex-direction: column; min-width: 0; }
.req-name { font-size: var(--fs-body-sm); font-weight: 700; color: var(--text); }
.req-handle { font-size: var(--fs-caption); color: var(--text-muted); }
.req-actions { display: flex; align-items: center; gap: 8px; flex: 0 0 auto; }
.req-actions .pf-action-form { display: inline; margin: 0; }
.follow-pill.req-reject { color: var(--text-muted); border-color: var(--border); }
.follow-pill.req-reject:hover { background: var(--surface); color: var(--text); }

/* A message-request row (Messages "Requests" lane) reuses .req-* above; the
   last-message preview sits under the sender name (truncated, muted). */
.req-preview {
  font-size: var(--fs-caption); color: var(--text-muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 340px;
}

/* Count pill on the "Requests" tab (the viewer's pending message requests). */
.tab-count {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 18px; height: 18px; padding: 0 5px; border-radius: var(--r-pill);
  background: var(--accent); color: var(--on-primary);
  font-size: var(--fs-caption); font-weight: 700; vertical-align: middle;
}

.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}

/* ── L2 Settings hub — the header/empty-state reuse .feed-head/.feed-empty
   above; the sign-in flash tokens (.auth__flash) are mirrored here under a
   .settings- prefix rather than shared, since the two pages never render
   together (see the settings build report for the dedup note). ── */
.settings-page { display: flex; flex-direction: column; gap: 34px; margin-top: 8px; }
.settings-section {
  padding-bottom: 28px; border-bottom: 1px solid var(--border);
}
.settings-section:last-child { border-bottom: none; padding-bottom: 0; }
.settings-section h2 {
  font-family: var(--font-display); font-weight: 600; font-size: var(--fs-subtitle);
  color: var(--text); margin: 0 0 6px;
}
.settings-section h3 {
  font-family: var(--font-body); font-weight: 700; font-size: var(--fs-body-sm);
  color: var(--text); margin: 0 0 8px;
}
.settings-subsection { margin-top: 18px; }
.settings-subsection:first-of-type { margin-top: 12px; }
.settings-hint { font-size: var(--fs-body-sm); color: var(--text-muted); line-height: 1.5; margin: 0 0 14px; }
.settings-empty { color: var(--text-muted); font-size: var(--fs-body-sm); margin: 0 0 14px; }
.settings-flash { padding: 12px 14px; border-radius: var(--r-md); font-size: var(--fs-body-sm); line-height: 1.4; margin: 16px 0 0; }
.settings-flash--notice { background: var(--primary-container); color: var(--on-primary-container); }
.settings-flash--alert { background: var(--tertiary-container); color: var(--tertiary); }
.settings-error { padding: 10px 12px; margin: 0 0 12px; border-radius: var(--r-md); background: rgba(255, 48, 64, .1); color: var(--heart-red); font-size: var(--fs-body-sm); line-height: 1.4; }

.settings-form .field { margin-bottom: 16px; display: flex; flex-direction: column; gap: 7px; max-width: 420px; }
.settings-form label { font: 600 var(--fs-body-sm) / 1 var(--font-body); color: var(--text); }
.settings-form legend { font: 600 var(--fs-body-sm) / 1 var(--font-body); color: var(--text); padding: 0; margin: 0 0 8px; }
.settings-form fieldset { border: none; padding: 0; margin: 0 0 16px; }
/* The border/background/radius/focus all come from the global Form-controls base
   above; the settings column just wants its text fields, textareas + <select>
   full-width (the radios / checks / file input stay at their natural size). */
.settings-form input[type="text"],
.settings-form input[type="password"],
.settings-form textarea,
.settings-form select { width: 100%; box-sizing: border-box; }
.settings-radio, .settings-check {
  display: flex; align-items: center; gap: 8px; font: 400 var(--fs-body-sm) / 1 var(--font-body);
  color: var(--text); margin-bottom: 8px; cursor: pointer;
}
.settings-actions { display: flex; gap: 10px; margin-top: 4px; }
.settings-danger { color: var(--heart-red); border-color: var(--heart-red); }

.settings-list { list-style: none; margin: 0 0 16px; padding: 0; max-width: 420px; }
.settings-list__item {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 10px 0; border-bottom: 1px solid var(--border); font-size: var(--fs-body-sm); color: var(--text);
}
.settings-list__item:last-child { border-bottom: none; }
.settings-inline-form { display: contents; }
.settings-add-form { display: flex; gap: 10px; max-width: 420px; }
.settings-add-form input { flex: 1; min-width: 0; }

/* One mental model: the avatar is the subject; Change + Remove are grouped beside
   it (a stacked control column), not floating loose across the row. */
.settings-avatar { display: flex; align-items: flex-start; gap: var(--space-lg); flex-wrap: wrap; }
.settings-avatar__figure { position: relative; flex: 0 0 auto; width: 74px; height: 74px; }
/* The just-picked photo, mirrored over the circle until saved/discarded (JS-on). */
.settings-avatar__pending {
  position: absolute; inset: 0; width: 100%; height: 100%;
  border-radius: 50%; object-fit: cover; box-shadow: 0 0 0 2px var(--accent);
}
.settings-avatar__pending-note {
  margin: var(--space-xs) 0 0; max-width: 22ch;
  color: var(--accent); font: 600 var(--fs-caption) / 1.3 var(--font-body);
}
.settings-avatar__controls { display: flex; flex-direction: column; align-items: flex-start; gap: 10px; }
.settings-avatar__controls input[type="file"] { font-size: var(--fs-body-sm); color: var(--text); max-width: 260px; }

/* ── Dynamic settings (Turbo Streams) ───────────────────────────────────────
   Muted words / accounts render as removable chips (replacing the old stacked
   rows + big "Remove" buttons), and every save flips an inline "Saved ✓" beside
   its button instead of a top-of-page flash that scroll-jumps the whole page. */
.settings-chip-list { max-width: 460px; margin: 0 0 14px; }
.settings-chips { list-style: none; display: flex; flex-wrap: wrap; gap: 8px; margin: 0; padding: 0; }
.settings-chip {
  display: inline-flex; align-items: center; gap: 4px; max-width: 100%;
  padding: 5px 5px 5px 12px; border: 1px solid var(--border); border-radius: var(--r-pill);
  background: var(--surface); font-size: var(--fs-body-sm); color: var(--text);
}
.settings-chip__label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.settings-chip__remove {
  display: inline-flex; align-items: center; justify-content: center;
  width: 22px; height: 22px; padding: 0; border: none; border-radius: 50%;
  background: transparent; color: var(--text-muted); cursor: pointer;
  font-size: 16px; line-height: 1;
}
.settings-chip__remove:hover { background: var(--surface-elevated); color: var(--text); }
.settings-chip__remove:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* The inline confirmation region sits beside the Save button (a persistent
   aria-live target the save stream fills). Reserve height so the row never jumps
   as it appears/clears. */
.settings-status { min-height: 1.5em; display: inline-flex; align-items: center; }
.settings-saved {
  display: inline-flex; align-items: center; gap: 5px;
  color: var(--primary); font: 600 var(--fs-body-sm) / 1.4 var(--font-body);
  animation: settings-saved-fade 3.2s ease forwards;
}
.settings-saved::after { content: "\2713"; font-weight: 700; } /* decorative check */
@keyframes settings-saved-fade { 0%, 70% { opacity: 1; } 100% { opacity: 0; } }
/* Reduced-motion: no fade — hold, then disappear in a single step (no animation). */
@media (prefers-reduced-motion: reduce) {
  .settings-saved { animation: settings-saved-hold 3.2s steps(1, end) forwards; }
  @keyframes settings-saved-hold { 0%, 99% { opacity: 1; } 100% { opacity: 0; } }
}

/* ── Settings in-page index — a WRAPPED strip of anchor-link chips so a member can
   jump to any section instead of scrolling the whole ~3,700px page. Pure anchors →
   JS-off. It wraps to 2–3 rows (flex-wrap, like .admin-nav) so EVERY chip stays
   on-screen + clickable at any width — incl. the last "Account" one that the old
   single nowrap-scroll row pushed off the hidden-scrollbar edge (the FE-audit bug).
   Deliberately NOT sticky: a wrapped multi-row bar trailing the scroll would eat
   too much of the viewport (worst on phones), so it sits once at the top. ── */
.settings-index {
  display: flex; gap: 6px; flex-wrap: wrap;
  margin: 8px -4px 0; padding: 10px 4px;
  background: var(--surface); border-bottom: 1px solid var(--border);
}
.settings-index a {
  flex: 0 0 auto; white-space: nowrap; text-decoration: none;
  font: 700 var(--fs-caption) / 1 var(--font-body); color: var(--text-muted);
  padding: 7px 13px; border: 1px solid var(--border); border-radius: var(--r-pill);
}
.settings-index a:hover { color: var(--text); border-color: var(--accent); }
/* The index no longer sticks, so an anchor jump only has to clear the sticky mobile
   top bar (.mnav, ~46px). Keeps the target heading out from under it on phones. */
.settings-page .settings-section h2[id] { scroll-margin-top: 66px; }

/* ── Settings timezone combobox (tz-combobox) — the searchable, keyboard-driven
   upgrade over the native <select>. JS-off the <select> shows and this is unused.
   The wrapper anchors the absolutely-positioned listbox; the input inherits the
   global form-control base (border/radius/focus-ring). ── */
.tz-combobox { position: relative; max-width: 420px; }
.tz-combobox__input { width: 100%; box-sizing: border-box; }
.tz-combobox__list {
  position: absolute; z-index: 20; left: 0; right: 0; top: calc(100% + 4px);
  max-height: 320px; overflow-y: auto; margin: 0; padding: 4px; list-style: none;
  background: var(--surface-elevated); border: 1px solid var(--border);
  border-radius: var(--r-md); box-shadow: 0 6px 20px -8px rgba(20, 15, 10, .12);
}
.tz-combobox__option {
  padding: 9px 12px; border-radius: var(--r-sm); cursor: pointer;
  font: 400 var(--fs-body-sm) / 1.3 var(--font-body); color: var(--text);
}
.tz-combobox__option:hover { background: var(--surface-muted); }
.tz-combobox__option.is-active { background: var(--accent-subtle); color: var(--text); }
.tz-combobox__option[aria-selected="true"] { font-weight: 700; }
/* The "Detected from your browser — change anytime" note under the picker. */
.tz-detected { margin: 8px 0 0; color: var(--accent); font-weight: 600; }

/* ── Admin console (BE-ADMIN) — internal, admin-only surface, never indexed ─── */
.admin-nav {
  display: flex; gap: 6px; flex-wrap: wrap;
  margin: 0 0 20px; padding-bottom: 12px; border-bottom: 1px solid var(--border);
}
.admin-nav__link {
  font-family: var(--font-body); font-weight: 700; font-size: var(--fs-body-sm);
  color: var(--text-muted); text-decoration: none;
  padding: 8px 14px; border-radius: var(--r-pill);
}
.admin-nav__link:hover { color: var(--text); background: var(--surface-muted); }
.admin-nav__link.is-active { color: var(--on-primary); background: var(--accent); }

.admin-stats { display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px; }
.admin-stat {
  display: flex; flex-direction: column; gap: 4px;
  padding: 18px 20px; border: 1px solid var(--border); border-radius: var(--r-lg);
  background: var(--surface-elevated); text-decoration: none; color: var(--text);
}
.admin-stat:hover { border-color: var(--accent); }
.admin-stat__value { font-family: var(--font-display); font-weight: 600; font-size: var(--fs-title); }
.admin-stat__label { font-size: var(--fs-body-sm); color: var(--text-muted); }

.admin-filters { display: flex; gap: 8px; margin: 0 0 16px; }
.admin-chip {
  font-size: var(--fs-caption); font-weight: 700; text-decoration: none;
  color: var(--text-muted); padding: 6px 14px; border: 1px solid var(--border); border-radius: var(--r-pill);
}
.admin-chip.is-active { color: var(--on-primary); background: var(--accent); border-color: var(--accent); }

/* Responsive-table wrapper: on narrow screens the wide admin tables scroll INSIDE
   their own box instead of forcing the whole page to scroll sideways. */
.admin-table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }
.admin-table { width: 100%; border-collapse: collapse; font-size: var(--fs-body-sm); }
.admin-table th, .admin-table td {
  text-align: left; padding: 10px 8px; border-bottom: 1px solid var(--border); vertical-align: top;
  overflow-wrap: anywhere; /* long unbreakable emails wrap instead of blowing out the layout */
}
.admin-table th { color: var(--text-muted); font-weight: 700; }

.admin-pill {
  display: inline-block; font-size: var(--fs-caption); font-weight: 700;
  padding: 2px 10px; border-radius: var(--r-pill); background: var(--surface-muted); color: var(--text);
}
.admin-pill--open, .admin-pill--suspended { background: var(--badge-red); color: #fff; }
.admin-pill--resolved, .admin-pill--active { background: var(--verified); color: #fff; }

/* Moderation severity chip — colour-coded low → high (a case's at-a-glance urgency).
   Shares the pill shape; an unknown/blank level falls back to the neutral base. */
.admin-sev {
  display: inline-block; font-size: var(--fs-caption); font-weight: 700; text-transform: capitalize;
  padding: 2px 10px; border-radius: var(--r-pill); background: var(--surface-muted); color: var(--text);
}
.admin-sev--low    { background: var(--surface-muted); color: var(--text-muted); }
.admin-sev--medium { background: #C77700; color: #fff; }
.admin-sev--high   { background: var(--badge-red); color: #fff; }

/* Colour swatch input (curation accent). Trim the native chrome to a tidy square. */
.admin-swatch {
  width: 44px; height: 32px; padding: 2px; vertical-align: middle;
  border: 1px solid var(--border); border-radius: var(--r-sm); background: var(--surface-elevated); cursor: pointer;
}

.admin-dl { display: grid; grid-template-columns: max-content 1fr; gap: 6px 16px; margin: 0; font-size: var(--fs-body-sm); }
.admin-dl dt { color: var(--text-muted); font-weight: 700; }
.admin-dl dd { margin: 0; }

.admin-muted { color: var(--text-muted); font-size: var(--fs-caption); }
.admin-post-body { white-space: pre-wrap; }

.admin-edit-row { padding: 14px 0; border-bottom: 1px solid var(--border); }
.admin-edit-row:last-child { border-bottom: none; }
.admin-edit-row__title { display: flex; gap: 8px; align-items: center; margin-bottom: 6px; }
.admin-edit-row__inline { display: flex; gap: 12px; flex-wrap: wrap; align-items: center; }

/* ── Messages / DM (direct-messaging CORE) ─────────────────────────────────
   The inbox rows + thread bubbles + composer reuse .feed-head / .feed-empty
   for the header + empty state; only the DM-specific pieces are tokenized here. */
.dm-new { display: flex; gap: 8px; margin: 10px 0 18px; }
.dm-new__input {
  flex: 1 1 auto; padding: 10px 14px; border: 1px solid var(--border);
  border-radius: var(--r-pill); background: var(--surface-elevated); color: var(--text);
  font: 400 var(--fs-body-sm) / 1.4 var(--font-body);
}
/* focus ring inherited from the global Form-controls base */
.dm-new__btn { flex: 0 0 auto; }

.dm-list { display: flex; flex-direction: column; }
.dm-row {
  display: flex; align-items: center; gap: 12px; padding: 12px 6px;
  border-bottom: 1px solid var(--border); text-decoration: none; color: var(--text);
}
.dm-row:hover { background: var(--accent-subtle); }
.dm-row__body { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.dm-row__name { font-weight: 700; font-size: var(--fs-body-sm); }
.dm-row__snippet {
  color: var(--text-muted); font-size: var(--fs-body-sm);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.dm-row--unread .dm-row__name, .dm-row--unread .dm-row__snippet { color: var(--text); font-weight: 700; }
.dm-row__badge {
  flex: 0 0 auto; min-width: 20px; height: 20px; padding: 0 6px; border-radius: var(--r-pill);
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--accent); color: var(--on-primary); font-weight: 700; font-size: var(--fs-caption);
}

.dm-head { display: flex; align-items: center; gap: 10px; }
.dm-head__back { text-decoration: none; color: var(--text-muted); font-size: 20px; line-height: 1; }
.dm-head__back:hover { color: var(--accent); }
.dm-head__title { margin: 0; }
.dm-head__title a { color: inherit; text-decoration: none; }
.dm-head__title a:hover { color: var(--accent); }

.dm-thread { display: flex; flex-direction: column; gap: 8px; padding: 14px 0; }
.dm-thread__empty { padding: 24px 0; }
.dm-bubble { display: flex; align-items: flex-end; gap: 8px; max-width: 82%; align-self: flex-start; }
.dm-bubble--mine { align-self: flex-end; flex-direction: row-reverse; }
.dm-bubble__inner {
  padding: 9px 13px; border-radius: var(--r-lg); background: var(--surface-muted); color: var(--text);
}
.dm-bubble--mine .dm-bubble__inner { background: var(--accent); color: var(--on-primary); }
.dm-bubble__text { margin: 0; font-size: var(--fs-body-sm); line-height: 1.4; white-space: pre-wrap; word-break: break-word; }
.dm-bubble__photo { border-radius: var(--r-md); overflow: hidden; max-width: 280px; background: var(--cover); }
.dm-bubble__photo:not(:last-child) { margin-bottom: 6px; }
.dm-bubble__photo img { display: block; width: 100%; height: auto; }
.dm-bubble__deleted { font-size: var(--fs-body-sm); color: var(--text-muted); }
.dm-bubble__time { display: block; margin-top: 3px; font-size: var(--fs-caption); color: var(--text-muted); }
.dm-bubble--mine .dm-bubble__time { color: var(--primary-tint); }

/* Quoted reply strip — a single-line preview of the replied-to message, above the
   body (mobile MessageBubble reply_preview parity). */
.dm-bubble__reply {
  margin-bottom: 4px; padding: 3px 8px; border-left: 2px solid var(--border);
  border-radius: 4px; background: var(--surface);
}
.dm-bubble--mine .dm-bubble__reply { border-left-color: var(--primary-tint); background: rgba(255, 255, 255, 0.14); }
.dm-bubble__reply-text {
  display: block; font-size: var(--fs-caption); color: var(--text-muted); line-height: 1.35;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.dm-bubble--mine .dm-bubble__reply-text { color: var(--primary-tint); }

/* A URL unfurl card inside a text DM — reuses .lp-card; margin lifts it off the body. */
.dm-bubble__link { margin-top: 6px; }
.dm-bubble__link .lp-card { max-width: 280px; }

/* The single "Read" receipt under the viewer's own latest read message. */
.dm-bubble__receipt {
  display: block; margin-top: 2px; text-align: right;
  font-size: var(--fs-caption); color: var(--text-muted);
}
.dm-bubble--mine .dm-bubble__receipt { color: var(--primary-tint); }

/* Counterpart-bubble ⋯ overflow menu (→ Report). Reuses the shared pc-menu trigger/
   panel/item styles; only the container placement is bubble-specific (a small,
   quiet control beside the counterpart's bubble — no post-header margin-left:auto). */
.dm-bubble__menu { position: relative; align-self: center; flex: 0 0 auto; list-style: none; }
.dm-bubble__menu .pc-menu__trigger { width: 24px; height: 24px; opacity: .5; }
.dm-bubble__menu[open] .pc-menu__trigger, .dm-bubble__menu .pc-menu__trigger:hover { opacity: 1; }

.dm-compose { display: flex; gap: 8px; align-items: flex-end; padding: 8px 0 4px; border-top: 1px solid var(--border); flex-wrap: wrap; }
.dm-compose__input {
  flex: 1 1 auto; resize: vertical; padding: 10px 14px; border: 1px solid var(--border);
  border-radius: var(--r-md); background: var(--surface-elevated); color: var(--text);
  font: 400 var(--fs-body-sm) / 1.4 var(--font-body);
}
/* focus ring inherited from the global Form-controls base */
.dm-compose__photo { flex: 1 1 100%; display: flex; flex-wrap: wrap; gap: 8px; align-items: flex-start; }
/* The uploader + the alt-text input sit side by side on wide screens but WRAP to
   stacked full-width rows on phones (basis 240px), so the row never overflows. */
.dm-compose__photo > * { flex: 1 1 240px; min-width: 0; }
.dm-compose__photo-label { flex: 0 0 auto; cursor: pointer; font-size: var(--fs-body); }
.dm-compose__file { flex: 0 0 auto; font-size: var(--fs-caption); color: var(--text-muted); }
.dm-compose__alt {
  flex: 1 1 auto; min-width: 0; padding: 8px 12px; border: 1px solid var(--border);
  border-radius: var(--r-md); background: var(--surface-elevated); color: var(--text);
  font: 400 var(--fs-body-sm) / 1.4 var(--font-body);
}
/* focus ring inherited from the global Form-controls base */
.dm-compose__btn { flex: 0 0 auto; }

/* The composer's blocked state — a pending recipient must Accept the request first. */
.dm-compose-blocked {
  margin: 8px 0 4px; padding: 12px 0 4px; border-top: 1px solid var(--border);
  color: var(--text-muted); font-size: var(--fs-body-sm); text-align: center;
}

/* ── DM message-request band (inline, in an opened pending thread) ──────────
   The Accept/Decline band at the top of a thread the viewer is the pending
   recipient of. Reuses .req-actions + .follow-pill from the Requests lane. */
.dm-request-band {
  display: flex; flex-wrap: wrap; align-items: center; gap: 10px 14px;
  margin: 12px 0; padding: 14px 16px;
  border: 1px solid var(--border); border-radius: var(--r-md); background: var(--surface-muted);
}
.dm-request-band__note { flex: 1 1 220px; margin: 0; font-size: var(--fs-body-sm); color: var(--text-muted); line-height: 1.4; }
.dm-request-band__note strong { color: var(--text); }

/* ── DM recipient picker (GET /messages/new) ───────────────────────────────
   Recent + Following + debounced search rows; each row is the whole POST /messages
   open-or-reuse door with a Message/Request hint. */
.dm-new-link { margin: 6px 0 12px; }
.dm-new-link__btn { display: inline-block; text-decoration: none; }
/* The single "start a conversation" block: search input, then the JS-off exact-username
   fallback (hidden once the search is enhanced) — one clear path, no redundant fields. */
.dm-picker-start { margin: 10px 0 8px; }
.dm-picker-start .dm-new { margin: 8px 0 0; }
/* The controller sets [hidden] on the fallback JS-on; win over .dm-new's display:flex. */
.dm-picker-start [hidden] { display: none; }
.dm-picker-search { margin: 2px 0 6px; }
.dm-picker-search__input { width: 100%; }
.dm-picker { display: block; }
.dm-picker-section {
  margin: 16px 0 4px; font: 700 var(--fs-caption) / 1 var(--font-body);
  text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-muted);
}
.dm-picker-list { display: flex; flex-direction: column; }
.dm-picker-row-form { margin: 0; display: block; }
.dm-picker-row {
  display: flex; align-items: center; gap: 12px; width: 100%; padding: 10px 6px;
  border: 0; border-bottom: 1px solid var(--border); background: none; text-align: left;
  color: var(--text); cursor: pointer; font: inherit;
}
.dm-picker-row:hover { background: var(--accent-subtle); }
.dm-picker-row__avatar { flex: 0 0 auto; display: inline-flex; }
.dm-picker-row__id { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.dm-picker-row__name { font-weight: 700; font-size: var(--fs-body-sm); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.dm-picker-row__handle { font-size: var(--fs-caption); color: var(--text-muted); }
.dm-picker-row__note { font-size: var(--fs-caption); color: var(--text-muted); }
.dm-picker-row__hint {
  flex: 0 0 auto; font-size: var(--fs-caption); font-weight: 700; color: var(--accent);
  padding: 4px 10px; border: 1px solid var(--border); border-radius: var(--r-pill);
}
.dm-picker-row__hint--request { color: var(--text-muted); }

/* ─────────────────────────────────────────────────────────────────────────────
   Web video call + screen share (feature-flagged; DM thread header + overlays).
   ───────────────────────────────────────────────────────────────────────────── */
.dm-call { margin-left: auto; }

.dm-call__start {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 7px 14px; border: 1px solid var(--primary); border-radius: var(--r-pill);
  background: var(--primary); color: #fff; cursor: pointer;
  font: 600 var(--fs-body-sm) / 1 var(--font-body);
}
.dm-call__start:hover { background: var(--primary-bright); border-color: var(--primary-bright); }
.dm-call__start-label { line-height: 1; }

/* The in-call stage — a fixed, full-viewport dark theatre over everything. */
.call-stage {
  position: fixed; inset: 0; z-index: 1000;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 18px; padding: 24px; background: rgba(8, 8, 12, 0.94);
}
.call-stage[hidden] { display: none; }

.call-stage__frame {
  position: relative; width: min(92vw, 720px); aspect-ratio: 16 / 10;
  border-radius: var(--r-lg); overflow: hidden;
  background: #0b0b10; box-shadow: 0 24px 60px rgba(0, 0, 0, 0.5);
}
.call-stage__remote { width: 100%; height: 100%; object-fit: cover; background: #0b0b10; }
.call-stage__local {
  position: absolute; right: 14px; bottom: 14px; width: 28%; max-width: 190px;
  aspect-ratio: 16 / 10; object-fit: cover; border-radius: var(--r-md);
  border: 2px solid rgba(255, 255, 255, 0.85); background: #16181c;
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.45); transform: scaleX(-1); /* mirror self-view */
}
.call-stage__status {
  position: absolute; left: 0; right: 0; top: 14px; margin: 0; text-align: center;
  color: rgba(255, 255, 255, 0.92); font: 600 var(--fs-body) / 1.3 var(--font-body);
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.6); pointer-events: none;
}

.call-stage__controls, .call-ring__actions {
  display: flex; flex-wrap: wrap; gap: 10px; justify-content: center;
}

.call-ctl {
  min-width: 96px; padding: 11px 18px; cursor: pointer;
  border: 1px solid rgba(255, 255, 255, 0.28); border-radius: var(--r-pill);
  background: rgba(255, 255, 255, 0.12); color: #fff;
  font: 600 var(--fs-body-sm) / 1 var(--font-body);
}
.call-ctl:hover { background: rgba(255, 255, 255, 0.2); }
.call-ctl--active { background: var(--primary); border-color: var(--primary); }
.call-ctl--accept { background: var(--primary); border-color: var(--primary); }
.call-ctl--accept:hover { background: var(--primary-bright); }
.call-ctl--end { background: var(--heart-red); border-color: var(--heart-red); }
.call-ctl--end:hover { filter: brightness(1.08); }

/* Incoming ring — a compact centred card over the theatre backdrop. */
.call-ring {
  position: fixed; inset: 0; z-index: 1001;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 18px; padding: 24px; background: rgba(8, 8, 12, 0.9);
}
.call-ring[hidden] { display: none; }
.call-ring__who {
  margin: 0; color: #fff; text-align: center;
  font: 500 var(--fs-body-lg) / 1.4 var(--font-body);
}
.call-ring__who strong { font-weight: 700; }
/* ── In-app realtime toast (nav_live_controller) ──────────────────────────
   A brief, calm notification when a new DM / activity / follow request lands
   while a tab is open. Fixed bottom-centre pill, quiet elevation, slides up +
   fades in, auto-dismisses. Premium/calm brand: one short toast, never a nag. */
.nav-toast {
  position: fixed; left: 50%; bottom: 28px; transform: translate(-50%, 12px);
  z-index: 60; max-width: min(90vw, 420px);
  display: flex; align-items: center; gap: var(--space-sm);
  padding: 12px 18px; border-radius: var(--r-pill);
  background: var(--surface-elevated); color: var(--text);
  border: 1px solid var(--border);
  box-shadow: 0 8px 28px rgba(20, 16, 40, 0.18);
  font: 600 var(--fs-body-sm) / 1.3 var(--font-body);
  opacity: 0; pointer-events: none;
  transition: opacity 220ms ease, transform 220ms ease;
}
.nav-toast::before {
  content: ""; flex: 0 0 auto; width: 8px; height: 8px; border-radius: 50%;
  background: var(--accent);
}
.nav-toast--in { opacity: 1; transform: translate(-50%, 0); }
@media (prefers-reduced-motion: reduce) {
  .nav-toast { transition: opacity 220ms ease; }
  .nav-toast, .nav-toast--in { transform: translate(-50%, 0); }
}

/* ── File uploader (Web::FileUploadComponent / file_upload_controller) ──────
   ONE reusable uploader across settings avatar, compose photo and DM photo.
   Progressive enhancement: JS-OFF shows a plain native <input type=file> (the
   decorative dropzone chrome stays hidden — it would be a lie without JS);
   JS-ON, .is-enhanced reveals the drag-drop zone and the native input becomes a
   transparent full-cover click/keyboard/attach_file target. */
.file-upload { display: block; }
.file-upload__label {
  display: block; margin-bottom: var(--space-xs);
  font: 600 var(--fs-body-sm) / 1.3 var(--font-body); color: var(--text);
}
.file-upload__hint {
  margin: 0 0 var(--space-sm); color: var(--text-muted);
  font: 400 var(--fs-caption) / 1.4 var(--font-body);
}
.file-upload__zone { position: relative; }

/* Decorative zone chrome — enhancement only. */
.file-upload__icon,
.file-upload__prompt,
.file-upload__meta { display: none; }
.file-upload.is-enhanced .file-upload__zone {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 6px; min-height: 132px; padding: var(--space-md); cursor: pointer;
  border: 1.5px dashed var(--border); border-radius: var(--r-lg);
  background: var(--surface-1); text-align: center;
  transition: border-color 160ms ease, background 160ms ease;
}
.file-upload.is-enhanced .file-upload__zone:hover,
.file-upload.is-enhanced .file-upload__zone:focus-within {
  border-color: var(--accent); background: var(--surface-2);
}
.file-upload.is-enhanced .file-upload__zone.is-dragover {
  border-color: var(--accent); border-style: solid;
  background: var(--primary-tint, var(--surface-2));
}
.file-upload.is-enhanced .file-upload__icon { display: block; width: 30px; height: 30px; color: var(--accent); }
.file-upload.is-enhanced .file-upload__prompt {
  display: block; margin: 0; color: var(--text);
  font: 500 var(--fs-body-sm) / 1.4 var(--font-body);
}
.file-upload__prompt-strong { font-weight: 700; }
.file-upload__browse-text { color: var(--accent); font-weight: 600; text-decoration: underline; }
.file-upload.is-enhanced .file-upload__meta {
  display: block; margin: 0; color: var(--text-muted);
  font: 400 var(--fs-caption) / 1.3 var(--font-body);
}

/* Native input: visible + normal JS-off (the fallback control). JS-on it is
   visually hidden with the accessible sr-only clip pattern — NOT display:none /
   visibility:hidden / opacity:0, so it stays keyboard-focusable AND
   Capybara/Cuprite-visible (attach_file drives it). The zone is the visual
   control; a zone click delegates to input.click(). */
.file-upload__input { display: block; max-width: 320px; font-size: var(--fs-body-sm); color: var(--text); }
.file-upload.is-enhanced .file-upload__input {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; border: 0;
}

/* Preview cards. */
.file-upload__previews { list-style: none; margin: var(--space-sm) 0 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.file-upload__item {
  display: flex; align-items: center; gap: var(--space-sm);
  padding: 8px; border: 1px solid var(--border); border-radius: var(--r-md);
  background: var(--surface-1);
}
.file-upload__item.is-done { border-color: var(--accent); }
.file-upload__thumb { flex: 0 0 auto; width: 48px; height: 48px; border-radius: var(--r-sm); overflow: hidden; background: var(--surface-2); }
.file-upload__thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.file-upload__item-body { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 4px; }
.file-upload__item-name { font: 500 var(--fs-caption) / 1.3 var(--font-body); color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.file-upload__progress { display: block; height: 5px; border-radius: var(--r-pill); background: var(--surface-3, var(--border)); overflow: hidden; }
.file-upload__progress-bar { display: block; height: 100%; width: 0; background: var(--accent); border-radius: var(--r-pill); transition: width 160ms ease; }
.file-upload__item-status { font: 400 var(--fs-caption) / 1.2 var(--font-body); color: var(--text-muted); }
.file-upload__item-status.is-error { color: var(--badge-red, var(--heart-red)); }

/* Transient progress: the filling bar shows ONLY during the upload. On completion
   (.is-done) it collapses — a full-width bar reading "ready" is noise — leaving a
   compact "✓ ready" confirmation beside the thumbnail + discard ×. Errors never get
   .is-done, so they keep their inline red status (no ✓). */
.file-upload__item.is-done .file-upload__progress { display: none; }
.file-upload__item.is-done .file-upload__item-status { color: var(--accent); font-weight: 600; }
.file-upload__item.is-done .file-upload__item-status::before { content: "✓"; margin-right: 5px; font-weight: 700; }

.file-upload__remove {
  flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 28px; border: none; border-radius: 50%;
  background: var(--surface-2); color: var(--text-muted); cursor: pointer;
}
.file-upload__remove:hover { background: var(--surface-3, var(--border)); color: var(--text); }

.file-upload__current { display: flex; align-items: center; gap: var(--space-sm); margin-top: var(--space-sm); }
.file-upload__current-img { width: 48px; height: 48px; border-radius: 50%; object-fit: cover; }
.file-upload__current-note { font: 400 var(--fs-caption) / 1.2 var(--font-body); color: var(--text-muted); }

.file-upload__notice { margin: var(--space-xs) 0 0; color: var(--badge-red, var(--heart-red)); font: 500 var(--fs-caption) / 1.3 var(--font-body); }

/* Settings avatar: the form is a 420px flex row (input + submit); let the
   uploader own a full row so the "Upload photo" button wraps below it instead of
   being squeezed. Scoped to the avatar form — the muted-words/accounts forms that
   share .settings-add-form still fit their input + button on one line. */
.settings-add-form { flex-wrap: wrap; }
.settings-add-form .file-upload { flex: 1 1 100%; }

@media (prefers-reduced-motion: reduce) {
  .file-upload.is-enhanced .file-upload__zone,
  .file-upload__progress-bar { transition: none; }
}

/* ── Compact uploader variant (compose only) ──────────────────────────────────
   FileUploadComponent(compact: true) adds .file-upload--compact. JS-on, it
   collapses the tall dropzone into a slim inline "add photo" pill (progressive
   disclosure of media) while keeping the SAME input / drag-drop / preview /
   Direct-Upload behaviour. Placed AFTER the base .file-upload rules so these
   equal-specificity overrides win by source order. Scoped to --compact, so the
   settings avatar + DM uploaders are visually untouched. JS-off, .is-enhanced is
   absent, so the plain native input shows exactly as before (the fallback). */
.file-upload--compact .file-upload__label,
.file-upload--compact .file-upload__hint {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; border: 0;
}
.file-upload--compact.is-enhanced .file-upload__zone {
  display: inline-flex; flex-direction: row; align-items: center; justify-content: flex-start;
  width: auto; min-height: 0; gap: 8px; padding: 9px 16px 9px 13px;
  border: 1.5px solid var(--border); border-radius: var(--r-pill);
  background: var(--surface-1); text-align: left;
}
.file-upload--compact.is-enhanced .file-upload__icon { width: 18px; height: 18px; }
.file-upload--compact.is-enhanced .file-upload__prompt { white-space: nowrap; font-size: var(--fs-body-sm); }
.file-upload--compact.is-enhanced .file-upload__meta { display: none; }

/* ── Large gallery preview variant (compose only) ─────────────────────────────
   FileUploadComponent(preview: :gallery) adds .file-upload--gallery. It re-lays
   the SAME preview markup (no template change → the settings avatar + DM
   uploaders are byte-identical) into a big Threads/X/Instagram-style inline image
   preview: a full-width thumbnail, a floating ✕ remove overlay, and the filename +
   upload status/progress as a quiet caption over a bottom scrim. Placed AFTER the
   base .file-upload rules so equal-specificity overrides win by source order. */
.file-upload--gallery .file-upload__previews { display: grid; grid-template-columns: minmax(0, 1fr); gap: 10px; margin-top: 12px; }
.file-upload--gallery .file-upload__item {
  position: relative; display: block; padding: 0; overflow: hidden;
  aspect-ratio: 3 / 2; border-radius: var(--r-lg); border: 1px solid var(--outline-variant);
  background: var(--surface-3);
}
.file-upload--gallery .file-upload__item.is-done { border-color: var(--outline-variant); }
.file-upload--gallery .file-upload__thumb {
  position: absolute; inset: 0; width: 100%; height: 100%; border-radius: 0;
}
.file-upload--gallery .file-upload__thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* Floating remove — an ✕ on a dark scrim disc at the image's top-right corner. */
.file-upload--gallery .file-upload__remove {
  position: absolute; top: 10px; right: 10px; z-index: 3;
  width: 32px; height: 32px; color: #fff;
  background: rgba(10, 10, 12, .6); backdrop-filter: blur(3px);
}
.file-upload--gallery .file-upload__remove:hover { background: rgba(10, 10, 12, .82); color: #fff; }

/* Caption strip — filename + status + progress over a bottom gradient scrim. */
.file-upload--gallery .file-upload__item-body {
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 2;
  padding: 26px 14px 11px; gap: 6px;
  background: linear-gradient(to top, rgba(10, 10, 12, .8), rgba(10, 10, 12, .28) 62%, transparent);
}
.file-upload--gallery .file-upload__item-name { color: #fff; font-weight: 600; }
.file-upload--gallery .file-upload__item-status { color: rgba(255, 255, 255, .82); }
.file-upload--gallery .file-upload__progress { background: rgba(255, 255, 255, .28); }
.file-upload--gallery .file-upload__item.is-done .file-upload__progress { display: none; }
/* The confirmed "✓ ready" caption sits over the image's dark scrim here — keep it
   white for contrast (the base rule tints it accent for the light row preview). */
.file-upload--gallery .file-upload__item.is-done .file-upload__item-status { color: #fff; }

/* ── Password-strength meter (auth signup / reset / set-password) ─────────────
   A decorative 4-segment bar + a plain caption, filled live by the
   `password-strength` Stimulus controller. The container is aria-hidden (the
   caption text carries the meaning) and stays hidden until the field is
   non-empty (the controller toggles [hidden] — never pre-validates). The
   segment colors mirror the mobile app's danger → warning → success ramp as
   locally-scoped vars, so dark mode swaps them without touching the tokens. */
.pw-meter {
  --pw-weak:   #DC2626;
  --pw-fair:   #D97706;
  --pw-strong: #16A34A;
  display: flex; flex-direction: column; gap: 6px; margin: 2px 0;
}
.pw-meter[hidden] { display: none; }
.pw-meter__bar { display: flex; gap: 6px; }
.pw-meter__seg {
  flex: 1; height: 4px; border-radius: var(--r-sm);
  background: var(--surface-muted); transition: background .15s ease;
}
.pw-meter[data-score="1"] .pw-meter__seg--on { background: var(--pw-weak); }
.pw-meter[data-score="2"] .pw-meter__seg--on { background: var(--pw-fair); }
.pw-meter[data-score="3"] .pw-meter__seg--on,
.pw-meter[data-score="4"] .pw-meter__seg--on { background: var(--pw-strong); }
.pw-meter__label { margin: 0; font: 500 var(--fs-caption) / 1.4 var(--font-body); color: var(--text-muted); }

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]):not(.theme-light) .pw-meter {
    --pw-weak: #F87171; --pw-fair: #FBBF24; --pw-strong: #4ADE80;
  }
}
:root[data-theme="dark"] .pw-meter,
.theme-dark .pw-meter {
  --pw-weak: #F87171; --pw-fair: #FBBF24; --pw-strong: #4ADE80;
}

/* ── Deletion review page (GET /settings/deletion) — consequences + reauth +
   type-@username confirm gate before the destructive request. Reuses the
   .settings-* card chrome; adds the honest what-happens / what-stays lists, the
   danger callout, and the armed-when-typed destructive button. ── */
.delete-review { max-width: 560px; }
.delete-review__list { list-style: none; margin: 0 0 18px; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.delete-review__list li {
  position: relative; padding-left: 18px;
  font: 400 var(--fs-body-sm) / 1.5 var(--font-body); color: var(--text-muted);
}
.delete-review__list li::before {
  content: ""; position: absolute; left: 2px; top: 8px;
  width: 5px; height: 5px; border-radius: 50%; background: var(--outline);
}
.delete-review__note {
  padding: 12px 14px; border-radius: var(--r-md); margin: 0 0 18px;
  font: 500 var(--fs-body-sm) / 1.5 var(--font-body);
  background: var(--tertiary-container); color: var(--tertiary);
}
.delete-review__actions { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 6px; }
.btn-danger {
  display: inline-flex; align-items: center; justify-content: center;
  padding: 11px 20px; border-radius: var(--r-pill); border: 1px solid transparent;
  font: 600 var(--fs-body-sm) / 1 var(--font-body); cursor: pointer;
  background: var(--heart-red); color: #fff; transition: opacity .12s ease, transform .12s ease;
}
.btn-danger:hover { transform: translateY(-1px); }
.btn-danger:disabled { opacity: .5; cursor: not-allowed; transform: none; }

/* ── Private-profile follow-wall (LOW-UX) — the calm explanatory block that fills
   the void the withheld feed leaves below a private account's identity header.
   Reuses tokens only; never hints at what's inside (privacy fails CLOSED). ── */
.private-wall {
  margin: 22px auto 8px; max-width: 460px; text-align: center;
  display: flex; flex-direction: column; align-items: center;
  padding: 30px 24px; border: 1px solid var(--border); border-radius: var(--r-lg);
  background: var(--surface);
}
.private-wall__icon {
  display: inline-flex; align-items: center; justify-content: center;
  width: 52px; height: 52px; border-radius: 50%; margin-bottom: 14px;
  background: var(--primary-container); color: var(--accent);
}
.private-wall__title {
  font-family: var(--font-display); font-weight: 600; font-size: var(--fs-title);
  color: var(--text); margin: 0 0 8px; line-height: 1.2;
}
.private-wall__lead {
  color: var(--text-muted); font-size: var(--fs-body-lg); line-height: 1.55; margin: 0;
}
.private-wall__lead b { color: var(--text); font-weight: 700; }
.private-wall__points {
  list-style: none; margin: 18px 0 0; padding: 16px 0 0; text-align: left;
  border-top: 1px solid var(--border); width: 100%;
  display: flex; flex-direction: column; gap: 10px;
}
.private-wall__points li {
  position: relative; padding-left: 22px;
  color: var(--text-muted); font-size: var(--fs-body-sm); line-height: 1.5;
}
.private-wall__points li::before {
  content: ""; position: absolute; left: 4px; top: 8px;
  width: 6px; height: 6px; border-radius: 50%; background: var(--accent);
}

/* ── Post-edit form (LOW-UX) — the live char counter + the read-only current
   photo, so a photo post's image is visibly safe while editing text. ── */
.edit-field { position: relative; }
.edit-counter {
  margin: 6px 2px 0; text-align: right;
  font: 600 var(--fs-caption) / 1 var(--font-body); color: var(--text-muted);
}
.edit-counter.is-warn { color: var(--violet-600); }
.edit-counter.is-over { color: var(--heart-red); }
.edit-photo {
  margin: 0; border: 1px solid var(--border); border-radius: var(--r-lg);
  overflow: hidden; background: var(--surface);
}
.edit-photo__img { display: block; width: 100%; max-height: 320px; object-fit: cover; }
.edit-photo__note {
  padding: 10px 14px; color: var(--text-muted);
  font: 500 var(--fs-caption) / 1.45 var(--font-body);
  border-top: 1px solid var(--border);
}

/* ── Generic "not available" state page (MED-UX) — the self-contained, byte-stable
   fallback (web/public/unavailable.html.erb, rendered layout: false). Brand-dressed
   with tokens + the shared button atoms; every rule here is constant (no per-request
   anything) so the non-enumeration byte-identical invariant holds. ── */
.unavailable-page { background: var(--bg); color: var(--text); min-height: 100vh; margin: 0; font-family: var(--font-body); }
.unavailable {
  min-height: 100vh; display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 30px; padding: 40px 20px; text-align: center;
}
.unavailable__mark {
  font-family: var(--font-display); font-weight: 600; font-size: 24px; letter-spacing: -0.01em;
  color: var(--text); text-decoration: none;
}
.unavailable__card {
  display: flex; flex-direction: column; align-items: center; gap: 14px;
  max-width: 420px; padding: 36px 28px;
  border: 1px solid var(--border); border-radius: var(--r-lg); background: var(--surface);
}
.unavailable__glyph {
  display: inline-flex; align-items: center; justify-content: center;
  width: 58px; height: 58px; border-radius: 50%; margin-bottom: 4px;
  background: var(--primary-container); color: var(--accent);
}
.unavailable__title {
  font-family: var(--font-display); font-weight: 600; font-size: var(--fs-title);
  color: var(--text); margin: 0; line-height: 1.2;
}
.unavailable__lead {
  color: var(--text-muted); font-size: var(--fs-body-lg); line-height: 1.55; margin: 0; max-width: 340px;
}
.unavailable__actions { display: flex; flex-wrap: wrap; gap: 12px; justify-content: center; margin-top: 8px; }
