/* ═══════════════════════════════════════════════════════════════
   INTERACTINK — design tokens
   Restyled to match the Twitter-clone reference: white app shell,
   sticky icon/label sidebar, bordered center column, green
   accent, rounded-full controls, rose "like" color.
   Variable NAMES kept stable (lots of rules reference them) —
   only the values changed, so nothing downstream breaks.
   ═══════════════════════════════════════════════════════════════ */
/* ── CROSS-PAGE VIEW TRANSITIONS — this app is plain multi-page HTML
   (no SPA router), so every internal link is a real full navigation.
   This one rule asks supporting browsers (Chrome/Edge 126+) to
   cross-fade between the old and new page instead of the default
   hard flash-to-white, which is most of what makes an MPA "feel" like
   an SPA. Free progressive enhancement — browsers without support
   just keep the normal instant navigation, nothing else changes. ── */
@view-transition {
  navigation: auto;
}

:root {
  --bg:        #FFFFFF;   /* page background */
  --ink:       #0F1419;   /* twitter gray-900 text */
  /* --maroon/--mustard/--link are the app's one accent color (green
     by default). Light-mode value is green-700 #15803D, which clears
     4.5:1 on white (5.31:1); dark/dim mode override below uses the
     brighter green-600 #16A34A, since against a near-black background
     that reads at a strong 5.7–7.3:1 while staying visually lighter/
     more legible than the green-700 shade would there. */
  --maroon:    #0B6FE0;   /* accent — brand blue, clears 4.5:1 AA on white */
  --maroon-dk: #0857B3;   /* accent hover/active */
  --maroon-tint: rgba(11,111,224,.10); /* accent hover wash */
  --mustard:   #0B6FE0;   /* secondary accent, kept same as maroon for one accent color */
  --mustard-dk:#0857B3;
  --cream-card:#FFFFFF;   /* card background */
  --line:      #EFF3F4;   /* hairline border — gray-100 */
  --muted:     #536471;   /* secondary text — twitter gray-500 */
  --green:     #00BA7C;   /* greentext */
  --link:      #0B6FE0;   /* matches the blue --maroon accent */
  --vis:       #7C3AED;
  /* --like was rose-500 #F43F5E, 3.67:1 on white — also below AA.
     Darkened to rose-600 (4.7:1); dark/dim mode restores the
     brighter rose-500 below for the same reason as --maroon above. */
  --like:      #E11D48;   /* rose-600 (AA on white) */
  --like-tint: rgba(225,29,72,.10);
  /* --share and --save are the two new semantic action colors: the
     Share button lights up blue, Save/Bookmark lights up yellow/amber,
     independent of the swappable accent above so they read the same
     everywhere. */
  --share:      #1D9BF0;  /* share — classic "link" blue */
  --share-tint: rgba(29,155,240,.12);
  --save:       #EAB308;  /* save/bookmark — amber-500 (AA on white) */
  --save-tint:  rgba(234,179,8,.14);
  /* Repost always lights up purple, independent of the swappable
     --maroon accent above — same "fixed semantic color" idea as
     --share/--save, so a reposted card reads the same regardless of
     which accent theme (blue/green/red/purple/orange) is active. */
  --repost:      #7C3AED; /* violet-600 (AA on white) */
  --repost-tint: rgba(124,58,237,.12);
  --hover-bg:  #F7F9F9;   /* row hover wash */
  --div:       #EFF3F4;   /* used by a couple of inline hr styles */
  --input-bg:  #FFFFFF;   /* form field / card-on-card background */
  --border-input: #CFD9DE; /* form field / outline-button border */
  --translucent-bg: rgba(255,255,255,.85); /* sticky/backdrop-blur headers */
  /* Rounded across the whole app — form fields, cards, dropdowns,
     avatars-on-square-slots, etc. all pull from these three sizes
     instead of each having its own one-off radius, so "make the
     corners less square" is a single token change instead of hunting
     down every element that looks like an edit-profile text field. */
  --r-sm: 12px;
  --r-md: 18px;
  --r-lg: 24px;
  --shadow-card: none;
  --shadow-card-hov: none;
  --shadow-pop:  0 12px 32px rgba(15,20,25,.18), 0 2px 8px rgba(15,20,25,.08);
  color-scheme: light; /* tells the browser to render native form
    controls (select dropdowns, date/time pickers, scrollbars) in a
    light palette — overridden to `dark` below for Dim/Lights out,
    otherwise those browser-native bits ignore our theme entirely
    and can render with black-on-dark text. */
}

/* ── THEMES — "Default" (light) is the :root above; Dim and Lights out
   (dark) are opted into via data-theme on <html>, set by settings.js
   (chooseTheme()) and persisted to localStorage, applied pre-paint by
   a tiny inline script in every page's <head> to avoid a flash. Accent
   (--maroon/--link) and --like revert to their original, brighter
   values here — against these near-black surfaces the brighter shades
   read at 5.9–7.6:1, better than the light-mode-safe darker shades
   would (which drop to ~2.8–4.5:1 on dark backgrounds). --green stays
   constant across themes, same as X's greentext does. ── */
html[data-theme="dim"] {
  --bg:#15202B; --ink:#F7F9F9; --cream-card:#15202B; --line:#38444D;
  --muted:#8B98A5; --hover-bg:#1E2732; --input-bg:#1E2732; --div:#38444D;
  --border-input:#38444D; --translucent-bg:rgba(21,32,43,.85);
  --maroon:#1185FE; --maroon-dk:#3B9EFF; --mustard:#1185FE; --mustard-dk:#3B9EFF; --link:#1185FE;
  --like:#F43F5E;
  --maroon-tint: rgba(17,133,254,.16); --like-tint: rgba(244,63,94,.16);
  --share:#3BA9F5; --share-tint: rgba(59,169,245,.18);
  --save:#FBBF24; --save-tint: rgba(251,191,36,.18);
  color-scheme: dark;
}
html[data-theme="dark"] {
  --bg:#000000; --ink:#E7E9EA; --cream-card:#000000; --line:#2F3336;
  --muted:#71767B; --hover-bg:#0B0B0B; --input-bg:#000000; --div:#2F3336;
  --border-input:#2F3336; --translucent-bg:rgba(0,0,0,.85);
  --maroon:#1185FE; --maroon-dk:#3B9EFF; --mustard:#1185FE; --mustard-dk:#3B9EFF; --link:#1185FE;
  --like:#F43F5E;
  --maroon-tint: rgba(22,163,74,.16); --like-tint: rgba(244,63,94,.16);
  --share:#3BA9F5; --share-tint: rgba(59,169,245,.18);
  --save:#FBBF24; --save-tint: rgba(251,191,36,.18);
  color-scheme: dark;
}

/* ── ACCENT COLOR — Settings → Appearance lets people swap the app's
   one accent color (buttons, links, active states — everything that
   reads var(--maroon)/var(--mustard)/var(--link)) between 5 options.
   Applied via data-accent on <html>, set by settings.js (chooseAccent())
   and persisted to localStorage, applied pre-paint by the same inline
   <head> script that handles the theme, to avoid a flash. "Blue" (the
   Bluesky-style brand blue) is the default and needs no override —
   it's just the :root values above.
   Light-mode values below are darkened from the original palette so
   every accent clears 4.5:1 on a white background (green and orange
   were previously 3.3:1 and 2.8:1 — both failed AA); the dark/dim
   overrides beneath restore the original, brighter shades since those
   already had strong contrast against near-black surfaces. */
html[data-accent="blue"] {
  --maroon:#0B6FE0; --maroon-dk:#0857B3; --maroon-tint:rgba(11,111,224,.10);
  --mustard:#0B6FE0; --mustard-dk:#0857B3; --link:#0B6FE0;
}
html[data-accent="green"] {
  --maroon:#15803D; --maroon-dk:#166534; --maroon-tint:rgba(21,128,61,.10);
  --mustard:#15803D; --mustard-dk:#166534; --link:#15803D;
}
html[data-accent="red"] {
  --maroon:#DC2626; --maroon-dk:#B91C1C; --maroon-tint:rgba(220,38,38,.10);
  --mustard:#DC2626; --mustard-dk:#B91C1C; --link:#DC2626;
}
html[data-accent="purple"] {
  --maroon:#7C3AED; --maroon-dk:#6D28D9; --maroon-tint:rgba(124,58,237,.10);
  --mustard:#7C3AED; --mustard-dk:#6D28D9; --link:#7C3AED;
}
html[data-accent="orange"] {
  --maroon:#C2410C; --maroon-dk:#9A3412; --maroon-tint:rgba(194,65,12,.10);
  --mustard:#C2410C; --mustard-dk:#9A3412; --link:#C2410C;
}
html[data-theme="dim"][data-accent="blue"],  html[data-theme="dark"][data-accent="blue"]  { --maroon:#1185FE; --maroon-dk:#3B9EFF; --mustard:#1185FE; --mustard-dk:#3B9EFF; --link:#1185FE; --maroon-tint:rgba(17,133,254,.16); }
html[data-theme="dim"][data-accent="green"], html[data-theme="dark"][data-accent="green"] { --maroon:#16A34A; --maroon-dk:#22C55E; --mustard:#16A34A; --mustard-dk:#22C55E; --link:#16A34A; --maroon-tint:rgba(22,163,74,.16); }
html[data-theme="dim"][data-accent="red"],   html[data-theme="dark"][data-accent="red"]   { --maroon-tint:rgba(220,38,38,.16); }
html[data-theme="dim"][data-accent="purple"],html[data-theme="dark"][data-accent="purple"]{ --maroon-tint:rgba(124,58,237,.16); }
html[data-theme="dim"][data-accent="orange"],html[data-theme="dark"][data-accent="orange"]{ --maroon:#F97316; --maroon-dk:#FB923C; --mustard:#F97316; --mustard-dk:#FB923C; --link:#F97316; --maroon-tint:rgba(249,115,22,.16); }

* { margin:0; padding:0; box-sizing:border-box; }
/* Safety net: every icon in this app is an inline <svg> with only a
   viewBox (no width/height attributes), sized entirely by CSS at
   each call site. Any spot that forgets a sizing rule falls back to
   the browser's unconstrained default for a dimensionless replaced
   SVG — ~300x150px — which reads as a giant broken icon blown up
   over the page (e.g. the New group/channel picker, or the "new
   message" sheet, before their specific rules existed/loaded). This
   low-specificity default keeps any such icon at a sane size; every
   real icon rule elsewhere in this file is more specific and wins
   over it as normal. */
svg { width:1em; height:1em; }

/* ── KILL THE MOBILE TAP-HIGHLIGHT FLASH — Safari/Chrome on mobile
   paint their own translucent blue/gray rectangle over whatever you
   tap (any <a>, <button>, or anything with a click handler) unless
   this is explicitly turned off. Set on every element (not just
   buttons/links) since taps can land on a span/div inside them too
   and still trigger the browser's default. This is purely a "don't
   paint the OS highlight" switch — it doesn't touch our own
   :active/oc-bounce feedback below, which is what should show
   instead. ── */
html { -webkit-tap-highlight-color:transparent; }
*, *::before, *::after { -webkit-tap-highlight-color:transparent; }

/* ── SUBTLE MOTION — a couple of small, reusable keyframes used across
   the app for "this just appeared" (feed rows/cards) and "this was
   just toggled on" (like/bookmark/repost) feedback. Kept short and
   low-amplitude on purpose so it reads as responsiveness, not as
   decoration. Anyone with prefers-reduced-motion set gets none of
   this (or the view-transitions/skeleton-pulse animations already
   in this file) — every animation/transition collapses to effectively
   instant below. ── */
@keyframes oc-fade-in { from { opacity:0; transform:translateY(4px); } to { opacity:1; transform:translateY(0); } }
@keyframes oc-pop { 0% { transform:scale(1); } 40% { transform:scale(1.22); } 100% { transform:scale(1); } }

/* ── BUTTON PRESS BOUNCE — a tiny, deliberately understated "press"
   animation applied to every button-like control on click (wired up
   via a single delegated listener in common.js, so it also reaches
   buttons that get inserted into the page later, like post actions).
   The scale delta is small (4% down, 2% overshoot) and the whole
   thing is over in ~200ms, so it reads as tactile feedback rather
   than a flashy bounce. Folds into the prefers-reduced-motion rule
   above like every other animation in this file. ── */
@keyframes oc-btn-bounce { 0% { transform:scale(1); } 45% { transform:scale(.96); } 100% { transform:scale(1); } }
.oc-bounce { animation:oc-btn-bounce .18s ease-out; }

/* ── INSTANT PRESS-DOWN — fires the moment a finger/cursor goes down,
   before the click (and the oc-bounce release animation above) even
   fires, so there's zero-latency feedback that a tap registered.
   Selector intentionally matches the delegated bounce listener in
   common.js (button/role=button/inputs/accent swatches/real links),
   minus the inline @mention, #hashtag, and URL links inside post
   bodies — those stay plain text links, not "buttons", so they don't
   flex mid-sentence. touch-action:manipulation on the same set drops
   the ~300ms tap delay/double-tap-zoom mobile browsers otherwise add,
   so taps feel immediate. Any control with its own bespoke :active
   rule elsewhere in this file (higher specificity, e.g. .act:active,
   .sidebar-post-btn:active) keeps using that instead of this default. ── */
button, [role="button"], input[type="submit"], input[type="button"], .accent-swatch,
a[href]:not(.body-link):not(.body-mention):not(.body-hashtag) {
  touch-action:manipulation;
}
button:not(:disabled):active, [role="button"]:active, input[type="submit"]:active, input[type="button"]:active, .accent-swatch:active,
a[href]:not(.body-link):not(.body-mention):not(.body-hashtag):active {
  transform:scale(.96);
}
/* ── "glass" press feedback — a soft frosted sheen that fills a
   control the instant it's tapped. The `inset 0 0 0 100vmax` trick
   floods the box with a translucent wash that's automatically
   clipped to whatever border-radius the element has, so it always
   reads as a soft glass tint on the control's own pill/circle shape.
   Scoped to *actual buttons/pills* only (real <button>s, submit
   inputs, and anything explicitly marked role="button") — deliberately
   NOT the broad a[href] selector above, since most links on this app
   (post cards, avatars, usernames, nav rows) have no border-radius of
   their own and would've shown this as a hard-edged gray rectangle
   instead of a glassy pill, which is exactly the "box" this is meant
   to avoid. Buttons with their own bespoke :active rule elsewhere
   (.act, the dark solid CTAs) layer their own tinted version instead
   of this neutral default. ── */
button:not(:disabled):active, [role="button"]:active, input[type="submit"]:active, input[type="button"]:active, .accent-swatch:active {
  box-shadow: inset 0 0 0 100vmax rgba(120,130,140,.14);
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration:.001ms !important; animation-iteration-count:1 !important;
    transition-duration:.001ms !important; scroll-behavior:auto !important;
  }
}

/* ── SMOOTH CROSS-PAGE NAVIGATION ──
   This is a classic multi-page app (real <a href> navigations, full
   document loads) rather than a client-side router, so the browser's
   built-in Cross-Document View Transitions are the right tool for
   "smooth navigation" here: same-origin link clicks crossfade instead
   of hard-cutting to a blank white page while the next document loads.
   Supported in Chromium-based browsers; everywhere else this rule is
   simply ignored and navigation behaves exactly as before. The
   `@view-transition` opt-in itself lives once, at the very top of this
   file — this section just tunes how the cross-fade actually moves.

   Opacity ONLY — deliberately no scale/translate here. #m-topbar,
   #m-tabbar and #m-fab are position:fixed and get swept up into this
   same root snapshot; a scale on the root (an earlier version of this
   rule used one) stretches those fixed bars along with the page, so
   for the ~250ms the two snapshots overlap you see the outgoing bar
   and incoming bar sitting at very slightly different sizes/positions
   — which reads as a flickering double nav bar, not a smooth
   transition. Plain opacity crossfades both snapshots in place with
   nothing to misalign, so the bars just sit still. Kept short (180ms)
   so it reads as quick and responsive rather than a slow dissolve. */
::view-transition-old(root), ::view-transition-new(root) {
  animation-duration: .18s;
  animation-timing-function: ease;
}

html { scroll-behavior:auto; }
/* Stops the browser's own full-page pull-to-refresh from ever firing
   on mobile Chrome/PWA — initPullToRefresh() in common.js (below)
   replaces it with an in-page refresh of just the posts. */
html, body { overscroll-behavior-y:contain; }

body {
  background:var(--bg);
  font-family:-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  font-size:15px;
  color:var(--ink);
  line-height:1.35;
  min-height:100vh;
  /* JS-driven fallback fade for browsers that ignore @view-transition
     above (see wirePageLeaveFade in common.js, which only wires this
     up when the browser lacks native support) — toggled by adding
     .oc-page-leaving to <html> right before navigating away. */
  transition:opacity .07s ease;
}
html.oc-page-leaving body { opacity:0; }

/* Safety net: nothing on the page should ever be wider than the
   viewport, but a too-wide row (e.g. a cramped post action row on a
   narrow phone) used to force the whole page into horizontal scroll,
   which on mobile shows up as content sheared off the right edge on
   first paint. This clips that instead of letting it drag the page
   sideways — nested horizontal scrollers (board-scroller, tables,
   the mobile repost sheet) keep scrolling normally since this only
   affects the outermost box. */
/* overflow-y:auto is written explicitly (not left to the browser to
   infer from overflow-x:hidden alone) — that inference is correct per
   spec and is what makes vertical scrolling work here at all, but a
   couple of older WebKit-based in-app browsers (some Android WebView
   builds in particular) have been seen getting it wrong and treating
   the y-axis as 'visible' instead. Spelling it out removes the
   guesswork on those. -webkit-overflow-scrolling gives old iOS Safari
   (<13) momentum/inertia scrolling on the root scroller — modern iOS
   ignores it safely.
   IMPORTANT: this lives on <html> ONLY, not <body>. Setting matching
   overflow on both used to make html AND body into two separate,
   ambiguous scroll containers stacked on top of each other — harmless
   for plain page scrolling, but it's exactly what broke wheel-scroll
   reliably reaching a position:fixed descendant like #sidebar on a
   number of real desktop browser/OS combinations (the fixed element's
   own overflow:auto box, plus two possible document scrollers above
   it, left the browser's "which ancestor actually owns this wheel
   event" resolution ambiguous). <body> is left with its default,
   unset overflow (effectively visible) so there is exactly one
   scrolling element on the page: document.scrollingElement, i.e.
   <html>. #sidebar handles its own internal scrolling independently
   via its own overflow-y:auto — see its rule below. */
html { overflow-x:hidden; overflow-y:auto; max-width:100vw; -webkit-overflow-scrolling:touch; }

a { color:var(--link); text-decoration:none; }
a:hover { color:var(--maroon-dk); text-decoration:underline; }
a:visited.pb-link { color:var(--vis); }

::selection { background:var(--maroon-tint); color:var(--ink); }

a:focus-visible, button:focus-visible, input:focus-visible,
textarea:focus-visible, select:focus-visible, .bl:focus-visible, .br:focus-visible {
  outline: 2px solid var(--maroon);
  outline-offset: 1px;
  /* was a hardcoded 2px radius, which squared off the outline on every
     pill/circle control (the "box around the like/share button" issue) —
     `inherit` lets the outline follow whatever radius the element
     already has (999px pill, 50% circle, etc.) instead of fighting it. */
  border-radius:inherit;
}
/* Post action buttons (like/reply/share/save/repost) get their own
   deliberate glass press-effect below instead of the default focus
   ring, so keyboard focus doesn't double up with it. */
.act:focus-visible { outline:none; }

.onigiri { display:inline-block; flex:none; width:1em; height:1em; }
/* ── LOGO MARK — img-based & logo, theme-swapped: a self-contained
   light-mode mark (white squircle, black &) and a transparent
   dark-mode mark (white silhouette) stacked in the same slot, toggled
   purely with CSS so there's no flash/JS needed for the in-page logo
   (the favicon, which can't be styled with CSS, is swapped by
   updateFavicon() in common.js instead — see applyTheme()). ── */
.logo-mark { display:none; width:100%; height:100%; object-fit:contain; }
.logo-mark-light { display:block; }
html[data-theme="dim"] .logo-mark-light, html[data-theme="dark"] .logo-mark-light { display:none; }
html[data-theme="dim"] .logo-mark-dark, html[data-theme="dark"] .logo-mark-dark { display:block; }

/* ═══════════════════════════════════════════════════════════════
   APP SHELL — #hdr is the fixed left sidebar (Twitter nav column),
   pinned to the viewport so it never moves while scrolling.
   #main is the flex row of [center column, right column].
   ═══════════════════════════════════════════════════════════════ */
.xshell {
  display:flex;
  width:100%;
  max-width:1265px;
  margin:0 auto;
  align-items:flex-start;
}

/* ── LEFT SIDEBAR ──
   position:fixed (not sticky) — sticky still lets the sidebar drift
   by a pixel or two in some browsers depending on how the sticky
   containing block/ancestor stacking resolves; fixed pins it to the
   viewport with zero ambiguity, so it truly never moves while the
   page scrolls. Because fixed elements are pulled out of normal
   flow, "left" is computed to land exactly where the in-flow sidebar
   used to sit — .xshell centers itself at max-width:1265px via
   margin:0 auto, so its own left edge is max(0, (viewport-1265)/2);
   the sidebar mirrors that. #main picks up a matching margin-left
   below so its content starts exactly where it used to. */
#hdr {
  width:275px;
  flex:none;
  position:fixed;
  top:0;
  left:max(0px, calc(50% - 632.5px));
  height:100vh;
  display:flex;
  flex-direction:column;
  justify-content:space-between;
  padding:8px 12px;
  border-right:1px solid var(--line);
  box-shadow:none;
  background:var(--bg);
  color:var(--ink);
  z-index:50;
}
#hdr .sb-top { display:flex; flex-direction:column; gap:2px; }

.logo {
  display:flex; align-items:center; gap:10px;
  font-family:inherit; font-size:19px; font-weight:800; color:var(--ink);
  text-decoration:none; padding:10px 8px; border-radius:999px; width:fit-content;
  transition:background .15s ease, filter .15s ease;
}
.logo:hover { background:var(--maroon-tint); text-decoration:none; color:var(--ink); }
.logo:active { background:var(--maroon-tint); filter:brightness(.93); }
.logo .badge {
  width:36px; height:36px; border-radius:50%;
  display:flex; align-items:center; justify-content:center; flex:none;
}
.logo .badge .logo-mark { width:26px; height:26px; }
.logo em { font-style:normal; color:var(--maroon); }
.logo .wordmark { white-space:nowrap; }

#hdr nav { display:flex; flex-direction:column; align-items:stretch; gap:2px; margin-top:6px; }
#hdr nav a {
  display:flex; align-items:center; gap:16px; color:var(--ink); text-decoration:none;
  font-size:19px; font-weight:500; padding:10px 12px; border-radius:999px;
  transition:background .15s ease, filter .15s ease; position:relative;
}
#hdr nav a:hover { background:var(--hover-bg); text-decoration:none; color:var(--ink); }
/* Press state reuses the same hover wash instead of an untransitioned
   inset box-shadow — the old box-shadow trick had no transition of
   its own, so it snapped on and off instantly on every tap, which is
   the "ugly" flash. Backing it with `background` (already smoothly
   transitioned above) plus a touch of `filter` darkening gives a
   soft, symmetric fade in both directions, matching how Twitter/X's
   own nav rows ease into and out of their press state. */
#hdr nav a:active { background:var(--hover-bg); filter:brightness(.93); }
#hdr nav a:visited { color:var(--ink); }
#hdr nav a.cur { font-weight:800; }
#hdr nav a .navicon { width:24px; height:24px; flex:none; position:relative; display:flex; }
#hdr nav a .navicon svg { width:100%; height:100%; fill:none; stroke:currentColor; stroke-width:1.8; stroke-linecap:round; stroke-linejoin:round; transition:transform .18s cubic-bezier(.34,1.56,.64,1), fill .12s ease; }
#hdr nav a.cur .navicon svg { fill:currentColor; transform:scale(1.08); }
.navbadge { margin-left:auto; background:var(--like); color:#fff; font-size:11px; font-weight:800; line-height:1; border-radius:999px; padding:3px 6px; flex:none; }
#hdr nav a .navicon .navbadge {
  position:absolute; top:-6px; right:-9px; margin-left:0; padding:1px 5px; font-size:10px; line-height:1.3;
  box-shadow:0 0 0 2px var(--bg);
}

/* "More" — flyout for Settings/Rules, styled like the other nav rows
   but as a <button> (it opens .acct-menu rather than navigating). */
.navmore-btn {
  display:flex; align-items:center; gap:16px; width:100%; background:none; border:none; text-align:left;
  color:var(--ink); font-family:inherit; font-size:19px; font-weight:500; padding:10px 12px; border-radius:999px;
  cursor:pointer; transition:background .15s ease, filter .15s ease;
}
.navmore-btn:hover { background:var(--hover-bg); }
.navmore-btn:active { background:var(--hover-bg); filter:brightness(.93); }
.navmore-btn.cur { font-weight:800; }
.navmore-btn .navicon { width:24px; height:24px; flex:none; }
.navmore-btn .navicon svg { width:100%; height:100%; fill:none; stroke:currentColor; stroke-width:1.8; stroke-linecap:round; stroke-linejoin:round; }
/* The "More" flyout reuses .acct-menu's box/animation/outside-click
   machinery wholesale (see auth.js). #more-wrap sits near the bottom
   of the nav list (just above the Post button), so opening *down*
   from it — the original approach — ran the menu off the bottom of
   the viewport with no room left to show it. Springing it *up* from
   the button instead (the same direction the account card's own menu
   already opens) keeps the whole thing on-screen. */
#more-wrap { position:relative; }
#more-wrap .acct-menu { bottom:calc(100% + 4px); }
#more-wrap.open .acct-menu { transform:translateY(0) scale(1); }
.navmore-menu a, #more-menu a { display:flex; align-items:center; gap:12px; }
.navmore-menu a svg, #more-menu a svg { width:18px; height:18px; fill:none; stroke:currentColor; stroke-width:1.8; flex:none; }

/* Big rounded "Post" pill under the nav items, X-style */
.sidebar-post-btn {
  display:flex; align-items:center; justify-content:center; gap:8px;
  width:100%; margin-top:14px; border:none; border-radius:999px;
  background:var(--maroon); color:#fff; font-family:inherit; font-weight:800; font-size:16px;
  padding:13px; cursor:pointer; text-decoration:none;
  box-shadow:none;
  transition:background-color .15s ease;
}
.sidebar-post-btn svg { width:18px; height:18px; flex:none; }
.sidebar-post-btn:hover { background:var(--maroon-dk); text-decoration:none; color:#fff; }
.sidebar-post-btn:active { background:var(--maroon-dk); opacity:.9; }

/* auth-area sits at the bottom of the sidebar, Twitter-profile-button style */
.auth-area { display:block; margin-top:auto; padding-top:8px; }
.auth-area a { color:var(--maroon); text-decoration:none; font-size:15px; font-weight:700; }
.auth-area a:hover { text-decoration:underline; }
.auth-area .divider { display:none; }
.auth-cta { display:flex; flex-direction:column; gap:8px; padding:4px; }
.auth-cta a {
  display:block; text-align:center; border-radius:999px; padding:9px 14px; font-size:15px; font-weight:700;
}
.auth-cta a.cta-primary { background:var(--maroon); color:#fff; }
.auth-cta a.cta-primary:hover { background:var(--maroon-dk); text-decoration:none; }
.auth-cta a.cta-ghost { border:1px solid var(--border-input); color:var(--ink); }
.auth-cta a.cta-ghost:hover { background:var(--hover-bg); text-decoration:none; }

.acct { position:relative; display:block; }
.acct-btn {
  display:flex; align-items:center; gap:10px; background:none; border:none; width:100%;
  cursor:pointer; color:var(--ink); font-family:inherit;
  font-size:15px; font-weight:700; padding:8px; border-radius:999px; text-align:left;
}
.acct-btn:hover { background:var(--hover-bg); }
.acct-btn .acct-txt { display:flex; flex-direction:column; min-width:0; flex:1; }
.acct-btn .acct-name { font-weight:800; font-size:14.5px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.acct-btn .acct-handle { font-weight:400; font-size:13.5px; color:var(--muted); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.acct-btn .acct-dots { flex:none; width:18px; height:18px; color:var(--ink); }
.acct-btn .acct-dots svg { width:100%; height:100%; }
.acct-menu {
  position:absolute; left:0; bottom:56px; z-index:50;
  background:var(--cream-card); border:1px solid var(--line); border-radius:var(--r-sm); min-width:220px;
  box-shadow:var(--shadow-pop); overflow:hidden;
  opacity:0; visibility:hidden; pointer-events:none;
  transform:translateY(6px) scale(.97); transform-origin:bottom left;
  transition:opacity .13s ease, transform .13s cubic-bezier(.2,.8,.2,1), visibility 0s linear .13s;
}
.acct.open .acct-menu {
  opacity:1; visibility:visible; pointer-events:auto; transform:translateY(0) scale(1);
  transition:opacity .15s ease, transform .15s cubic-bezier(.2,.8,.2,1);
}
.acct-menu a, .acct-menu button {
  display:block; width:100%; text-align:left; background:none; border:none;
  font-family:inherit; font-size:14px; font-weight:700;
  color:var(--ink); text-decoration:none; padding:12px 16px; cursor:pointer;
  border-bottom:1px solid var(--line);
}
.acct-menu a:last-child, .acct-menu button:last-child { border-bottom:none; }
.acct-menu a:hover, .acct-menu button:hover { background:var(--hover-bg); }

/* ── TABS — sticky "For you / Following" header of the center column.
   Pill/segmented-control styling: the two tabs live inside one rounded
   "track" and the active tab is a solid filled pill that slides between
   them, same modern language as the mobile dock (#m-tabbar) and drawer
   CTAs elsewhere in this file, instead of the old flat underline. ── */
#board-hdr {
  position:sticky; top:0; z-index:5;
  background:var(--translucent-bg); backdrop-filter:blur(8px); -webkit-backdrop-filter:blur(8px);
  border-bottom:1px solid var(--line);
  display:flex; align-items:center; justify-content:space-between; gap:10px;
  padding:10px 14px;
}
.xtabs {
  display:flex; position:relative; flex:1; min-width:0; gap:2px;
  background:var(--hover-bg); border:1px solid var(--line);
  border-radius:999px; padding:4px;
}
/* The sliding filled pill itself — one absolutely-positioned layer
   behind the tab labels, moved/resized in JS (see positionTabIndicator()
   in board.js) to exactly match whichever tab is active. Doing this
   as a single element that animates its transform/width, instead of
   styling each .xtab.active individually, is what actually produces
   the "pill slides between them" motion the old inset-outline version
   only described in a comment but never built. */
.xtabs-indicator {
  position:absolute; top:4px; left:4px; bottom:4px; width:0;
  background:var(--maroon); border-radius:999px;
  transform:translateX(0); z-index:0; pointer-events:none;
  transition:transform .28s cubic-bezier(.2,.8,.2,1), width .28s cubic-bezier(.2,.8,.2,1);
}
.xtabs-indicator.no-anim { transition:none; }
.xtab {
  flex:1; background:none; border:none; cursor:pointer; font-family:inherit;
  font-size:14.5px; font-weight:700; color:var(--muted); padding:9px 0;
  border-radius:999px; position:relative; z-index:1;
  transition:color .18s ease;
}
.xtab:hover { color:var(--ink); }
/* Explore/Search tab row has 5 items (Explore/Trending/News/Sports/
   Entertainment) instead of the usual 2-3, so the shared flex:1 sizing
   above squeezes every label into an equal-width slot and "Entertainment"
   overflows straight past the pill's rounded edge on anything narrower
   than ~650px (mobile in particular). Give this one row content-sized,
   non-wrapping tabs inside a horizontally-scrollable track instead, so
   labels stay intact and reachable by a swipe rather than clipping. */
#search-tabs.xtabs {
  overflow-x:auto; scrollbar-width:none; -ms-overflow-style:none;
}
#search-tabs.xtabs::-webkit-scrollbar { display:none; }
#search-tabs .xtab { flex:none; padding:9px 14px; white-space:nowrap; }
/* Fallback fill for the active tab — the sliding maroon pill
   (.xtabs-indicator) only exists in the DOM on the home page
   (index.html) and is only ever positioned by board.js's
   positionTabIndicator(), which targets that one element by id.
   Every other page that reuses the plain .xtabs/.xtab markup
   (Explore/Search, Lists, Followers/Following, a List's
   Posts/Members/Followers) never got an indicator element or any
   JS wiring for one, so .xtab.active previously only set
   `color:#fff` with nothing behind it — white text on the plain
   light-mode track, invisible. Giving the active tab its own
   background directly fixes every one of those pages regardless
   of whether an indicator is present; on the home page (which DOES
   have a working sliding indicator) the more specific
   `body[data-page="home"] #board-hdr .xtab.active` rule further
   down still wins and switches this back off in favor of the
   underline treatment, so nothing changes there. */
.xtab.active { color:#fff; background:var(--maroon); }
.xtab.active:hover { background:var(--maroon-dk); }
.xtabs-plus {
  flex:none; width:38px; height:38px; display:flex; align-items:center; justify-content:center;
  background:var(--hover-bg); border:1px solid var(--line); border-radius:999px;
  color:var(--muted); cursor:pointer; transition:background-color .15s ease, color .15s ease, border-color .15s ease;
}
.xtabs-plus:hover { color:var(--maroon); background:var(--maroon-tint); border-color:transparent; }
.xtabs-plus svg { width:17px; height:17px; }

/* ── SHOW N NEW POSTS PILL — Twitter's "refresh" affordance ── */
.show-new-pill {
  display:block; text-align:center; padding:12px 16px; font-size:15px; font-weight:700;
  color:var(--maroon); text-decoration:none; border-bottom:1px solid var(--line);
  background:var(--cream-card); transition:background-color .12s ease;
}
.show-new-pill:hover { background:var(--maroon-tint); text-decoration:none; }

/* Bottom-of-feed marker the infinite-scroll IntersectionObserver
   watches to trigger the next page — invisible, just needs height so
   it can scroll into view before the user hits the literal bottom. */
.feed-sentinel { height:1px; }

/* ── POST FORM (inline composer, Twitter-style) — compact by default
   (single slim row: avatar + placeholder, like Bluesky's "What's up?"
   bar) and expands to the full toolbar/audience-picker once you tap
   in or start typing. See pf-body focus/blur wiring in board.js.
   Same floating rounded-card treatment as the "Post your reply" box
   (.rfm) below — one consistent radius whether collapsed or expanded,
   not a stretched stadium/pill shape once the audience chip and
   toolbar make it taller than one line. ── */
#pf-wrap { max-width:none; margin:10px 12px 14px; padding:0; }
/* Composer card: a real elevated card (rounded corners + soft shadow)
   instead of the flat hairline-bordered square it used to be — reads
   as an inviting, tappable surface at rest, then lifts slightly and
   picks up the accent border once expanded, so the "start typing"
   state change is felt, not just inferred from a color swap. */
#pf-box {
  background:var(--cream-card); border:1px solid var(--line); border-radius:22px;
  box-shadow:0 1px 2px rgba(15,20,25,.04); overflow:visible; padding:16px 18px; cursor:text;
  transition:padding .15s ease, border-color .15s ease, background-color .15s ease, box-shadow .15s ease, transform .15s ease;
}
#pf-box:not(.pf-expanded):hover {
  border-color:color-mix(in srgb, var(--maroon) 35%, var(--line));
  box-shadow:0 4px 16px rgba(15,20,25,.08);
  /* No `transform` here (was translateY(-1px)) — a transform on this
     box makes it a CSS containing block for any position:fixed
     descendant, which includes the reply-audience picker's mobile
     bottom sheet (.ga-dd, see its @media(max-width:520px) rules).
     Mobile browsers apply :hover on tap and keep it "stuck" until the
     next tap elsewhere, so tapping the "Who can reply?" pill inside
     this card triggered this rule and left the sheet + its dark
     backdrop resolving position:fixed against this small card instead
     of the real viewport — landing it as a stray floating box under
     the tab bar rather than a full-screen sheet pinned to the bottom.
     Same failure mode already fixed for .pc's entrance animation
     above; the box-shadow/border-color lift alone is enough feedback
     without needing transform. */
}
#pf-box.pf-expanded {
  border-color:var(--maroon); padding:18px 18px 12px;
  box-shadow:0 8px 24px rgba(15,20,25,.1), 0 0 0 1px var(--maroon);
}
.pf-row { display:flex; gap:14px; align-items:center; }
#pf-box.pf-expanded .pf-row { align-items:flex-start; }
.pf-avatar {
  flex:none; width:46px; height:46px; border-radius:50%; overflow:hidden; display:block;
  background:var(--hover-bg); box-shadow:0 0 0 2px var(--card-bg, var(--cream-card)), 0 0 0 3px var(--line);
}
.pf-avatar img { width:100%; height:100%; object-fit:cover; display:block; }
.pf-col { flex:1; min-width:0; }
/* Collapsed by default: no toolbar, no audience pill, no bottom
   border under the row — just the slim avatar + placeholder line.
   #pf-box.pf-expanded (added on focus/typing, removed on blur when
   empty — see board.js) reveals all of it again. */
.pf-toolbar, #pf-ga-slot { display:none; }
#pf-box.pf-expanded .pf-toolbar { display:flex; align-items:center; justify-content:space-between; margin-top:10px; padding-top:12px; border-top:1px solid var(--line); }
/* The floating global-compose and reply modals (.gc-toolbar) reuse the
   same .pf-toolbar class as the home-feed inline composer, but they
   aren't nested inside #pf-box.pf-expanded — so without this they
   inherited display:none from the rule above and the whole toolbar
   (media/GIF/poll/emoji/schedule icons AND the Post/Reply submit
   button, which lives in the same row) was invisible any time you
   opened the "What's happening?" composer or a reply box. */
.gc-toolbar { display:flex !important; align-items:center; justify-content:space-between; }
#pf-box.pf-expanded #pf-ga-slot { display:block; }
.pf-icons { display:flex; align-items:center; gap:2px; }
.pf-ic {
  width:34px; height:34px; border-radius:50%; border:none; background:none; cursor:pointer;
  color:var(--maroon); display:inline-flex; align-items:center; justify-content:center;
  transition:background-color .15s ease;
}
.pf-ic svg { width:19px; height:19px; display:block; }
.pf-ic:hover:not(:disabled) { background:var(--maroon-tint); }
.pf-ic:active:not(:disabled) { background:var(--maroon-tint); filter:brightness(.92); }
.pf-ic:disabled { color:#9EC8DE; cursor:default; }

input[type="text"], input[type="email"], input[type="password"], textarea, select {
  font-family:inherit; font-size:15px; border-radius:var(--r-sm);
  border:1px solid var(--border-input); padding:8px 10px; background:var(--input-bg); width:100%; color:var(--ink);
}
input[type="text"]:focus, input[type="email"]:focus, input[type="password"]:focus, textarea:focus, select:focus { border-color:var(--maroon); outline:none; box-shadow:0 0 0 1px var(--maroon); }
/* Compact single line by default (Bluesky-style "What's up?" bar);
   board.js's focus/input handlers add .pf-expanded and grow this to
   its old 56px+ auto-growing height once you actually start typing. */
/* height must fit padding + one full line at this font-size (box-sizing:
   border-box means padding eats into the height budget) — too short and
   the browser reserves a scrollbar track that clips the placeholder text,
   which is the "What's happening?" getting cut off with a gray sliver on
   the right. overflow:hidden is belt-and-suspenders so no scrollbar can
   ever appear even if a browser rounds line-height up a hair. */
#pf-box textarea { height:40px; min-height:40px; resize:none; border:none; padding:8px 0; font-size:16px; line-height:1.4; overflow:hidden; overflow-y:hidden; transition:height .15s ease; }
#pf-box.pf-expanded textarea { height:56px; min-height:56px; font-size:19px; }
.rfm textarea { border:none; padding:5px 0; font-size:17px; line-height:1.4; overflow-y:hidden; }
#pf-box textarea:focus, .rfm textarea:focus { box-shadow:none; }
#pf-box textarea::-webkit-scrollbar, .rfm textarea::-webkit-scrollbar, .cx-emoji-search::-webkit-scrollbar { display:none; }

.pf-note { font-size:11.5px; color:var(--muted); display:block; margin-top:2px; }

.pf-btn, .rfm-row input[type="submit"], .modal-btn {
  font-family:inherit; font-size:14px; font-weight:700;
  background:var(--maroon); color:#fff; border:none; border-radius:999px;
  padding:7px 18px; cursor:pointer; box-shadow:0 1px 4px rgba(0,0,0,.15);
  transition:background .12s ease, opacity .1s ease, transform .1s ease, box-shadow .12s ease;
}
.pf-btn:hover, .rfm-row input[type="submit"]:hover, .modal-btn:hover { background:var(--maroon-dk); box-shadow:0 2px 8px rgba(0,0,0,.22); }
.pf-btn:active, .rfm-row input[type="submit"]:active, .modal-btn:active { background:var(--maroon-dk); opacity:.85; transform:scale(.97); }
.pf-btn:disabled, .rfm-row input[type="submit"]:disabled { opacity:0.5; cursor:default; }
.f-note { font-size:11px; color:var(--muted); text-align:left; padding:8px 0 0; }

input[type="file"] { font-size:13px; color:var(--muted); max-width:100%; }
input[type="file"]::file-selector-button {
  font-family:inherit; font-weight:700; font-size:13px;
  background:var(--maroon-tint); color:var(--maroon); border:none; border-radius:999px; padding:6px 14px; margin-right:8px; cursor:pointer;
}
input[type="file"]::file-selector-button:hover { background:var(--maroon); color:#fff; }

select {
  appearance:none; -webkit-appearance:none;
  /* neutral mid-gray works on light AND dark surfaces — a fixed dark
     fill here (as before) went near-invisible against a dark <select>
     background in Dim/Lights out mode. */
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23536471'/%3E%3C/svg%3E");
  background-repeat:no-repeat; background-position:right 10px center; padding-right:26px;
}

.fp { margin-top:8px; }
.fp img, .fp video { max-width:160px; max-height:120px; border:1px solid var(--line); border-radius:16px; display:block; }
.rm-f { font-size:12px; color:var(--maroon); cursor:pointer; text-decoration:underline; display:inline-block; margin-top:4px; }

/* ── SECTION BAR ── */
.sec-bar {
  background:none; color:var(--ink); font-weight:800; font-size:19px;
  padding:12px 16px; margin-bottom:0; display:flex; align-items:baseline; gap:8px;
  border:none; border-bottom:1px solid var(--line); border-radius:0; font-family:inherit;
  position:sticky; top:0; background-color:var(--translucent-bg); backdrop-filter:blur(8px); z-index:5;
}
.sec-bar .sub { font-weight:400; font-size:13px; color:var(--muted); }
.sec-bar h1 { font:inherit; font-weight:inherit; margin:0; color:inherit; }
.sec-bar a.back { color:var(--maroon); font-size:14px; font-weight:700; margin-left:auto; text-decoration:none; }
.sec-bar a.back:hover { text-decoration:underline; }

/* ── MAIN LAYOUT ── */
#main { display:flex; gap:0; padding:0; max-width:none; margin:0 0 0 275px; align-items:flex-start; flex:1; min-width:0; }
#feed { flex:1 1 600px; max-width:600px; min-width:0; border-left:1px solid var(--line); border-right:1px solid var(--line); min-height:100vh; }
/* position:sticky, in normal flex flow (not fixed/detached) — pins
   the sidebar to the viewport top exactly like position:fixed did,
   but because it stays part of the document's normal scroll flow, a
   mouse wheel/trackpad/touch gesture anywhere over it is guaranteed
   to land on either its own overflow-y:auto box or the single page
   scroller (<html>) — there's no separate, easily-bypassed scroll
   container for the browser to fail to route the gesture to, which is
   what made the sidebar's own content stop responding to scrolling on
   some desktop browser/OS combinations under the old position:fixed
   setup. align-self:flex-start keeps it from stretching to #main's
   full row height (the default for a flex item) so height:100vh +
   overflow-y:auto only ever scrolls the sidebar's own content, never
   the whole row. */
#sidebar { width:350px; flex:none; min-width:0; padding:8px 16px; position:sticky; align-self:flex-start; top:0; height:100vh; overflow-y:auto; scrollbar-width:none; z-index:5; }
#sidebar::-webkit-scrollbar { display:none; }

/* ── WHO TO FOLLOW (right column, Twitter-style suggestions box) ── */
.who-box { background:var(--hover-bg); border-radius:var(--r-lg); padding:4px 0 6px; margin-bottom:16px; }
.who-box .t-lbl { margin-bottom:0; }
.who-row { display:flex; align-items:center; gap:10px; padding:10px 16px; text-decoration:none; color:var(--ink); transition:background-color .12s ease; animation:oc-fade-in .2s ease both; }
.who-row:hover { background:rgba(15,20,25,.03); text-decoration:none; }
.who-row .avatar { flex:none; }
.who-row-txt { display:flex; flex-direction:column; min-width:0; flex:1; }
.who-row-name { font-size:14px; font-weight:800; color:var(--ink); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.who-row-handle { font-size:13px; color:var(--muted); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.who-follow-btn {
  flex:none; font-size:13px; font-weight:800; padding:6px 14px; border-radius:999px;
  background:var(--ink); color:var(--bg); border:none; cursor:pointer; transition:background-color .12s ease, filter .12s ease, transform .1s ease;
}
.who-follow-btn:hover { filter:brightness(.85); }
.who-follow-btn:active { transform:scale(.95); box-shadow:inset 0 0 0 100vmax rgba(255,255,255,.18); }
.who-follow-btn:disabled { opacity:.5; cursor:default; }
.who-box .show-more { display:block; padding:12px 16px; font-size:15px; color:var(--link); text-decoration:none; border-radius:0 0 var(--r-lg) var(--r-lg); }
.who-box .show-more:hover { background:rgba(15,20,25,.03); text-decoration:none; }
.who-box .no-t { padding:4px 16px 12px; }

/* ── COMMUNITY GUIDE (communities.html explainer — collapsible accordion,
   styled after .who-box: same --hover-bg card + --r-lg radius, real
   margin-top so it reads as its own block below the communities list
   instead of a flush-against-it wall of text) ── */
.comm-guide-title { font:inherit; font-weight:800; font-size:19px; color:var(--ink); margin:28px 16px 10px; }
.comm-guide { background:var(--hover-bg); border-radius:var(--r-lg); margin:0 16px 16px; overflow:hidden; }
.comm-guide-item { border-top:1px solid var(--line); }
.comm-guide-item:first-child { border-top:none; }
.comm-guide-item summary {
  list-style:none; cursor:pointer; display:flex; align-items:center; justify-content:space-between; gap:12px;
  padding:14px 16px; font-size:15px; font-weight:700; color:var(--ink); transition:background-color .12s ease;
}
.comm-guide-item summary::-webkit-details-marker { display:none; }
.comm-guide-item summary:hover { background:rgba(15,20,25,.03); }
.comm-guide-item summary::after {
  content:''; flex:none; width:8px; height:8px; margin-top:-3px;
  border-right:2px solid var(--muted); border-bottom:2px solid var(--muted);
  transform:rotate(45deg); transition:transform .15s ease;
}
.comm-guide-item[open] summary::after { margin-top:3px; transform:rotate(-135deg); }
.comm-guide-item[open] summary { border-bottom:1px solid var(--line); }
.comm-guide-body { padding:2px 16px 16px; }
.comm-guide-body p { font-size:14.5px; line-height:1.5; color:var(--ink); margin:0; }
.comm-guide-body b { font-weight:700; }

/* ── COMMUNITIES — sidebar box rows, browse list, and single-community page ── */
.comm-avatar {
  flex:none; width:40px; height:40px; border-radius:50%; display:flex; align-items:center; justify-content:center;
  background:var(--maroon); color:#fff; font-weight:800; font-size:16px; overflow:hidden;
}
.comm-avatar-plus { background:var(--hover-bg); color:var(--maroon); border:1.5px dashed var(--maroon); }
.comm-avatar-plus svg { width:18px; height:18px; stroke:currentColor; fill:none; stroke-width:2.2; }
.comm-create-row { display:flex; align-items:center; gap:10px; padding:10px 16px; text-decoration:none; color:var(--ink); transition:background-color .12s ease; }
.comm-create-row:hover { background:rgba(15,20,25,.03); text-decoration:none; }
.comm-create-row .who-row-name { color:var(--maroon); }
.comm-joined-btn { background:none; color:var(--muted); border:1px solid var(--line); }
.comm-joined-btn:hover { background:rgba(220,38,38,.08); color:#dc2626; border-color:rgba(220,38,38,.4); }

.comm-list { display:flex; flex-direction:column; }
.comm-list .comm-row { border-bottom:1px solid var(--line); padding:14px 16px; }
.comm-list .comm-avatar { width:48px; height:48px; font-size:18px; }
.comm-list .avatar { width:48px; height:48px; }
.comm-list .who-row-name { font-size:15px; }
.comm-list .comm-desc { font-size:13px; color:var(--muted); margin-top:2px; display:block; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }

/* ── PAGE-NUMBER PAGER — shared by any 10-per-page list (Communities
   browse, Lists browse): Prev/Next arrows + numbered pages with
   first/last/current/±1-neighbor kept and gaps collapsed to "…". ── */
.comm-pager { display:flex; align-items:center; justify-content:center; gap:4px; padding:16px; flex-wrap:wrap; }
.comm-pager-btn {
  flex:none; min-width:32px; height:32px; padding:0 6px; border-radius:var(--r-sm); border:none; background:none;
  font-family:inherit; font-size:14px; font-weight:700; color:var(--ink); cursor:pointer;
  display:flex; align-items:center; justify-content:center; transition:background-color .12s ease;
}
.comm-pager-btn:hover:not(:disabled) { background:var(--hover-bg); }
.comm-pager-btn:disabled { color:var(--muted); opacity:.4; cursor:default; }
.comm-pager-btn.active { background:var(--ink); color:var(--bg); }
.comm-pager-btn svg { width:16px; height:16px; }
.comm-pager-ellipsis { flex:none; min-width:32px; height:32px; display:flex; align-items:center; justify-content:center; color:var(--muted); font-size:14px; }

/* ── COMMUNITY ABOUT TAB — Community Info / Rules / Moderators,
   styled after an X Community's About panel. ── */
.comm-about-section { padding:16px; border-bottom:1px solid var(--line); }
.comm-about-section h3 { font-size:17px; font-weight:800; color:var(--ink); margin:0 0 10px; }
.comm-about-section-hdr { display:flex; align-items:center; justify-content:space-between; margin-bottom:2px; }
.comm-about-section-hdr h3 { margin-bottom:0; }
.comm-about-desc { font-size:15px; line-height:1.4; color:var(--ink); margin:0 0 14px; white-space:pre-wrap; word-break:break-word; }
.comm-about-fact { display:flex; align-items:flex-start; gap:12px; font-size:14px; color:var(--ink); margin-bottom:14px; }
.comm-about-fact:last-child { margin-bottom:0; }
.comm-about-fact svg { flex:none; width:20px; height:20px; stroke:var(--muted); margin-top:1px; }
.comm-about-fact strong { font-weight:700; display:block; }
.comm-about-fact-sub { color:var(--muted); font-size:13px; }
.comm-about-fact a { color:var(--link); text-decoration:none; font-weight:700; }
.comm-about-fact a:hover { text-decoration:underline; }
.comm-about-empty { font-size:14px; color:var(--muted); margin:0; }
.comm-about-add { background:none; border:none; color:var(--link); font-size:13px; font-weight:700; cursor:pointer; padding:4px 6px; border-radius:10px; }
.comm-about-add:hover { background:var(--hover-bg); text-decoration:underline; }

.comm-danger-section { border-bottom:none; }
.comm-danger-section h3 { color:#dc2626; }
.comm-danger-section .comm-about-fact-sub { display:block; margin:0 0 12px; }

.comm-rules-list { list-style:none; margin:0; padding:0; counter-reset:comm-rule; }
.comm-rule-row { display:flex; align-items:flex-start; gap:12px; padding:10px 0; border-bottom:1px solid var(--line); }
.comm-rule-row:last-child { border-bottom:none; }
.comm-rule-num { flex:none; width:24px; height:24px; border-radius:50%; background:var(--hover-bg); color:var(--muted); font-size:12px; font-weight:800; display:flex; align-items:center; justify-content:center; counter-increment:comm-rule; }
.comm-rule-num::before { content:counter(comm-rule); }
.comm-rule-body { flex:1; min-width:0; }
.comm-rule-title { font-size:14.5px; font-weight:800; color:var(--ink); }
.comm-rule-desc { font-size:13.5px; color:var(--muted); margin-top:2px; line-height:1.4; }
.comm-row-remove { flex:none; background:none; border:none; color:var(--muted); cursor:pointer; font-size:12px; width:24px; height:24px; border-radius:50%; display:flex; align-items:center; justify-content:center; }
.comm-row-remove:hover { background:rgba(220,38,38,.08); color:#dc2626; }

.comm-mods-list { display:flex; flex-direction:column; }
.comm-mod-row { padding:8px 0; }
.comm-mod-row .who-follow-btn { flex:none; }

.comm-inline-form { background:var(--hover-bg); border-radius:var(--r-md); padding:12px; margin:0 0 12px; }
.comm-inline-form input[type="text"], .comm-inline-form textarea {
  width:100%; box-sizing:border-box; margin-bottom:8px; background:var(--bg);
  border:1px solid var(--line); border-radius:12px; padding:8px 10px; font:inherit; color:var(--ink); text-align:center;
}
.comm-inline-form textarea { resize:vertical; min-height:44px; }
.comm-inline-form-actions { display:flex; justify-content:flex-end; }
.comm-mod-search-row { padding:8px 4px; }
#cam-results:empty { display:none; }

/* Create-community modal — same .modal shell as the delete/quote/gif
   modals above, just a little wider to fit the description textarea. */
/* max-height + flex column: without this the modal (which inherits
   `overflow:hidden` from the base .modal rule, not a scrollbar) just
   grows as tall as its content — fine for the name/description steps,
   but the rules step's list is unbounded (up to 20 rules), so once
   there are enough of them the whole dialog becomes taller than the
   viewport and, because nothing scrolls, the "Next"/"Create
   community" button in .cc-nav is pushed out of the visible/tappable
   area with no way to reach it. Capping the modal's own height and
   letting only .cc-step-body scroll internally (below) keeps the
   dots/header and the nav buttons always on-screen regardless of how
   many rules or moderators have been added. */
/* Single-screen create-community modal — same markup and classes on
   mobile and desktop; the shared .modal-bg/.modal shell already
   centers and caps width responsively (max-width:92vw), so there is
   no separate mobile layout to keep in sync with this one. */
.modal.cc-modal { width:420px; padding-bottom:0; max-height:88vh; display:flex; flex-direction:column; }
.cc-head { display:flex; align-items:center; justify-content:space-between; padding:16px 16px 0; flex:none; }
.cc-head h2 { margin:0; padding:0; border-bottom:none; }
.cc-head .modal-close { position:static; }
.cc-step-body { padding:0 16px; min-height:120px; flex:1 1 auto; overflow-y:auto; overflow-x:hidden; }
.cc-priv-row { display:flex; align-items:center; justify-content:space-between; padding:12px 2px; border-top:1px solid var(--line); margin-top:4px; }
.cc-priv-title { font-weight:700; font-size:14px; color:var(--ink); }
.cc-priv-sub { font-size:12px; color:var(--muted); }
.cc-switch { width:40px; height:22px; border-radius:99px; background:var(--line); border:none; position:relative; flex:none; cursor:pointer; padding:0; transition:background-color .15s ease; }
.cc-switch::after { content:""; position:absolute; width:18px; height:18px; background:#fff; border-radius:50%; top:2px; left:2px; transition:left .15s ease; box-shadow:0 1px 2px rgba(0,0,0,.2); }
.cc-switch.on { background:var(--maroon); }
.cc-switch.on::after { left:20px; }
.cc-advanced-toggle { display:block; width:100%; text-align:left; background:none; border:none; border-top:1px solid var(--line); padding:12px 2px; margin-top:4px; font-weight:700; font-size:13.5px; color:var(--maroon); cursor:pointer; }
.cc-advanced-toggle .cc-hint { color:var(--muted); font-weight:400; }
.cc-advanced-toggle:hover { color:var(--maroon-dk); }
.cc-modal .cc-step-body label { font-size:15px; font-weight:800; color:var(--ink); display:block; margin:0 0 8px; }
.cc-modal .cc-step-body input[type="text"], .cc-modal .cc-step-body textarea {
  width:100%; box-sizing:border-box; margin:0 0 8px; background:var(--bg);
  border:1px solid var(--line); border-radius:12px; padding:9px 10px; font:inherit; color:var(--ink); text-align:center;
}
.cc-step-body textarea { resize:vertical; }
.cc-modal .comm-inline-form input[type="text"], .cc-modal .comm-inline-form textarea { margin:0 0 8px; width:100%; }
.cc-step-hint { font-size:12.5px; color:var(--muted); margin:6px 0 0; line-height:1.4; }
.cc-nav { display:flex; align-items:center; gap:10px; padding:16px; border-top:1px solid var(--line); margin-top:16px; flex:none; }
.cc-modal > h2, .cc-modal > .errmsg { flex:none; }
.cc-nav-back { background:none; border:1px solid var(--line); color:var(--ink); border-radius:999px; padding:8px 16px; font-weight:700; cursor:pointer; }
.cc-nav-back:hover { background:var(--hover-bg); }
.cc-nav-next { flex:1; margin:0 !important; width:auto !important; }

.cc-banner-wrap {
  position:relative; height:120px; border-radius:var(--r-md); overflow:visible;
  background:var(--banner-img, linear-gradient(135deg,#BEE3F8,#E6D9F7)) center/cover no-repeat;
  margin-bottom:40px;
}
.cc-banner-pick {
  position:absolute; top:50%; left:50%; transform:translate(-50%,-50%); width:36px; height:36px;
  display:flex; align-items:center; justify-content:center; cursor:pointer;
  background:rgba(15,20,25,.55); color:#fff; border-radius:50%; transition:background-color .12s ease;
}
.cc-banner-pick:hover { background:rgba(15,20,25,.7); }
.cc-banner-pick svg { width:18px; height:18px; }
.cc-avatar-wrap { position:absolute; left:16px; bottom:-32px; width:72px; height:72px; }
.cc-avatar-preview {
  display:flex; align-items:center; justify-content:center; width:72px; height:72px; border-radius:50%;
  border:4px solid var(--cream-card); background:var(--hover-bg); color:var(--muted);
  font-size:24px; font-weight:800; overflow:hidden;
  /* border above is var(--cream-card), which equals the modal's own
     background in dark themes (#000 on #000) — invisible, so the
     circle had no visible edge and read as a shapeless blob with the
     pick icon floating on it. .ep-avatar (edit-profile) already
     solves this with a subtle outline independent of theme; mirror
     that here. */
  box-shadow:0 0 0 1px var(--line);
  /* Without border-box, the 4px border was added OUTSIDE the 72px
     content box, rendering this at 80x80 while .cc-avatar-wrap and
     the .cc-avatar-pick overlay (which fills the wrap via inset:0)
     stayed at 72x72 — an 8px size mismatch between the circle and
     its own icon overlay. */
  box-sizing:border-box;
}
.cc-avatar-preview img { width:100%; height:100%; object-fit:cover; border-radius:50%; }
.cc-avatar-pick {
  position:absolute; inset:0; display:flex; align-items:center; justify-content:center; cursor:pointer;
  background:rgba(15,20,25,.55); color:#fff; border-radius:50%; opacity:0; transition:opacity .12s ease;
}
.cc-avatar-wrap:hover .cc-avatar-pick { opacity:1; }
.cc-avatar-pick svg { width:20px; height:20px; }
/* .cc-modal .cc-step-body label (below, meant for plain text labels
   like "Add a banner & picture") is MORE specific than the bare
   .cc-avatar-pick/.cc-banner-pick rules above, since both of these
   labels live inside .cc-step-body inside .cc-modal. It was winning
   the cascade and forcing display:block + margin:0 0 8px onto them —
   killing .cc-avatar-pick's flex-centering (icon collapsed to the
   top-left) and, via the inset:0 + explicit bottom margin combo,
   shrinking its box by 8px too. .cc-banner-pick escaped visibly
   because it centers itself with a transform instead of flex, which
   that override doesn't touch. Re-declare both here with matching
   specificity so they win instead. */
.cc-modal .cc-step-body label.cc-avatar-pick,
.cc-modal .cc-step-body label.cc-banner-pick {
  display:flex; margin:0;
}

/* Single community header (community.html) */
.community-banner-wrap {
  position:relative; height:100px;
  background:var(--banner-img, linear-gradient(135deg,#BEE3F8,#E6D9F7)) center/cover no-repeat;
}
.comm-banner-pick {
  position:absolute; top:50%; left:50%; transform:translate(-50%,-50%);
  display:flex; align-items:center; justify-content:center; cursor:pointer;
  width:40px; height:40px; background:rgba(15,20,25,.55); color:#fff;
  border-radius:50%; transition:background-color .12s ease;
}
.comm-banner-pick:hover { background:rgba(15,20,25,.7); }
.comm-banner-pick svg { width:20px; height:20px; }
.community-hero { padding:16px; border-bottom:1px solid var(--line); }
.community-hero-top { display:flex; gap:14px; align-items:flex-end; justify-content:space-between; }
.community-hero .comm-avatar { width:56px; height:56px; font-size:22px; }
.comm-avatar-wrap { position:relative; flex:none; width:56px; height:56px; }
.comm-avatar-lg { width:56px; height:56px; font-size:22px; }
.comm-avatar img { width:100%; height:100%; object-fit:cover; border-radius:50%; }
.comm-avatar-pick {
  position:absolute; inset:0; display:flex; align-items:center; justify-content:center;
  border-radius:50%; background:rgba(15,20,25,.5); color:#fff; opacity:0; cursor:pointer; transition:opacity .12s ease;
}
.comm-avatar-pick:hover { opacity:1; }
.comm-avatar-pick svg { width:20px; height:20px; }
.community-hero-body { min-width:0; margin-top:12px; }
.community-hero-name { font-size:19px; font-weight:800; color:var(--ink); word-break:break-word; }
.community-hero-desc { font-size:14px; color:var(--ink); margin-top:4px; line-height:1.4; }
.community-hero-meta { font-size:13px; color:var(--muted); margin-top:8px; }
.community-hero-actions { flex:none; display:flex; align-items:center; gap:8px; }
.comm-join-btn, .comm-leave-btn {
  font-size:14px; font-weight:800; padding:8px 18px; border-radius:999px; cursor:pointer;
  transition:background-color .12s ease, filter .12s ease;
}
.comm-join-btn { background:var(--ink); color:var(--bg); border:none; }
.comm-join-btn:hover { filter:brightness(.85); }
.comm-leave-btn { background:none; color:var(--ink); border:1px solid var(--line); }
.comm-leave-btn:hover { background:rgba(220,38,38,.08); color:#dc2626; border-color:rgba(220,38,38,.4); }
.comm-join-btn:disabled, .comm-leave-btn:disabled { opacity:.6; cursor:default; }

/* ── LISTS — sidebar box rows, browse list, and single-list page.
   Square glyph avatar (instead of communities' circle) so a list
   card never reads as a person or a community at a glance. ── */
.list-avatar {
  flex:none; width:40px; height:40px; border-radius:var(--r-sm); display:flex; align-items:center; justify-content:center;
  background:var(--maroon); color:#fff; overflow:hidden;
}
.list-avatar img { width:100%; height:100%; object-fit:cover; }
.list-avatar-glyph { width:18px; height:18px; }
.list-avatar-glyph svg { width:100%; height:100%; stroke:currentColor; fill:none; stroke-width:2.2; }
.list-avatar-sm { width:36px; height:36px; }
.list-avatar-lg { width:56px; height:56px; border-radius:var(--r-md); }
.list-avatar-lg .list-avatar-glyph { width:26px; height:26px; }
.list-privacy-tag { display:inline-flex; align-items:center; gap:3px; font-size:12px; font-weight:700; color:var(--muted); vertical-align:middle; }
.list-lock-ic { width:11px; height:11px; }

.list-list { display:flex; flex-direction:column; }
.list-list .list-row { border-bottom:1px solid var(--line); padding:14px 16px; }
.list-list .list-avatar { width:48px; height:48px; }
.list-list .list-avatar-glyph { width:22px; height:22px; }
.list-list .who-row-name { font-size:15px; }

/* Deterministic accent colors for a list's square glyph avatar (only
   applied when it has no custom picture) — keeps a browse list from
   reading as a wall of identical maroon squares. */
.list-avatar-c1 { background:#F59E0B; }
.list-avatar-c2 { background:#DB2777; }
.list-avatar-c3 { background:#2563EB; }
.list-avatar-c4 { background:#059669; }
.list-avatar-c5 { background:#7C3AED; }

/* "Following" pill on a "Your Lists" row (lists.html/sidebar) — lets
   you unfollow a List you don't own without opening it. */
.list-row-unfollow { flex:none; align-self:center; background:none; color:var(--muted); border:1px solid var(--line); font-size:12px; font-weight:800; padding:5px 12px; border-radius:999px; cursor:pointer; transition:background-color .12s ease, color .12s ease, border-color .12s ease; }
.list-row-unfollow:hover { background:rgba(220,38,38,.08); color:#dc2626; border-color:rgba(220,38,38,.4); }

/* ── LIST DISCOVER SECTION (lists.html) — Twitter-style "Discover new
   Lists": public Lists the current user doesn't already own or
   follow, each with a circular Follow (+) button and a small
   overlapping avatar stack of a few of its followers. ── */
.list-sec-hdr { font-size:20px; font-weight:800; color:var(--ink); padding:14px 16px 8px; }
.list-discover-list { display:flex; flex-direction:column; }
.list-discover-row { display:flex; align-items:flex-start; gap:12px; padding:12px 16px; border-bottom:1px solid var(--line); text-decoration:none; color:var(--ink); transition:background-color .12s ease; animation:oc-fade-in .2s ease both; }
.list-discover-row:hover { background:rgba(15,20,25,.03); text-decoration:none; }
.list-discover-row .list-avatar { width:44px; height:44px; margin-top:1px; }
.list-discover-body { flex:1; min-width:0; }
.list-discover-name { display:block; font-size:15px; font-weight:800; color:var(--ink); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.list-discover-meta { display:block; font-size:13px; color:var(--muted); margin-top:1px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.list-fp-row { display:flex; align-items:center; gap:6px; margin-top:6px; min-width:0; }
.list-fp-stack { display:flex; flex:none; }
.list-fp-avatar { width:20px; height:20px; border-radius:50%; object-fit:cover; border:2px solid var(--bg); margin-left:-8px; background:var(--hover-bg); }
.list-fp-stack .list-fp-avatar:first-child { margin-left:0; }
.list-fp-txt { font-size:13px; color:var(--muted); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.list-follow-btn {
  flex:none; width:32px; height:32px; margin-top:1px; border-radius:50%; display:flex; align-items:center; justify-content:center;
  background:var(--bg); color:var(--ink); border:1px solid var(--line); cursor:pointer; transition:background-color .12s ease, transform .1s ease;
}
.list-follow-btn svg { width:16px; height:16px; stroke:currentColor; fill:none; stroke-width:2.4; }
.list-follow-btn:hover { background:var(--hover-bg); }
.list-follow-btn:active { transform:scale(.9); }
.list-follow-btn.list-following-btn { background:var(--ink); color:var(--bg); border-color:var(--ink); }
.list-discover-showmore { display:block; padding:12px 16px; font-size:15px; color:var(--link); text-decoration:none; border-bottom:1px solid var(--line); }
.list-discover-showmore:hover { background:rgba(15,20,25,.03); text-decoration:none; }

/* Follow/Following pill on the single-list page header (list.html),
   shown to a logged-in non-owner viewing a public List — same visual
   language as .comm-join-btn/.comm-leave-btn. */
.list-follow-pill, .list-following-pill {
  font-size:14px; font-weight:800; padding:8px 18px; border-radius:999px; cursor:pointer; transition:background-color .12s ease, color .12s ease, border-color .12s ease, transform .1s ease;
}
.list-follow-pill { background:var(--ink); color:var(--bg); border:none; }
.list-follow-pill:hover { filter:brightness(.85); }
.list-following-pill { background:none; color:var(--ink); border:1px solid var(--line); }
.list-following-pill:hover { background:rgba(220,38,38,.08); color:#dc2626; border-color:rgba(220,38,38,.4); }
.list-follow-pill:active, .list-following-pill:active { transform:scale(.96); }
.list-follow-pill:disabled, .list-following-pill:disabled { opacity:.6; cursor:default; }

/* Create/edit-list modal — same .modal shell as the create-community
   modal, just a little taller to fit the Private toggle row. */
.cl-modal { width:380px; }
.cl-modal input[type="text"], .cl-modal textarea { text-align:center; }

/* Add/remove-from-Lists popup, opened from a profile's "···" menu */
.alm-modal { width:380px; max-height:80vh; display:flex; flex-direction:column; }
.alm-modal h2 { flex:none; }
#alm-body { overflow-y:auto; }
.alm-list { display:flex; flex-direction:column; }
.alm-row { display:flex; align-items:center; gap:10px; padding:10px 16px; cursor:pointer; transition:background-color .12s ease; }
.alm-row:hover { background:rgba(15,20,25,.03); }
.alm-check { flex:none; width:20px; height:20px; accent-color:var(--maroon); cursor:pointer; }

/* Single-list header (list.html) — mirrors .community-hero */
.list-banner-wrap {
  position:relative; height:100px;
  background:var(--banner-img, linear-gradient(135deg,#BEE3F8,#E6D9F7)) center/cover no-repeat;
}
.list-banner-pick {
  position:absolute; top:50%; left:50%; transform:translate(-50%,-50%);
  display:flex; align-items:center; justify-content:center; cursor:pointer;
  width:40px; height:40px; background:rgba(15,20,25,.55); color:#fff;
  border-radius:50%; transition:background-color .12s ease;
}
.list-banner-pick:hover { background:rgba(15,20,25,.7); }
.list-banner-pick svg { width:20px; height:20px; }
.list-avatar-wrap { position:relative; flex:none; width:56px; height:56px; }
.list-avatar-pick {
  position:absolute; inset:0; display:flex; align-items:center; justify-content:center; cursor:pointer;
  background:rgba(15,20,25,.55); color:#fff; border-radius:var(--r-md); opacity:0;
  transition:background-color .12s ease, opacity .12s ease;
}
.list-avatar-pick:hover { opacity:1; }
.list-avatar-pick svg { width:22px; height:22px; }
.list-hero { padding:16px; border-bottom:1px solid var(--line); display:flex; gap:14px; align-items:flex-start; }
.list-hero-body { flex:1; min-width:0; }
.list-hero-name { font-size:19px; font-weight:800; color:var(--ink); word-break:break-word; display:flex; align-items:center; gap:8px; flex-wrap:wrap; }
.list-hero-desc { font-size:14px; color:var(--ink); margin-top:4px; line-height:1.4; }
.list-hero-meta { font-size:13px; color:var(--muted); margin-top:8px; }
.list-hero-owner { font-size:13px; color:var(--muted); margin-top:2px; }
.list-hero-owner a { color:inherit; font-weight:700; }
.list-hero-actions { flex:none; display:flex; align-items:center; gap:8px; }
/* The share/"···" icon buttons reuse .pc-menu-wrap (post-card menu
   component) — its default margin-left:auto is for pinning a lone
   menu to the right edge of a card; here they sit inline alongside
   the Follow/Following pill instead, same override profile.html
   already uses for its own header actions row. */
.list-hero-actions .pc-menu-wrap { margin-left:0; }
.list-edit-btn, .list-delete-btn {
  font-size:14px; font-weight:800; padding:8px 18px; border-radius:999px; cursor:pointer;
  background:none; color:var(--ink); border:1px solid var(--line); transition:background-color .12s ease, transform .1s ease;
}
.list-edit-btn:hover { background:var(--hover-bg); }
.list-delete-btn:hover { background:rgba(220,38,38,.08); color:#dc2626; border-color:rgba(220,38,38,.4); }
.list-edit-btn:active, .list-delete-btn:active { transform:scale(.96); }

/* ── ARTICLES ── */
.article-row { align-items:flex-start; }
.article-row .who-row-name { font-size:15px; white-space:normal; }
.article-row .comm-desc { white-space:normal; -webkit-line-clamp:2; display:-webkit-box; -webkit-box-orient:vertical; overflow:hidden; margin-top:2px; }
.article-row-cover { flex:none; width:64px; height:64px; border-radius:var(--r-md); overflow:hidden; background:var(--hover-bg); margin-left:8px; }
.article-row-cover img { width:100%; height:100%; object-fit:cover; }

.article-hero { padding:16px; border-bottom:1px solid var(--line); }
.article-hero-cover { width:100%; max-height:360px; object-fit:cover; border-radius:var(--r-md); margin-bottom:14px; display:block; }
.article-hero-title { font-size:26px; font-weight:800; color:var(--ink); line-height:1.25; word-break:break-word; }
.article-hero-byline { display:flex; align-items:center; gap:8px; margin-top:12px; }
.article-hero-byline .who-row-name { font-size:14px; }
.article-hero-meta { font-size:13px; color:var(--muted); margin-top:2px; }
.article-hero-actions { flex:none; display:flex; align-items:center; gap:8px; margin-left:auto; }
.article-body { font-size:17px; line-height:1.65; color:var(--ink); white-space:pre-wrap; word-wrap:break-word; padding:18px 16px 24px; }

/* Rich content rendered from an article's content_html (sanitized
   by sanitizeArticleHtml() before this ever reaches the page) —
   sits inside .article-body, so it doesn't need its own font-size
   baseline, just spacing/weight for the handful of tags the editor
   toolbar can produce. */
.article-rich { white-space:normal; }
.article-rich p { margin:0 0 14px; }
.article-rich p:last-child { margin-bottom:0; }
.article-rich b, .article-rich strong { font-weight:800; }
.article-rich h2 { font-size:22px; font-weight:800; margin:26px 0 10px; line-height:1.3; }
.article-rich h3 { font-size:19px; font-weight:800; margin:22px 0 8px; line-height:1.3; }
.article-rich blockquote {
  margin:14px 0; padding:2px 0 2px 14px; border-left:3px solid var(--maroon);
  color:var(--muted); font-style:italic;
}
.article-rich ul, .article-rich ol { margin:0 0 14px; padding-left:24px; }
.article-rich li { margin:4px 0; }
.article-rich a { color:var(--link); text-decoration:none; }
.article-rich a:hover { text-decoration:underline; }
.article-rich img { max-width:100%; height:auto; border-radius:var(--r-md); display:block; margin:16px 0; }

/* ── write/edit article rich-text editor (js/editarticle.js) ── */
.ea-toolbar {
  display:flex; align-items:center; gap:2px; padding:6px 12px; margin:0 16px;
  border:1px solid var(--border-input); border-bottom:none; border-radius:14px 14px 0 0;
  background:var(--hover-bg); flex-wrap:wrap;
}
.ea-tb-btn {
  display:inline-flex; align-items:center; justify-content:center; width:30px; height:30px;
  border:none; background:transparent; border-radius:10px; color:var(--ink); cursor:pointer; font-size:14px; font-weight:700;
}
.ea-tb-btn svg { width:16px; height:16px; fill:none; stroke:currentColor; stroke-width:2; }
.ea-tb-btn:hover { background:var(--maroon-tint); color:var(--maroon); }
.ea-tb-btn.active { background:var(--maroon-tint); color:var(--maroon); }
.ea-tb-sep { width:1px; height:20px; background:var(--line); margin:0 4px; flex:none; }
.ea-editor {
  min-height:320px; margin:0 16px; padding:14px; font-size:16px; line-height:1.6; color:var(--ink);
  border:1px solid var(--border-input); border-radius:0 0 14px 14px; background:var(--input-bg);
  outline:none; word-wrap:break-word;
}
.ea-editor:focus { border-color:var(--maroon); }
.ea-editor:empty::before { content:attr(data-placeholder); color:var(--muted); pointer-events:none; }
.ea-editor p { margin:0 0 14px; }
.ea-editor h2 { font-size:21px; font-weight:800; margin:18px 0 8px; }
.ea-editor h3 { font-size:18px; font-weight:800; margin:16px 0 6px; }
.ea-editor blockquote { margin:10px 0; padding-left:14px; border-left:3px solid var(--maroon); color:var(--muted); font-style:italic; }
.ea-editor ul, .ea-editor ol { padding-left:24px; margin:0 0 14px; }
.ea-editor img { max-width:100%; height:auto; border-radius:var(--r-md); display:block; margin:12px 0; }
.ea-editor img.ea-img-uploading { opacity:.4; }
.ea-cover-row { display:flex; gap:8px; }
.ea-cover-row input { flex:1; }
.ea-cover-upload-btn {
  flex:none; padding:0 14px; border-radius:12px; border:1px solid var(--border-input);
  background:var(--hover-bg); color:var(--ink); font-size:13px; font-weight:700; cursor:pointer;
}
.ea-cover-upload-btn:hover { background:var(--maroon-tint); color:var(--maroon); border-color:var(--maroon); }
.ea-share-field { border-top:1px solid var(--line); margin-top:6px; padding-top:14px; }
.ea-check { display:flex; align-items:center; gap:8px; font-size:14.5px; font-weight:600; color:var(--ink); cursor:pointer; }
.ea-check input { width:16px; height:16px; accent-color:var(--maroon); }
.ea-share-field textarea { margin-top:8px; min-height:60px; }

.ea-field { padding:12px 16px; display:flex; flex-direction:column; gap:6px; }
.ea-field label { font-size:12.5px; font-weight:700; color:var(--muted); }
.ea-field input, .ea-field textarea {
  border:1px solid var(--line); border-radius:var(--r-sm); padding:10px 12px; font:inherit; color:var(--ink);
  background:var(--bg); resize:vertical;
}
.ea-field textarea { line-height:1.5; min-height:280px; }
.ea-field input:focus, .ea-field textarea:focus { outline:none; border-color:var(--maroon); }
.ea-actions { padding:8px 16px 20px; }
.ea-actions .modal-btn { width:100%; margin:0; }

/* Member row on the single-list Members tab — a who-row plus a
   Remove button that only the list's owner ever sees. */
.list-member-remove { background:none; color:var(--muted); border:1px solid var(--line); font-size:13px; font-weight:800; padding:6px 14px; border-radius:999px; cursor:pointer; transition:background-color .12s ease, color .12s ease, transform .1s ease; }
.list-member-remove:hover { background:rgba(220,38,38,.08); color:#dc2626; border-color:rgba(220,38,38,.4); }
.list-member-remove:active { transform:scale(.94); }


/* ── SEARCH BOX (right column, decorative like the reference) ── */
.xsearch { display:flex; align-items:center; gap:8px; background:var(--hover-bg); border-radius:999px; padding:10px 16px; margin:4px 0 16px; }
.xsearch svg { width:16px; height:16px; color:var(--muted); flex:none; }
.xsearch input { background:transparent; border:none; outline:none; font-size:15px; width:100%; color:var(--ink); }
.xsearch input::placeholder { color:var(--muted); }

/* ── POST CARD (tweet-style) ── */
.pc {
  background:var(--cream-card); border:none; border-bottom:1px solid var(--line); border-radius:0;
  margin-bottom:0; padding:12px 16px; box-shadow:none; cursor:pointer;
  transition:background-color .12s ease;
  /* The whole card navigates on tap via a JS onclick (cardClick() in
     common.js), not a real <a href>. Without these, a tap that lands on
     any text inside the card (post body, name, timestamp) is ambiguous
     to a touchscreen — it reads as the start of a text selection, so a
     quick tap highlights the word/line (and on iOS pops the Copy/Look Up
     callout) instead of just navigating like the rest of the card
     already does. touch-action:manipulation also removes the ~300ms
     tap delay some mobile browsers add. Selection stays off for the
     whole card since none of it is meant to be press-and-hold text —
     the actual link/@mention/#hashtag/button children inside still get
     their own tap handling via cardClick()'s target check. */
  -webkit-touch-callout:none; -webkit-user-select:none; user-select:none;
  touch-action:manipulation;
  /* fill-mode intentionally omitted (was `both`) — with `both`/`forwards`,
     the animation's final `transform:translateY(0)` keyframe stays applied
     to this element forever after the .2s entrance finishes. A transform
     value other than the literal keyword `none` — even a no-op identity
     one like translateY(0) — makes the element a CSS containing block for
     any `position:fixed` descendant. Every "···" menu (.pc-menu-dd) is
     positioned by JS as position:fixed using viewport coordinates
     (see positionMenuDd() in js/common.js), so once a card's entrance
     animation finished, its menu started resolving those coordinates
     against the card's own box instead of the viewport — landing the
     dropdown far from the "···" button, worse the further down the
     feed the card sat. Dropping the fill-mode lets `transform` revert
     to `none` once the animation ends, same visual entrance, no lasting
     containing block. */
  animation:oc-fade-in .2s ease;
  /* Off-screen cards skip layout/paint/style work entirely until they
     scroll near the viewport — the single biggest lever for keeping
     long feeds/threads smooth, since a 200-post feed only ever pays
     rendering cost for the ~10 cards actually on screen. The
     intrinsic-size guess keeps the scrollbar/scroll position stable
     before a card has been measured for real. */
  content-visibility:auto;
  contain-intrinsic-size:auto 180px;
}
/* content-visibility:auto above gives the card its own containment
   box, which silently clips anything a descendant tries to paint
   outside it — including the "···" and repost popovers, which are
   position:absolute and often taller than the (short) card they sit
   in. That clipped the popover mid-item on small cards instead of
   letting it float over the content below. Suspending containment
   while a popover inside this card is open lets it paint normally,
   without giving up the off-screen perf win everywhere else. */
.pc:has(.pc-menu-wrap.open), .pc:has(.rp-menu-wrap.open) {
  content-visibility:visible;
  /* content-visibility:auto (on every .pc, including this one's
     siblings) creates a containment box, which is also a stacking
     context. Turning off containment above stops THIS card from
     clipping its own popover, but the next sibling card still has
     its own stacking context and paints after this one in DOM order
     — so it painted over the popover regardless of z-index, since
     z-index only ranks elements within the same stacking context.
     Giving this card position+z-index lifts it into the parent's
     stacking context so it wins against every sibling. */
  position:relative;
  z-index:50;
}
.pc:hover { background:var(--hover-bg); border-color:var(--line); }
.pc.flash { background:var(--maroon-tint); animation:flashfade 2.4s ease-out; }
@keyframes flashfade { 0% { background:var(--maroon-tint); } 100% { background:var(--maroon-tint); } }

/* "[Name] reposted" / "You reposted" line above a card that showed up
   in a feed/profile via a repost rather than authorship. */
.repost-banner {
  display:flex; align-items:center; gap:8px; margin:0 0 6px 28px;
  font-size:13px; font-weight:700; color:var(--muted);
}
.repost-banner svg {
  width:15px; height:15px; flex:none; fill:none; stroke:currentColor;
  stroke-width:2; stroke-linecap:round; stroke-linejoin:round;
}
.repost-banner a { color:var(--muted); text-decoration:none; }
.repost-banner a:hover { text-decoration:underline; }

/* Pinned-post banner — the pin glyph is a solid silhouette (not a
   stroked outline like the repost icon), tilted slightly and tinted
   with the accent color so it reads as a small badge rather than
   plain muted text. */
.repost-banner svg.pin-ic {
  width:14px; height:14px; fill:var(--maroon); stroke:none;
  transform:rotate(-45deg);
}

.pc-row { display:flex; gap:12px; align-items:flex-start; }
.pc-avatar-lnk { flex:none; line-height:0; }
.pc-avatar { width:40px; height:40px; }
.pc-main { flex:1; min-width:0; }

/* header line: Name  @handle · time  ···menu
   nowrap + the .ph-names wrapper below is what keeps this to one row
   no matter how long the display name or username get — previously
   this wrapped onto a second line once the two together ran out of
   room, which pushed "· time" and the "···" menu down with it (or,
   for a single very long unbroken name/handle, let them shove the
   menu button off the edge of the card entirely). */
.ph {
  display:flex; align-items:center; flex-wrap:nowrap; gap:0;
  font-size:15px; line-height:1.3; margin-bottom:1px; min-width:0;
}
/* Groups name+badge+handle so they can be measured, and truncated,
   as a unit that shrinks to fit whatever room is left after the
   always-visible timestamp and menu button take theirs. */
.ph-names { display:flex; align-items:baseline; min-width:0; overflow:hidden; flex:1 1 auto; }
.ph .nm {
  color:var(--ink); font-weight:800; text-decoration:none; margin-right:5px;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
  min-width:0; max-width:60%; flex:0 1 auto;
}

/* Verified checkmark shown next to a display name wherever it appears
   (post cards, reply headers, profile header, who-to-follow, follower
   lists). Sits inline right after the name text — see vBadge() in
   js/common.js, which is the single place this <img> gets built. */
.verified-badge { width:16px; height:16px; margin:0 5px 0 0; vertical-align:middle; flex:none; display:inline-block; align-self:center; }
.op-detail-names .verified-badge { width:18px; height:18px; margin-left:0; vertical-align:middle; flex:none; }
.uname-row .verified-badge { width:20px; height:20px; margin-left:3px; vertical-align:middle; }

/* Contextual sizing for every other spot the badge got added to (chat,
   notifications, search/follow lists, group-creation modal) — each of
   these appends the badge straight after plain name text with no
   flex+gap wrapper, so unlike .op-detail-names/.uname-row above, the
   badge's own left margin is what has to create the breathing room.
   Without a per-context size the base 16px badge also reads oversized
   next to the smaller text these rows actually use (12–15px), which is
   what made it look "off" — sitting too big and too close to the name. */
.notif-txt .verified-badge { width:16px; height:16px; margin:0 0 0 3px; vertical-align:middle; flex:none; display:inline-block; align-self:center; }
.conv-name .verified-badge { width:16px; height:16px; margin:0 0 0 3px; vertical-align:middle; flex:none; display:inline-block; align-self:center; }
.nm .verified-badge { margin-left:3px; }
.gm-sender-name .verified-badge { width:12px; height:12px; margin-left:3px; vertical-align:middle; }
.ulrow-name .verified-badge { width:14px; height:14px; margin-left:3px; vertical-align:middle; }
.gi-member-row .verified-badge { width:14px; height:14px; margin-left:3px; vertical-align:middle; }
.gcv-chip .verified-badge { width:13px; height:13px; margin:0 0 0 2px; vertical-align:middle; }
.expl-post-meta .verified-badge { width:13px; height:13px; margin-left:3px; vertical-align:middle; }
.who-row-handle .verified-badge { width:13px; height:13px; margin-left:3px; vertical-align:middle; }
.ph .nm:hover { text-decoration:underline; }
.ph .pc-handle {
  color:var(--muted); font-weight:400; margin-right:5px;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
  min-width:22px; flex:1 1 auto;
}
.ph .dt { color:var(--muted); font-weight:400; white-space:nowrap; flex:none; }
.ph .dt::before { content:"\00b7"; margin-right:5px; }
.ph .num { display:none; }

.pc-menu-wrap { position:relative; margin-left:auto; flex:none; }
/* Same "glass chip" press language as .act (like/reply/share/bookmark)
   above: a soft frosted sheen floods the circle the instant it's
   pressed, clipped to the button's own round shape via isolation +
   overflow:hidden, plus a quick icon pop when the dropdown opens.
   Pure CSS/click-driven (no touch-only handlers, no mobile media
   query), so it behaves identically with a mouse on desktop and a
   finger on mobile — same as every other .act flourish in this file. */
.pc-menu-btn {
  position:relative; isolation:isolate; overflow:hidden;
  background:none; border:none; cursor:pointer; color:var(--muted); border-radius:50%;
  width:30px; height:30px; display:inline-flex; align-items:center; justify-content:center;
  transition:background .12s ease, color .12s ease, transform .1s cubic-bezier(.2,.8,.2,1);
}
.pc-menu-btn:hover { background:var(--maroon-tint); color:var(--maroon); }
.pc-menu-btn:active { transform:scale(.90); }
.pc-menu-btn svg { width:17px; height:17px; transition:transform .18s cubic-bezier(.34,1.56,.64,1); }
.pc-menu-btn:active svg { transform:scale(.86) rotate(-8deg); }
.pc-menu-btn::before {
  content:""; position:absolute; inset:0; border-radius:inherit; z-index:-1;
  background:currentColor; opacity:0; backdrop-filter:blur(6px);
  transform:scale(.6); transition:opacity .18s ease, transform .18s cubic-bezier(.2,.8,.2,1);
}
.pc-menu-btn:active::before, .pc-menu-btn:focus-visible::before { opacity:.16; transform:scale(1); }
/* Little pop when the dropdown actually opens (menu-open class toggled
   in togglePostMenu(), common.js) — same spirit as .act.like.liked's
   oc-like-pop, just on the "···" glyph instead of the heart. */
.pc-menu-btn.menu-open svg { animation:oc-pop .3s cubic-bezier(.17,.89,.32,1.49); }
.pc-menu-dd {
  position:absolute; right:0; top:calc(100% + 4px); z-index:40; min-width:180px;
  max-width:min(280px, calc(100vw - 24px));
  background:var(--cream-card); border:1px solid var(--line); border-radius:18px;
  box-shadow:var(--shadow-pop); overflow:hidden; padding:6px;
  opacity:0; visibility:hidden; pointer-events:none;
  transform:translateY(4px) scale(.97); transform-origin:top right;
  transition:opacity .13s ease, transform .13s cubic-bezier(.2,.8,.2,1), visibility 0s linear .13s;
}
.pc-menu-wrap.open .pc-menu-dd {
  opacity:1; visibility:visible; pointer-events:auto; transform:translateY(0) scale(1);
  transition:opacity .15s ease, transform .15s cubic-bezier(.2,.8,.2,1);
}
.pc-menu-dd button, .pc-menu-dd a {
  display:block; width:100%; text-align:left; background:none; border:none; cursor:pointer;
  border-radius:12px; font-family:inherit; font-size:14px; font-weight:700; color:var(--ink);
  padding:10px 12px; text-decoration:none; box-sizing:border-box; transition:background .1s ease;
  overflow-wrap:anywhere; word-break:break-word;
}
.pc-menu-dd button:hover, .pc-menu-dd a:hover { background:var(--hover-bg); text-decoration:none; }
.pc-menu-dd button.pc-menu-danger { color:var(--like); }
.pc-menu-dd button.pc-menu-danger:hover { background:var(--like-tint); }

/* thread title — kept above the body since this board has subjects, Twitter has none */
.subj { display:block; font-weight:800; font-size:14px; color:var(--ink); margin:1px 0 3px; }

.pb { line-height:1.4; word-wrap:break-word; font-size:15px; white-space:pre-wrap; color:var(--ink); margin-top:1px; }
.gt { color:var(--green); }
.body-link, .body-mention, .body-hashtag { color:var(--link); text-decoration:none; }
.body-link:hover, .body-mention:hover, .body-hashtag:hover { text-decoration:underline; }

.pm { margin:8px 0 2px; clear:both; }
.pm .ml { font-size:10px; color:var(--muted); margin-bottom:2px; }
.pm img  {
  max-width:100%; max-height:380px; border:1px solid var(--line); border-radius:20px;
  cursor:pointer; display:block; transition:opacity .12s ease;
}
.pm img:hover { opacity:.96; }
.pm img.exp { max-height:none; }
.pm video { max-width:100%; max-height:380px; border:1px solid var(--line); border-radius:20px; display:block; }

/* action row: icon + count, evenly spread across the card width */
.acts { margin-top:10px; clear:both; display:flex; align-items:center; justify-content:space-between; gap:4px; max-width:425px; }

/* Each pill is its own little "glass chip": transparent by default,
   fills with a soft frosted tint of its own color on hover, and on
   press/tap shows a stronger frosted-glass sheen (blur + translucent
   color wash) that's always clipped to the pill's own rounded shape —
   never a hard square box, on any of the four actions. */
.act {
  position:relative; isolation:isolate; overflow:hidden;
  font-family:inherit; font-size:13px; font-weight:600;
  border:none; border-radius:999px; padding:7px 10px; cursor:pointer;
  color:var(--muted); text-decoration:none; background:none; display:inline-flex; align-items:center; gap:6px;
  transition:background .15s ease, color .15s ease, transform .1s cubic-bezier(.2,.8,.2,1);
}
.act:active { transform:scale(.90); }
.act svg { width:19px; height:19px; flex:none; fill:none; stroke:currentColor; stroke-width:1.8; stroke-linecap:round; stroke-linejoin:round; shape-rendering:geometricPrecision; transition:transform .18s cubic-bezier(.34,1.56,.64,1); }
.act.share svg, .act.bookmark svg { stroke-width:2.1; }
.act:hover { text-decoration:none; }
.act:active svg { transform:scale(.86); }

/* the frosted glass sheen — a blurred, translucent fill that pops in
   under the icon the instant you press, then fades right back out.
   Sits between the pill's own transparent background and its icon
   (z-index via `isolation:isolate` above), and is always cropped to
   the pill's border-radius:999px, so it's a soft glass capsule, not
   a box. */
.act::before {
  content:""; position:absolute; inset:0; border-radius:inherit; z-index:-1;
  background:currentColor; opacity:0; backdrop-filter:blur(6px);
  transform:scale(.6); transition:opacity .18s ease, transform .18s cubic-bezier(.2,.8,.2,1);
}
.act:hover::before { opacity:.08; transform:scale(1); }
.act:active::before, .act:focus-visible::before { opacity:.16; transform:scale(1); }

/* Comment / Like / Save — no glass-sheen background on hover/press,
   just the icon (and its own color change / pop animation) reacting. */
.act.reply::before, .act.like::before, .act.bookmark::before { display:none; }

a.act:hover, button.act.reply:hover, button.act.reply:active { color:var(--maroon); }
.act.views:hover { color:var(--maroon); }
/* the views icon is solid bars (Twitter's analytics glyph), unlike the
   outlined stroke icons the other .act buttons use — override the
   shared .act svg stroke treatment above so it renders filled. */
.act.views svg { fill:currentColor; stroke:none; }
.act.like:hover, .act.like:active { color:var(--like); }
.act.like.liked { color:var(--like); }
.act.like.liked svg { fill:var(--like); stroke:var(--like); animation:oc-like-pop .45s cubic-bezier(.17,.89,.32,1.49); }

/* ── ACTION BURST — a quick radial confetti of little dots fired from
   an action icon's center the instant it fires: like (on like, not
   unlike), save (on bookmark), repost/quote (on repost, not undo),
   share (on tap), comment (on tap) — see spawnActionBurst() in
   common.js, which colors the particles per-action via an inline
   style override on this same class. Same spirit as X/Instagram's
   tap-to-like flourish, now shared by every action instead of just
   like. Particles are fixed-position (viewport coordinates from the
   button's own getBoundingClientRect()) so they're never clipped by
   any ancestor's overflow:hidden — including .act's own, which every
   one of these icons sits inside. Self-removing: each particle's
   `animationend` listener deletes it from the DOM. Pure viewport math,
   so this looks identical on mobile and desktop — no separate mobile
   version to keep in sync. */
@keyframes oc-like-pop { 0% { transform:scale(1); } 30% { transform:scale(1.35); } 55% { transform:scale(.92); } 100% { transform:scale(1); } }
.act-burst-particle {
  position:fixed; left:0; top:0; width:6px; height:6px; margin:-3px 0 0 -3px;
  border-radius:50%; background:var(--like); pointer-events:none; z-index:9999;
  animation:oc-like-burst .55s cubic-bezier(.16,.84,.32,1) forwards;
}
@keyframes oc-like-burst {
  0% { transform:translate(0,0) scale(1); opacity:1; }
  100% { transform:translate(var(--tx), var(--ty)) scale(0); opacity:0; }
}
.act.reply.disabled, .act.reply:disabled {
  color:var(--muted); opacity:.55; cursor:not-allowed;
}
.act.reply.disabled:hover, .act.reply:disabled:hover { color:var(--muted); }
.act.reply.disabled::before, .act.reply:disabled::before { display:none; }

/* Share — always reads blue, on hover, press, and after a successful
   copy, so tapping it never looks like an unrelated brand color. */
.act.share:hover, .act.share:active, .act.share.copied { color:var(--share); }
.act.share:active svg { animation:oc-pop .28s ease; }
.act.reply:not(.disabled):active svg { animation:oc-pop .28s ease; }

/* Save/Bookmark — always reads amber/yellow, same idea. */
.act.bookmark { flex:none; }
.act.bookmark:hover, .act.bookmark:active { color:var(--save); }
.act.bookmark.bookmarked { color:var(--save); }
.act.bookmark.bookmarked svg { fill:var(--save); stroke:var(--save); animation:oc-pop .28s ease; }

/* ── repost (retweet) button + Repost/Quote dropdown ──
   Desktop: a small popover anchored above the button (Twitter-style),
   fading + scaling in from its anchor corner instead of snapping open.
   Mobile (≤640px): becomes a full-width bottom sheet with its own
   backdrop, since a 180px popover is a fiddly tap target on a phone. */
.rp-menu-wrap { position:relative; flex:none; }
.act.repost:hover, .act.repost:active { color:var(--repost); }
.act.repost.reposted { color:var(--repost); animation:oc-pop .28s ease; }
.rp-menu-dd {
  position:absolute; left:0; bottom:calc(100% + 4px); z-index:40; min-width:200px;
  background:var(--cream-card); border:1px solid var(--line); border-radius:18px;
  box-shadow:var(--shadow-pop); overflow:hidden; padding:6px;
  opacity:0; visibility:hidden; pointer-events:none;
  transform:translateY(4px) scale(.97); transform-origin:bottom left;
  transition:opacity .13s ease, transform .13s cubic-bezier(.2,.8,.2,1), visibility 0s linear .13s;
}
.rp-menu-wrap.open .rp-menu-dd {
  opacity:1; visibility:visible; pointer-events:auto; transform:translateY(0) scale(1);
  transition:opacity .15s ease, transform .15s cubic-bezier(.2,.8,.2,1);
}
.rp-menu-dd button {
  display:flex; align-items:center; gap:12px; width:100%; text-align:left; background:none; border:none; cursor:pointer;
  border-radius:12px; font-family:inherit; font-size:14.5px; font-weight:700; color:var(--ink); padding:10px 12px;
  transition:background .1s ease;
}
.rp-menu-dd button svg { width:18px; height:18px; flex:none; }
.rp-menu-dd button:hover { background:var(--hover-bg); }
.rp-menu-dd button.rp-do svg, .rp-menu-dd button.rp-undo svg {
  fill:none; stroke:currentColor; stroke-width:2; stroke-linecap:round; stroke-linejoin:round;
}
.rp-menu-dd button.rp-undo { color:var(--green); }
.rp-menu-dd button.rp-undo:hover { background:rgba(0,186,124,.08); }
.rp-menu-dd button.rp-quote:hover { background:var(--maroon-tint); color:var(--maroon); }
.rp-menu-dd-sep { height:1px; background:var(--line); margin:5px 4px; }
.rp-menu-sheet-title { display:none; }

@media (max-width: 640px) {
  .rp-menu-wrap.open::before {
    content:""; position:fixed; inset:0; background:rgba(15,20,25,.5);
    z-index:998; animation:oc-fade-in .15s ease;
  }
  .rp-menu-dd {
    position:fixed; left:0; right:0; bottom:0; top:auto; z-index:999;
    min-width:0; width:100%; max-width:100%; border-radius:24px 24px 0 0; border:none;
    padding:6px 8px calc(8px + env(safe-area-inset-bottom, 0px));
    transform:translateY(100%); transform-origin:bottom center;
    transition:transform .2s cubic-bezier(.2,.8,.2,1), opacity .2s ease, visibility 0s linear .2s;
  }
  .rp-menu-wrap.open .rp-menu-dd {
    transform:translateY(0);
    transition:transform .22s cubic-bezier(.2,.8,.2,1), opacity .15s ease;
  }
  /* Drag-handle, like a real bottom sheet, instead of the row list
     just starting flush against the top edge. */
  .rp-menu-dd::before {
    content:""; display:block; width:36px; height:4px; border-radius:999px;
    background:var(--line); margin:6px auto 4px;
  }
  .rp-menu-sheet-title { display:none; }
  .rp-menu-dd button { padding:12px 14px; font-size:15px; gap:14px; }
  .rp-menu-dd button svg { width:19px; height:19px; }
  .rp-menu-dd-sep { margin:2px 6px; }
}
/* Locks background scroll while the mobile repost sheet is open.
   html:has(...) covers <html> — the actual scrolling element — since
   the class itself is only ever toggled on <body> by the JS. */
body.oc-sheet-open,
html:has(> body.oc-sheet-open) { overflow:hidden; }

/* ── POST DETAIL (thread.html's OP) — Twitter's post-detail screen:
   bigger body text, a "9:00 PM · Aug 8, 2026 · 64.5k Views" meta line
   instead of the compact card's inline timestamp, a full-width icon
   row (with a bookmark count, unlike the compact row), and a
   Relevant/View-quotes row — all separated by hairline dividers. ── */
.op-detail { padding:12px 16px 0; border-bottom:1px solid var(--line); }
.op-detail-head { display:flex; align-items:flex-start; gap:12px; }
.op-detail-head .pc-avatar { width:48px; height:48px; }
.op-detail-names { flex:1; min-width:0; display:flex; flex-direction:column; line-height:1.3; padding-top:1px; }
.op-detail-names .op-name-line { display:flex; align-items:center; gap:3px; min-width:0; }
.op-detail-names .nm { color:var(--ink); font-weight:800; font-size:16px; text-decoration:none; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.op-detail-names .nm:hover { text-decoration:underline; }
.op-detail-names .pc-handle { color:var(--muted); font-size:15px; }
.op-detail-body { font-size:21px; line-height:1.35; word-wrap:break-word; white-space:pre-wrap; color:var(--ink); margin-top:14px; }
.op-detail-meta { font-size:15px; color:var(--muted); margin-top:16px; padding-bottom:12px; }
.op-detail-meta b { color:var(--ink); }
.op-detail-views { display:inline-flex; align-items:center; gap:5px; vertical-align:-3px; }
.op-detail-views svg { width:16px; height:16px; flex:none; }
.op-detail-divider { border-top:1px solid var(--line); }
.op-stats { display:flex; align-items:center; justify-content:space-between; padding:4px 0; margin:0 -8px; }
.op-stats .act { padding:8px; }
.op-stats .act svg { width:20px; height:20px; }
.op-relevant { display:flex; align-items:center; justify-content:space-between; font-size:15px; padding:12px 0; }
.op-relevant-btn {
  background:none; border:none; cursor:pointer; font:inherit; font-weight:800; color:var(--ink);
  display:inline-flex; align-items:center; gap:4px; padding:0;
}
.op-relevant-btn svg { width:16px; height:16px; }
.op-relevant a { color:var(--maroon); font-weight:700; text-decoration:none; }
.op-relevant a:hover { text-decoration:underline; }
.op-relevant-wrap { position:relative; }
.op-relevant-menu {
  position:absolute; top:calc(100% + 6px); left:0; z-index:20;
  background:var(--cream-card); border:1px solid var(--line); border-radius:16px;
  box-shadow:0 4px 18px rgba(0,0,0,.18); padding:6px; min-width:160px;
  flex-direction:column; gap:2px;
}
.op-relevant-menu[hidden] { display:none; }
.op-relevant-menu:not([hidden]) { display:flex; }
.op-relevant-opt {
  display:block; width:100%; text-align:left; background:none; border:none; cursor:pointer;
  font:inherit; font-size:14px; font-weight:600; color:var(--ink); padding:9px 10px; border-radius:12px;
}
.op-relevant-opt:hover { background:var(--hover-bg, rgba(127,127,127,.12)); }
.op-relevant-opt.active { color:var(--maroon); }
.op-quotes-list { border-top:1px solid var(--line); margin:0 -16px; }

/* ── quoted-post embed (shown inside a quote post's card) ── */
.qp-embed {
  border:1px solid var(--line); border-radius:var(--r-lg,24px); padding:10px 12px; margin-top:8px; cursor:pointer;
  transition:background-color .12s ease, border-color .12s ease;
}
.qp-embed:hover { background:var(--hover-bg); border-color:var(--muted); }
.qp-embed .ph { font-size:14px; margin-bottom:2px; }
.qp-embed .pb { font-size:14.5px; }
.qp-embed .pm img, .qp-embed .pm video { max-height:220px; }
.qp-embed-gone { border:1px solid var(--line); border-radius:var(--r-md); padding:10px 12px; margin-top:8px; color:var(--muted); font-size:13.5px; }

/* ── link card (Bluesky/X-style unfurl for a bare URL in a post body) ──
   Renders below the post text, one per post (first link only), and
   only when the post has no other embed of its own — see the
   one-embed-slot comment above linkCardHtml() in js/common.js. Image
   block is only present in the DOM when the target site actually has
   an og:image; a site without one just gets the text block, no empty/
   broken image area (see hydrateLinkCard()). While the preview is
   still loading (.lc-pending, before hydrateLinkCard() replaces the
   placeholder's innerHTML) the card sits empty at a fixed min-height
   so the post doesn't visibly grow once the card resolves. */
.lc-card {
  display:block; margin:8px 0 2px; clear:both;
  border:1px solid var(--line); border-radius:var(--r-lg,24px); overflow:hidden;
  transition:background-color .12s ease, border-color .12s ease;
}
.lc-card.lc-pending { min-height:80px; background:var(--hover-bg); }
.lc-card:has(.lc-link):hover { background:var(--hover-bg); border-color:var(--muted); }
.lc-link { display:block; color:inherit; }
.lc-link:hover { text-decoration:none; color:inherit; }
.lc-img { width:100%; aspect-ratio:1.91/1; background:var(--hover-bg); }
.lc-img img { width:100%; height:100%; object-fit:cover; display:block; }
.lc-text { padding:10px 12px; }
.lc-title {
  font-weight:700; font-size:14.5px; color:var(--ink); line-height:1.3;
  display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden;
}
.lc-desc {
  font-size:13px; color:var(--muted); margin-top:2px; line-height:1.35;
  display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden;
}
.lc-domain {
  display:flex; align-items:center; gap:5px; font-size:12px; color:var(--muted);
  margin-top:6px; padding-top:6px; border-top:1px solid var(--line); text-transform:lowercase;
}


/* ── article embed (a post that's "promoting" an Article — see
   articleCardHtml() in common.js) — same X/Twitter-style article
   card shape as the qp-embed above: full-bleed cover image with a
   small pill overlay, bold title + a couple lines of excerpt. ── */
.article-embed {
  border:1px solid var(--line); border-radius:var(--r-lg,24px); margin-top:8px; cursor:pointer;
  overflow:hidden; transition:background-color .12s ease, border-color .12s ease;
}
.article-embed:hover { border-color:var(--muted); }
.article-embed-media { position:relative; width:100%; aspect-ratio:2/1; background:var(--hover-bg); }
.article-embed-media img { width:100%; height:100%; object-fit:cover; display:block; }
.article-embed-badge {
  display:inline-flex; align-items:center; gap:4px; position:absolute; left:10px; bottom:10px;
  background:rgba(15,20,25,.75); color:#fff; font-size:11.5px; font-weight:700; letter-spacing:.2px;
  padding:4px 9px; border-radius:999px; backdrop-filter:blur(2px);
}
.article-embed-badge svg { width:12px; height:12px; fill:none; stroke:#fff; stroke-width:2; }
.article-embed-badge-standalone { position:static; margin:10px 0 0 10px; background:var(--hover-bg); color:var(--muted); }
.article-embed-text { padding:10px 12px 12px; }
.article-embed-title { font-size:15.5px; font-weight:800; color:var(--ink); line-height:1.3; }
.article-embed-excerpt {
  font-size:13.5px; color:var(--muted); margin-top:3px; line-height:1.4;
  overflow:hidden; text-overflow:ellipsis; display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical;
}

/* ── quote-post modal (compose a comment on top of an embedded post) ── */
.qm-body { display:flex; gap:12px; align-items:flex-start; padding:12px 16px 0; }
.qm-body textarea { height:80px; resize:none; border:none; padding:8px 0; font-size:17px; }
.qm-body textarea:focus { box-shadow:none; }
.qm-preview-wrap { padding:0 16px 4px 60px; }
.qm-foot { display:flex; align-items:center; justify-content:flex-end; gap:10px; padding:6px 16px 0; }
.qm-count { font-size:13px; color:var(--muted); font-variant-numeric:tabular-nums; }
.qm-count.qm-count-warn { color:#e0a100; }
.qm-count.qm-count-over { color:var(--like); font-weight:700; }

/* ── GLOBAL COMPOSE MODAL — opened by the sidebar/mobile "Post"
   button from any page, X-style. Built in common.js (gcModalEl()).
   Selectors use the compound ".modal.gc-modal" (not just ".gc-modal")
   so these reliably win over the generic ".modal{width:340px}" rule
   above — same class count as it, so without the extra specificity
   the two rules tie and whichever is later in the file (that one)
   wins regardless of which is "more specific-sounding" in code. ── */
.modal.gc-modal { width:600px; max-width:92vw; padding:44px 0 14px; }
/* align-items:flex-start (overriding .pf-row's default :center) pins
   the avatar to the TOP of the composer instead of vertically centering
   it against the textarea's full box — the fixed height:100px below,
   plus whatever the poll/schedule builder adds, made a plain centered
   avatar drift oddly far from the actual first line of text once
   any of that was present. Matches the same override the home feed's
   inline composer already gets once expanded (#pf-box.pf-expanded
   .pf-row), so avatar alignment is consistent everywhere. */
.gc-row { padding:0 16px; align-items:flex-start; }
.gc-row textarea { height:100px; resize:none; border:none; padding:8px 0; font-size:19px; }
.gc-row textarea:focus { box-shadow:none; }
.gc-toolbar { margin-top:0; padding:10px 16px 0; }
.gc-status { display:block; padding:2px 16px 0; font-size:11px; color:var(--muted); min-height:14px; }

/* ── REPLY AUDIENCE PICKER — the "Everyone can reply" pill above a
   composer's toolbar (see replyAudienceMenuHtml() in common.js),
   tappable to restrict a new post to "No one". Same popover-menu look
   as the repost/quote dropdown (.rp-menu-dd) for visual consistency,
   just anchored under the pill instead of the repost icon. Shared
   across the global compose modal (gc), the home feed's inline
   composer (pf) and a community's inline composer (cf) — only the id
   prefix differs. ── */
.ga-row { padding:6px 16px 10px 60px; }
.ga-wrap { position:relative; display:inline-block; }
.ga-btn {
  display:flex; align-items:center; gap:6px; padding:5px 10px 5px 8px;
  font-family:inherit; font-size:13px; font-weight:700; color:var(--maroon); background:var(--maroon-tint);
  border:none; border-radius:999px; cursor:pointer; transition:background .12s ease, opacity .12s ease;
}
.ga-btn:hover { opacity:.85; }
.ga-btn-icon { display:inline-flex; }
.ga-btn-icon svg { width:15px; height:15px; flex:none; }
.ga-btn .ga-chev { width:14px; height:14px; margin-left:-1px; opacity:.85; }
.ga-btn.ga-restricted { color:var(--muted); background:var(--hover-bg); }
.ga-dd {
  position:absolute; left:0; top:calc(100% + 4px); z-index:40; width:280px; max-width:calc(100vw - 32px);
  background:var(--cream-card); border:1px solid var(--line); border-radius:18px;
  box-shadow:var(--shadow-pop); overflow:hidden; padding:6px;
  opacity:0; visibility:hidden; pointer-events:none;
  transform:translateY(-4px) scale(.97); transform-origin:top left;
  transition:opacity .13s ease, transform .13s cubic-bezier(.2,.8,.2,1), visibility 0s linear .13s;
}
.ga-wrap.open .ga-dd {
  opacity:1; visibility:visible; pointer-events:auto; transform:translateY(0) scale(1);
  transition:opacity .15s ease, transform .15s cubic-bezier(.2,.8,.2,1);
}
.ga-dd-title { font-size:15px; font-weight:800; color:var(--ink); padding:8px 10px 6px; }
.ga-opt {
  display:flex; align-items:center; gap:12px; width:100%; text-align:left; background:none; border:none; cursor:pointer;
  border-radius:12px; font-family:inherit; padding:9px 10px; transition:background .1s ease;
}
.ga-opt:hover { background:var(--hover-bg); }
.ga-opt-icon { width:19px; height:19px; flex:none; color:var(--maroon); }
.ga-opt-txt { flex:1; min-width:0; display:flex; flex-direction:column; gap:1px; }
.ga-opt-txt b { font-size:14px; color:var(--ink); font-weight:700; }
.ga-opt-txt small { font-size:12px; color:var(--muted); font-weight:400; }
.ga-opt .ga-check { width:17px; height:17px; flex:none; color:var(--maroon); opacity:0; }
.ga-opt.active .ga-check { opacity:1; }
@media (max-width: 520px) {
  .ga-wrap.open::before {
    content:""; position:fixed; inset:0; background:rgba(15,20,25,.5); z-index:998; animation:oc-backdrop-in .15s ease;
  }
  .ga-dd {
    position:fixed; left:0; right:0; bottom:0; top:auto; z-index:999;
    width:100%; max-width:100%; border-radius:24px 24px 0 0; border:none;
    padding:6px 8px calc(8px + env(safe-area-inset-bottom, 0px));
    transform:translateY(100%); transform-origin:bottom center;
    transition:transform .2s cubic-bezier(.2,.8,.2,1), opacity .2s ease, visibility 0s linear .2s;
  }
  .ga-wrap.open .ga-dd { transform:translateY(0); transition:transform .22s cubic-bezier(.2,.8,.2,1), opacity .15s ease; }
  .ga-dd::before { content:""; display:block; width:36px; height:4px; border-radius:999px; background:var(--line); margin:6px auto 8px; }
  .ga-opt { padding:12px 14px; }
}

@media (max-width:520px) {
  /* Full-screen compose, X-app style: Close top-left, Post/Reply
     pinned top-right (pulled out of the toolbar's flow via
     position:fixed), and the icon toolbar pinned to the bottom of
     the screen — instead of everything just sitting in normal flow
     inside a height:100% box, which left a dead, disconnected-looking
     gap of empty space between the textarea and the toolbar.
     Height comes off 100dvh (the dynamic viewport unit — shrinks on
     its own when the on-screen keyboard opens, no JS needed) with
     --vvh (kept in sync with window.visualViewport by common.js) as
     a finer-grained override for browsers where it fires. Plain
     100%/100vh doesn't shrink when the keyboard opens on a lot of
     mobile browsers/WebViews, so the flex:none toolbar at the bottom
     of this column would end up sitting under the keyboard — reachable
     only by scrolling, if at all — instead of right above it like X's
     compose screen. This used to be --vvh-only, which left the icon
     row (and the whole toolbar) invisible under the keyboard on any
     browser where the visualViewport 'resize' listener never fired;
     100dvh as the guaranteed first-line fallback fixes that. */
  .modal.gc-modal {
    width:100%; max-width:100%; border-radius:0; padding-top:52px;
    height:100%; height:100dvh; height:var(--vvh, 100dvh);
    max-height:100vh; max-height:100dvh; max-height:var(--vvh, 100dvh);
    display:flex; flex-direction:column; overflow:hidden;
  }
  #gc-modal-bg.open { align-items:stretch; justify-content:stretch; }
  .modal.gc-modal .modal-close { left:14px; right:auto; }
  .modal.gc-modal .pf-btn { position:fixed; top:11px; right:14px; z-index:2; padding:8px 20px; }
  .modal.gc-modal .rpc-context { flex:none; }
  .modal.gc-modal .errmsg { flex:none; }
  /* .gc-row is the SAME element as .pf-row (class="pf-row gc-row"),
     and .pf-row sets align-items:center for the desktop/compact inline
     composer where that's correct. Left as-is here, that center rule
     also applied to this full-height flex:1 mobile row, vertically
     centering the avatar+textarea in the middle of the whole screen
     instead of pinning it under the header — the huge dead gap above
     the input, and the toolbar/Post button pushed down out of view.
     align-items:flex-start keeps the avatar+textarea pinned to the
     TOP of the (still full-height, still scrollable) row, X-app style,
     so any leftover space falls below the text where it belongs. */
  .modal.gc-modal .gc-row { flex:0 1 auto; min-height:0; max-height:100%; overflow-y:auto; align-items:flex-start; }
  .modal.gc-modal .ga-row { flex:none; }
  /* Absorbs whatever vertical space the draft + audience pill don't
     use, so the toolbar still pins to the bottom of the screen (right
     above the keyboard, X-app style) without .gc-row itself having to
     stretch+center to fill that space — see the .gc-row comment above. */
  .modal.gc-modal .gc-spacer { flex:1 1 auto; min-height:0; }
  .modal.gc-modal .gc-toolbar {
    flex:none; border-top:1px solid var(--line);
    padding:10px 16px calc(10px + env(safe-area-inset-bottom, 0px));
  }
  .modal.gc-modal .gc-status { flex:none; }
}


/* ── REPLY POPUP — opened by tapping the comment icon on a feed post
   card. Same .gc-modal shell as the global compose modal above, just
   shorter (no poll/schedule) and with a "Replying to @user" line up
   top. Built in common.js (rpcModalEl()). ── */
.modal.rpc-modal { padding-top:44px; }
.rpc-context { padding:0 16px 8px; font-size:13px; color:var(--muted); }
.rpc-context a { color:var(--link); text-decoration:none; }
.rpc-context a:hover { text-decoration:underline; }
/* Mobile full-screen layout, incl. the pinned-toolbar flex fix, comes
   from the shared ".modal.gc-modal" rules above — .rpc-modal carries
   that class too, so it doesn't need its own copy here. */

/* legacy classes still referenced by unused code paths — harmless if present */
.ai { color:var(--muted); font-size:11px; display:inline-flex; align-items:center; gap:4px; padding:4px 6px; }
.ai.flag { cursor:pointer; border-radius:999px; }
.ai.flag:hover { color:var(--maroon); background:var(--maroon-tint); }
.rp-btn { font-family:inherit; font-size:11px; font-weight:700; background:var(--maroon-tint); border:none; border-radius:999px; padding:3px 10px; cursor:pointer; color:var(--maroon); }
.rp-btn:hover { background:var(--maroon); color:#fff; }

/* Real X reply lists stay edge-to-edge with a full-width avatar column
   at every depth — replies don't march to the right as they nest, a
   thin vertical line under the avatar is what shows the conversation
   is a reply-to-a-reply. That's the look here: constant left padding,
   a connector line, and a small "Replying to @x" tag on nested ones. */
.rw { margin-top:0; }
/* animation fill-mode intentionally omitted here too — see the long
   comment on .pc above: `both`/`forwards` would permanently pin a
   non-"none" transform on this card after the entrance animation ends,
   turning it into a containing block that breaks the fixed-position
   "···" menu the same way it did on .pc. */
.rc { background:var(--cream-card); border:none; border-bottom:1px solid var(--line); border-radius:0; padding:12px 16px; margin-bottom:0; box-shadow:none; position:relative; transition:background-color .15s ease; cursor:pointer; animation:oc-fade-in .2s ease; }
.rc:hover { background:var(--hover-bg); }
.rc:active { background:var(--hover-bg); }

/* Focused-reply "detail" view (see thread.js renderConversation()) —
   clicking any comment opens it here, Twitter-style: the ancestor
   chain leading to it in compact rows, then the comment itself as a
   second, smaller "OP", then its own replies below. This bar sits
   right above that chain as an escape hatch back to the full thread. */
.thread-focus-bar { padding:10px 16px; border-bottom:1px solid var(--line); font-size:14px; }
.thread-focus-bar a { color:var(--maroon); text-decoration:none; font-weight:600; }
.thread-focus-bar a:hover { text-decoration:underline; }
#focused-reply .op-detail-body { font-size:19px; }
.rc .pc-row { position:relative; }
.rc .pc-avatar { width:40px; height:40px; }
.rc .ph { font-size:15px; }
.rc .pb { font-size:15px; }
.rc .pm img  { max-height:280px; }
.rc .pm video { max-height:280px; }
.rc .acts { max-width:340px; }
.rc-reply-tag { font-size:13px; color:var(--muted); margin:0 0 2px; }
.rc-reply-tag a { color:var(--maroon); text-decoration:none; }
.rc-reply-tag a:hover { text-decoration:underline; }

/* connector line: drawn under a reply's avatar whenever it has at
   least one child, so it visually threads down into the next reply —
   same trick X uses instead of ever-deepening indentation. */
.rc.has-children .pc-row::after {
  content:''; position:absolute; top:44px; bottom:-12px; left:19px;
  width:2px; background:var(--line);
}

/* Compact "Post your reply" row — sits right under the Relevant bar,
   above the replies list, X-style: avatar + single-line textarea that
   grows as you type, with the Reply button on the same row. Floating
   pill/rounded card (same treatment as #pf-box above) instead of a
   flush, edge-to-edge bar — margin on all sides, its own shadow, no
   straight divider lines. */
.rfm { background:var(--cream-card); border:1px solid var(--line); border-radius:22px; box-shadow:0 1px 2px rgba(15,20,25,.04); padding:12px 18px; margin:10px 12px; overflow:hidden; display:flex; gap:12px; align-items:flex-start; }
.rfm-locked { align-items:center; justify-content:center; gap:10px; color:var(--muted); font-size:14px; font-weight:600; padding:18px 16px; }
.rfm-locked svg { width:18px; height:18px; flex:none; }
.rfm-col { flex:1; min-width:0; }
.rfm input[type="text"] { margin:0 0 8px; width:100%; max-width:100%; }
.rfm textarea { display:block; margin:0; width:100%; height:40px; min-height:40px; max-height:200px; resize:none; overflow:hidden; line-height:1.3; }
.rfm input[type="file"] { margin:0; }
.rfm .fp { margin-left:0; }
.rfm-row { display:flex; gap:10px; align-items:center; margin:8px 0 0; }
.rfm-row .pf-ic:last-of-type { margin-right:auto; }
.rfm-row input[type="submit"] { margin-left:0; }

/* ── COMPOSER EXTRAS: GIF picker modal ── */
.modal.gif-modal { width:520px; max-width:92vw; padding-bottom:10px; }
.gif-search-row { display:flex; align-items:center; gap:8px; margin:0 16px 10px; padding:8px 12px; border:1px solid var(--border-input); border-radius:999px; background:var(--input-bg); }
.gif-search-row svg { width:16px; height:16px; color:var(--muted); flex:none; }
.gif-search-row input { border:none; background:none; padding:0; font-size:14px; }
.gif-search-row input:focus { box-shadow:none; }
/* The generic `.modal input[type="text"]` rule above (meant for
   plain stacked form fields directly inside a modal) also matches
   this input since it's still a descendant of `.modal` — its
   `margin:0 16px 12px; width:calc(100% - 32px)` fights the pill's
   own flex layout (padding + gap already handle spacing here),
   shoving the input out of the rounded search pill so it overlaps
   the magnifying-glass icon and placeholder text. Needs to win over
   that rule (same specificity, so scoped one level deeper here to
   be safely more specific) rather than just override the two
   properties, in case anything else generic targets input[type=text]
   later. */
.modal .gif-search-row input[type="text"], .modal .cx-emoji-search-row input[type="text"] {
  margin:0; width:auto; flex:1; min-width:0; color:var(--ink);
}
.gif-grid { display:grid; grid-template-columns:repeat(3,1fr); gap:6px; max-height:420px; overflow-y:auto; padding:0 16px; }
.gif-item { padding:0; border:none; border-radius:16px; overflow:hidden; cursor:pointer; background:var(--hover-bg); aspect-ratio:1/1; }
.gif-item img { width:100%; height:100%; object-fit:cover; display:block; }
.gif-item:hover { outline:2px solid var(--maroon); outline-offset:-2px; }
.gif-attrib { font-size:11px; color:var(--muted); text-align:center; padding:10px 0 0; }

/* ── COMPOSER EXTRAS: emoji picker — same centered-modal shell as the
   GIF picker (.modal.gif-modal) instead of a small anchored popover,
   so it can't get clipped/cut off inside the mobile full-screen
   composer the way the old position:absolute version did. ── */
.modal.emoji-modal { width:420px; max-width:92vw; padding-bottom:14px; }
.cx-emoji-grid { display:grid; grid-template-columns:repeat(7,1fr); gap:2px; max-height:320px; overflow-y:auto; padding:2px 16px 0; }
.cx-emoji-empty { grid-column:1/-1; text-align:center; font-size:12.5px; color:var(--muted); padding:14px 0; }
.cx-emoji-item { background:none; border:none; cursor:pointer; font-size:22px; padding:7px; border-radius:14px; line-height:1; }
.cx-emoji-item:hover { background:var(--hover-bg); }

/* ── COMPOSER EXTRAS: poll builder — rounded card instead of a flat
   1px square box, tinted background so it reads as its own module
   inside the composer, and a per-option remove (×) button once a
   third/fourth choice has been added (previously the only way to
   drop back to 2 options was scrapping the whole poll). ── */
.cx-poll { border:1px solid var(--line); border-radius:20px; padding:14px; margin:12px 0 0; background:var(--hover-bg); }
.cx-poll-opts { display:flex; flex-direction:column; gap:8px; }
.cx-poll-opt-row { display:flex; align-items:center; gap:8px; }
.cx-poll-opt { font-size:14px; padding:10px 14px; border-radius:999px; flex:1; min-width:0; }
.cx-poll-opt-rm {
  flex:none; width:26px; height:26px; border-radius:50%; border:none; background:none; color:var(--muted);
  cursor:pointer; display:inline-flex; align-items:center; justify-content:center; font-size:13px;
}
.cx-poll-opt-rm:hover { background:var(--line); color:var(--ink); }
.cx-poll-row { display:flex; flex-wrap:wrap; align-items:center; gap:10px; margin-top:12px; }
.cx-poll-add { background:none; border:none; color:var(--maroon); font-weight:700; font-size:13px; cursor:pointer; padding:4px 0; }
.cx-poll-add:hover { text-decoration:underline; }
.cx-poll-add:disabled { color:var(--muted); cursor:default; text-decoration:none; }
.cx-poll-row select {
  width:auto; min-width:0; flex:1 1 110px; font-size:13px; padding:7px 24px 7px 12px;
  border-radius:999px; background-color:var(--input-bg);
}
.cx-poll-remove {
  margin-left:auto; background:none; border:none; color:var(--muted); cursor:pointer; width:26px; height:26px;
  border-radius:50%; display:inline-flex; align-items:center; justify-content:center; font-size:13px;
}
.cx-poll-remove:hover { background:var(--line); color:var(--ink); }

/* ── COMPOSER EXTRAS: schedule picker — same rounded-card treatment
   as the poll builder above, and the date input can shrink instead
   of forcing the row wider than the composer on narrow screens. ── */
.cx-sched { display:flex; align-items:center; gap:8px; border:1px solid var(--line); border-radius:999px; padding:9px 14px; margin:12px 0 0; background:var(--hover-bg); }
.cx-sched[hidden], .cx-poll[hidden] { display:none; }
.cx-sched svg { width:17px; height:17px; color:var(--maroon); flex:none; }
.cx-sched input[type="datetime-local"] { border:none; background:none; padding:2px 0; font-size:13.5px; width:auto; min-width:0; flex:1; }
.cx-sched input[type="datetime-local"]:focus { box-shadow:none; }
.cx-sched-remove { background:none; border:none; color:var(--muted); cursor:pointer; width:22px; height:22px; border-radius:50%; flex:none; }
.cx-sched-remove:hover { background:var(--line); color:var(--ink); }

/* ── POLLS (rendered on a post card / thread detail) ── */
.poll-box { display:flex; flex-direction:column; gap:6px; margin-top:10px; max-width:420px; }
.poll-opt-btn {
  text-align:left; font-family:inherit; font-size:14.5px; font-weight:600; color:var(--ink);
  background:none; border:1px solid var(--maroon); border-radius:999px; padding:9px 14px; cursor:pointer; transition:background-color .12s ease;
}
.poll-opt-btn:hover { background:var(--maroon-tint); }
.poll-opt-result {
  position:relative; border-radius:999px; padding:9px 14px; font-size:14.5px; font-weight:600; color:var(--ink);
  background:var(--hover-bg); overflow:hidden; display:flex; justify-content:space-between; gap:8px;
}
.poll-opt-result.mine { outline:2px solid var(--maroon); outline-offset:-2px; }
.poll-opt-fill { position:absolute; inset:0; width:0; background:var(--maroon-tint); border-radius:999px; z-index:0; }
.poll-opt-label, .poll-opt-pct { position:relative; z-index:1; }
.poll-opt-pct { font-weight:800; }
.poll-meta { font-size:13px; color:var(--muted); margin-top:2px; }

/* ── RIGHT COLUMN: trending panel ── */
.trends-box { background:var(--hover-bg); border-radius:var(--r-lg); padding:4px 0 6px; margin-bottom:16px; }
.t-block { margin-bottom:2px; }
.t-block:last-child { margin-bottom:0; }
.t-lbl { font-weight:800; font-size:15px; color:var(--ink); background:none; padding:10px 16px 6px; border-radius:0; text-transform:none; letter-spacing:0; margin-bottom:0; }
.tbadge { display:none; }
.no-t { font-size:13px; color:var(--muted); font-style:normal; padding:4px 16px 12px; }

.tcard {
  background:none; border:none; border-radius:0; padding:10px 16px; margin-bottom:0;
  cursor:pointer; text-decoration:none; display:block; color:var(--ink); box-shadow:none;
  border-top:1px solid var(--line); transition:background-color .12s ease;
}
.tcard:first-of-type { border-top:none; }
.tcard:hover { background:rgba(15,20,25,.03); text-decoration:none; box-shadow:none; }

/* Avatar + text sit in a row, same as a real post card, so the
   snippet/stats below line up under the name instead of under the
   avatar — this is what was misaligned before. */
.tcard-row { display:flex; gap:10px; align-items:flex-start; }
.tcard-avatar { flex:none; width:34px; height:34px; }
.tcard-body { flex:1; min-width:0; }

.tcard-head { display:flex; align-items:baseline; gap:4px; flex-wrap:wrap; font-size:13px; line-height:1.3; }
.tcard-name { font-weight:800; color:var(--ink); }
.tcard-handle { color:var(--muted); font-weight:400; }
.tcard-handle::after { content:"\00b7"; margin-left:4px; color:var(--muted); }
.tcard-dt { color:var(--muted); font-weight:400; white-space:nowrap; }

.tsnip {
  font-size:13px; line-height:1.35; color:var(--muted); margin-top:2px;
  display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden; word-wrap:break-word;
}
.tsnip:empty { display:none; margin:0; }

.tmeta { display:flex; align-items:center; gap:14px; margin-top:6px; }
.tmeta-item { display:inline-flex; align-items:center; gap:4px; font-size:12px; font-weight:700; color:var(--muted); }
.tmeta-item svg { width:14px; height:14px; fill:none; stroke:currentColor; stroke-width:2; stroke-linecap:round; stroke-linejoin:round; }

/* ── EXPLORE PAGE (search.html with no query) ── */
.expl-hdr { font-weight:800; font-size:20px; color:var(--ink); padding:16px 16px 12px; }
.expl-section { border-bottom:8px solid var(--line); }
.expl-post { display:block; padding:12px 16px; border-top:1px solid var(--line); text-decoration:none; color:var(--ink); transition:background-color .12s ease; }
.expl-post:first-child { border-top:none; }
.expl-post:hover { background:var(--hover-bg); text-decoration:none; }
.expl-post-title { font-size:15px; font-weight:800; line-height:1.3; margin-bottom:8px; word-wrap:break-word; }
.expl-post-thumb { border-radius:12px; overflow:hidden; margin-bottom:8px; background:var(--hover-bg); max-height:220px; }
.expl-post-thumb img, .expl-post-thumb video { display:block; width:100%; max-height:220px; object-fit:cover; }
.expl-post-thumb-video { position:relative; }
.expl-post-thumb-play {
  position:absolute; top:50%; left:50%; transform:translate(-50%,-50%);
  width:40px; height:40px; border-radius:50%; background:rgba(15,20,25,.6); color:#fff;
  display:flex; align-items:center; justify-content:center;
}
.expl-post-thumb-play svg { width:16px; height:16px; margin-left:2px; }
.expl-post-meta { display:flex; align-items:center; gap:8px; font-size:13px; color:var(--muted); }
.expl-post-meta .avatar { width:20px; height:20px; border-radius:50%; flex:none; }
.expl-post-meta .dot { display:inline; }
.expl-post-meta .dot::before { content:"\00b7"; margin:0 2px; }
.trend-row { display:flex; align-items:center; justify-content:space-between; gap:8px; padding:12px 16px; border-top:1px solid var(--line); text-decoration:none; color:var(--ink); transition:background-color .12s ease; }
.trend-row:first-child { border-top:none; }
.trend-row:hover { background:var(--hover-bg); text-decoration:none; }
.trend-row-txt { display:flex; flex-direction:column; min-width:0; gap:2px; }
.trend-cat { font-size:13px; color:var(--muted); }
.trend-word { font-size:15px; font-weight:800; color:var(--ink); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.trend-count { font-size:13px; color:var(--muted); white-space:nowrap; flex:none; }
.expl-showmore { display:block; padding:14px 16px; font-size:15px; color:var(--link); text-decoration:none; }
.expl-showmore:hover { background:var(--hover-bg); text-decoration:none; }

/* ── NESTED REPLIES (reply-to-a-reply) ── */
.rc-children { margin:0; }
.rc-parent-tag { font-size:13px; color:var(--muted); margin:0 0 2px; }
.rc-parent-tag a { color:var(--maroon); font-weight:600; }

/* inline "reply to this comment" composer, toggled per-reply */
.rc-inline-compose { display:none; padding:10px 16px 14px; border-bottom:1px solid var(--line); background:var(--cream-card); }
.rc-inline-compose.open { display:block; }
.rc-inline-compose textarea { height:60px; resize:vertical; font-size:15px; }
.rc-inline-compose .rfm-row { margin-top:8px; }

html[data-theme="dim"] .errmsg, html[data-theme="dark"] .errmsg { background:rgba(244,63,94,.14); color:#FDA4AF; }

.spinner { display:block; text-align:center; padding:28px; font-size:14px; color:var(--muted); font-style:normal; }

/* ── PULL TO REFRESH — see initPullToRefresh() in common.js. Sits
   right above #feed-posts, collapsed to 0 height until a touch-drag
   from the very top of the page opens it up; height and spin angle
   both track the drag live, then it either snaps back (short pull)
   or locks into a spinning "loading" state while the feed refetches. */
#ptr-indicator {
  display:flex; align-items:center; justify-content:center;
  height:0; overflow:hidden; opacity:0;
  transition:height .22s cubic-bezier(.2,.8,.2,1), opacity .22s ease;
}
#ptr-indicator.ptr-dragging { transition:opacity .22s ease; }
#ptr-indicator.ptr-visible { opacity:1; }
.ptr-spinner {
  width:24px; height:24px; border-radius:50%; flex:none; margin:10px 0;
  background:conic-gradient(from 0deg, var(--maroon), rgba(255,255,255,0) 78%);
  -webkit-mask:radial-gradient(farthest-side, transparent calc(100% - 3px), #000 calc(100% - 3px));
          mask:radial-gradient(farthest-side, transparent calc(100% - 3px), #000 calc(100% - 3px));
  transform:rotate(var(--ptr-rot, 0deg));
}
#ptr-indicator.ptr-loading .ptr-spinner { animation:ptr-spin .8s cubic-bezier(.5,0,.5,1) infinite; }
@keyframes ptr-spin { to { transform:rotate(360deg); } }
html[data-theme="dim"] .ptr-spinner, html[data-theme="dark"] .ptr-spinner {
  background:conic-gradient(from 0deg, var(--maroon), rgba(0,0,0,0) 78%);
}

/* ── SKELETON LOADERS — shown the instant a page/feed starts loading,
   swapped for real cards the moment data arrives. Reads as "already
   working" instead of a blank pause, same trick X/Bluesky use instead
   of a spinner for anything list-shaped. Colors ride the same theme
   tokens as everything else, so it's correct in dim/dark for free. ── */
@keyframes skel-pulse { 0%,100% { opacity:.55; } 50% { opacity:1; } }
/* "Ghost" shimmer sweep layered on top of the opacity pulse above —
   a soft highlight band travels left-to-right across each skeleton
   shape, same effect as the loading placeholders in most chat apps,
   instead of the whole block just fading in and out in place. */
@keyframes skel-shimmer { 100% { transform:translateX(100%); } }
.skel-avatar, .skel-line, .skel-pill, .skel-profile-hdr .skel-banner, .skel-profile-hdr .skel-avatar-xl, .skel-article .skel-cover {
  position:relative; overflow:hidden;
}
.skel-avatar::after, .skel-line::after, .skel-pill::after, .skel-profile-hdr .skel-banner::after, .skel-profile-hdr .skel-avatar-xl::after, .skel-article .skel-cover::after {
  content:""; position:absolute; inset:0; transform:translateX(-100%);
  background:linear-gradient(90deg, transparent, rgba(255,255,255,.4), transparent);
  animation:skel-shimmer 1.6s ease-in-out infinite;
}
.skel-card { display:flex; gap:12px; padding:16px; border-bottom:1px solid var(--line); }
.skel-avatar { width:40px; height:40px; border-radius:50%; background:var(--line); flex:none; animation:skel-pulse 1.3s ease-in-out infinite; }
.skel-lines { flex:1; display:flex; flex-direction:column; gap:9px; padding-top:3px; }
.skel-line { height:11px; border-radius:10px; background:var(--line); animation:skel-pulse 1.3s ease-in-out infinite; }
.skel-line.w20 { width:20%; }
.skel-line.w40 { width:40%; }
.skel-line.w60 { width:60%; }
.skel-line.w90 { width:90%; }
.skel-op .skel-avatar { width:48px; height:48px; }
.skel-op .skel-line.tall { height:16px; }
.skel-line.w30 { width:30%; }
.skel-line.w50 { width:50%; }
.skel-line.w70 { width:70%; }
.skel-avatar.skel-avatar-sm { width:34px; height:34px; }
.skel-avatar.skel-avatar-md { width:50px; height:50px; }
.skel-avatar.skel-avatar-sq { border-radius:var(--r-md); }
.skel-pill { width:84px; height:32px; border-radius:999px; background:var(--line); flex:none; animation:skel-pulse 1.3s ease-in-out infinite; }
.skel-pill.sm { width:64px; height:30px; }

/* ── skeleton: profile-style hero (banner + overlapping avatar), used
   while profile.html/community.html/list.html headers load. Mirrors
   .profile-hdr's own dimensions so there's no layout jump when the
   real header swaps in. ── */
.skel-profile-hdr { padding:0 16px 14px; border-bottom:1px solid var(--line); }
.skel-profile-hdr .skel-banner { height:220px; margin:0 -16px 0; background:var(--line); animation:skel-pulse 1.3s ease-in-out infinite; }
.skel-profile-hdr .skel-avatar-xl {
  width:112px; height:112px; border-radius:50%; border:4px solid var(--bg);
  background:var(--line); animation:skel-pulse 1.3s ease-in-out infinite;
  margin-top:-62px; position:relative; z-index:1;
}
.skel-profile-hdr-top { display:flex; align-items:flex-end; justify-content:space-between; gap:10px; margin-top:14px; }
.skel-profile-hdr-lines { margin-top:16px; display:flex; flex-direction:column; gap:10px; }
@media (max-width:480px) {
  .skel-profile-hdr .skel-banner { height:180px; }
  .skel-profile-hdr .skel-avatar-xl { width:88px; height:88px; margin-top:-46px; }
}

/* ── skeleton: compact hero row (community.html / list.html), which
   use a smaller square/rounded avatar next to name+desc+meta instead
   of a full banner. ── */
.skel-hero { display:flex; gap:14px; align-items:flex-start; padding:16px; border-bottom:1px solid var(--line); }
.skel-hero .skel-avatar { width:56px; height:56px; border-radius:var(--r-md); }
.skel-hero .skel-lines { flex:1; padding-top:2px; gap:10px; }

/* ── skeleton: single-article content page (article.html) — cover
   image + title + body paragraph lines, no avatar row. ── */
.skel-article { padding:16px; display:flex; flex-direction:column; gap:12px; }
.skel-article .skel-cover { height:200px; border-radius:var(--r-md); background:var(--line); animation:skel-pulse 1.3s ease-in-out infinite; }
.skel-article .skel-line.title { height:22px; margin-top:6px; }

/* ── skeleton: settings/form pages (editprofile.html, settings.html)
   — a label bar + input-height bar, repeated per field. ── */
.skel-form { padding:16px; display:flex; flex-direction:column; gap:20px; }
.skel-field { display:flex; flex-direction:column; gap:8px; }
.skel-field .skel-line.label { height:11px; }
.skel-field .skel-line.input { height:38px; border-radius:var(--r-sm); }
.errmsg { background:#FEECEE; border:1px solid var(--like); color:#B0192B; border-radius:var(--r-sm); font-size:13px; padding:8px 12px; margin:10px 16px; }
#feed-empty { text-align:center; padding:48px 16px; font-size:15px; color:var(--muted); }

.modal-bg { display:none; position:fixed; inset:0; background:rgba(91,112,131,0.4); z-index:1000; align-items:center; justify-content:center; padding:16px 0; }
.modal-bg.open { display:flex; animation:oc-backdrop-in .15s ease both; }
/* GIF and emoji pickers are sub-panels of whichever composer opened
   them (home inline box, global "pen" compose modal, reply modal) —
   they must always paint above that composer's own .modal-bg, so
   they get a dedicated higher layer instead of relying purely on DOM
   order (see the re-append-on-open comment in openGifPicker() /
   toggleEmojiPicker() in common.js for the other half of this fix). */
#gif-modal-bg, #emoji-modal-bg { z-index:1100; }
@keyframes oc-backdrop-in { from { background:rgba(91,112,131,0); } to { background:rgba(91,112,131,.4); } }
/* scrollbar-gutter:stable reserves the scrollbar's width on both
   sides up front, so content (like the gcv-name/gcv-desc pills below)
   stays centered whether or not a scrollbar actually shows — without
   it, a modal tall enough to scroll had its right-side scrollbar eat
   into the right padding only, visually pushing centered fields a
   few px right of true-center. */
.modal { background:var(--cream-card); border:none; border-radius:24px; padding:0 0 16px; width:340px; max-width:92vw; position:relative; box-shadow:var(--shadow-pop); overflow-y:auto; overflow-x:hidden; max-height:100%; scrollbar-gutter:stable both-edges; animation:oc-modal-in .18s cubic-bezier(.2,.8,.2,1) both; }
@keyframes oc-modal-in { from { opacity:0; transform:translateY(10px) scale(.97); } to { opacity:1; transform:translateY(0) scale(1); } }
.modal h2 { font-family:inherit; font-size:17px; color:var(--ink); background:none; margin-bottom:12px; padding:16px 16px 0; border-bottom:none; font-weight:800; }

/* ── followers/following list modal ── */
.followlist-body { max-height:340px; overflow-y:auto; padding:2px 8px 6px; }
.ulrow { display:flex; align-items:center; gap:10px; padding:8px 8px; border-radius:var(--r-sm); text-decoration:none; color:var(--ink); transition:background .12s ease; }
.ulrow:hover { background:var(--hover-bg); text-decoration:none; }
.ulrow-txt { display:flex; flex-direction:column; min-width:0; }
.ulrow-name { font-size:14px; font-weight:800; color:var(--ink); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.ulrow-handle { font-size:13px; color:var(--muted); }
.modal label { font-size:12px; font-weight:700; display:block; margin:0 16px 5px; color:var(--muted); }
.modal select, .modal textarea, .modal input[type="text"] { margin:0 16px 12px; width:calc(100% - 32px); }
.modal-btn { width:calc(100% - 32px); margin:4px 16px 0; padding:9px 14px; }
.modal-close { position:absolute; top:12px; right:14px; cursor:pointer; font-size:16px; color:var(--muted); text-decoration:none; line-height:1; width:30px; height:30px; display:flex; align-items:center; justify-content:center; border-radius:50%; }
.modal-close:hover { background:var(--hover-bg); color:var(--ink); }

/* ── delete-post confirmation modal ── */
.dc-modal { width:300px; padding:20px 20px 16px; text-align:center; }
.dc-title { font-size:17px; font-weight:800; color:var(--ink); margin:4px 0 8px; }
.dc-desc { font-size:14px; line-height:1.4; color:var(--muted); margin:0 0 18px; }
.dc-actions { display:flex; flex-direction:column; gap:10px; }
.dc-btn { width:100%; padding:11px 14px; border-radius:999px; font-size:15px; font-weight:800; cursor:pointer; border:1px solid transparent; transition:background .12s ease, opacity .12s ease, transform .1s ease; }
.dc-btn:active { transform:scale(.97); box-shadow:inset 0 0 0 100vmax rgba(255,255,255,.16); }
.dc-btn-delete { background:var(--like); color:#fff; }
.dc-btn-delete:hover { background:#E11D48; }
.dc-btn-delete:disabled { opacity:.6; cursor:default; }
.dc-btn-cancel { background:transparent; color:var(--ink); border-color:var(--div); }
.dc-btn-cancel:hover { background:var(--hover-bg); }
.dc-btn-primary { background:var(--maroon); color:#fff; }
.dc-btn-primary:hover { background:var(--maroon-hover, var(--maroon)); filter:brightness(.92); }

/* ── EDIT POST/REPLY MODAL — same .modal/.dc-* shell as the delete
   confirm modal (dc-modal), just wider and text-aligned left to fit
   a textarea comfortably. ── */
.modal.ec-modal { width:420px; max-width:92vw; padding:20px 20px 16px; text-align:left; }
.ec-modal .dc-title { text-align:left; }
.ec-modal textarea#ec-body {
  width:100%; min-height:110px; resize:vertical; font:inherit; font-size:15px;
  line-height:1.4; padding:10px 12px; border-radius:var(--r-sm); border:1px solid var(--div);
  background:var(--bg); color:var(--ink); margin:0;
}
.ec-modal textarea#ec-body:focus { outline:none; border-color:var(--maroon); }
.ec-count { font-size:12px; color:var(--muted); text-align:right; margin:4px 0 10px; }
.ec-modal .errmsg { margin:0 0 10px; }

/* "· Edited" tag shown next to a post/reply's timestamp once it's
   been changed via the Edit flow — see editedSuffix()/markEditedTag()
   in js/common.js. */
.edited-tag { color:var(--muted); font-weight:400; }

/* ── TOAST — replaces alert() for routine confirmations ── */
.oc-toast {
  position:fixed; left:50%; bottom:28px; transform:translate(-50%, 16px);
  display:flex; align-items:center; gap:10px; max-width:min(92vw, 420px);
  background:var(--ink); color:var(--bg); font-size:14px; font-weight:600; line-height:1.35;
  padding:12px 18px; border-radius:999px; box-shadow:var(--shadow-pop);
  opacity:0; pointer-events:none; z-index:999; transition:opacity .18s ease, transform .18s ease;
}
.oc-toast.show { opacity:1; transform:translate(-50%, 0); pointer-events:auto; }
.oc-toast svg { width:18px; height:18px; flex:none; fill:none; stroke:currentColor; stroke-width:2; stroke-linecap:round; stroke-linejoin:round; }
.oc-toast-error { background:var(--like); color:#fff; }
@media (max-width: 600px) {
  .oc-toast { bottom:calc(84px + env(safe-area-inset-bottom, 0px)); font-size:13px; padding:11px 16px; }
}

#ftr { border-top:none; text-align:left; font-size:13px; color:var(--muted); padding:16px; margin-top:0; }
#ftr a { font-size:13px; font-weight:700; color:var(--maroon); }

.page-wrap { max-width:600px; margin:0; padding:16px 16px 48px; border-left:1px solid var(--line); border-right:1px solid var(--line); min-height:100vh; }
.page-wrap h1 { font-family:inherit; font-size:22px; font-weight:800; color:var(--ink); margin-bottom:12px; }
.page-wrap h2 { font-family:inherit; font-size:16px; font-weight:800; color:var(--ink); margin:20px 0 7px; }
.page-wrap p, .page-wrap li { font-size:14.5px; line-height:1.6; margin-bottom:8px; color:var(--ink); }
.page-wrap ul, .page-wrap ol { margin-left:20px; margin-bottom:10px; }
.page-wrap li::marker { color:var(--maroon); font-weight:700; }

@media (max-width:1150px) {
  #hdr { width:88px; align-items:center; padding:8px 6px; }
  #main { margin-left:88px; }
  #hdr .sb-top { align-items:center; width:100%; }
  .logo { padding:10px; width:auto; }
  .logo .wordmark { display:none; }
  #hdr nav { align-items:center; width:100%; }
  #hdr nav a { width:auto; padding:12px; gap:0; justify-content:center; }
  #hdr nav a .navlabel { display:none; }
  .navmore-btn { width:auto; padding:12px; gap:0; justify-content:center; }
  .navmore-btn .navlabel { display:none; }
  .sidebar-post-btn { width:52px; height:52px; padding:0; border-radius:50%; }
  .sidebar-post-btn span { display:none; }
  .sidebar-post-btn svg { width:22px; height:22px; }
  .acct-btn { justify-content:center; padding:8px; }
  .acct-btn .acct-txt, .acct-btn .acct-dots { display:none; }
  .acct-menu { left:0; }
  .auth-cta { align-items:center; }
  .auth-cta a { width:44px; height:44px; padding:0; display:flex; align-items:center; justify-content:center; font-size:11px; }
  #sidebar, .xright { display:none; }
}

@media (max-width:640px) {
  #feed, .page-wrap, .auth-wrap { border-left:none; }
  #board-hdr h1 { font-size:17px; }
  .pc-avatar { width:36px; height:36px; }
  .rc .pc-avatar { width:30px; height:30px; }
  .rc { padding-left:16px; }

  /* The action row (reply/repost/like/views/share/bookmark) has six
     items and, unlike the rest of the row, the Share button always
     carried a visible "Share" text label. On a phone-width card that
     extra word was the one thing that didn't fit — the row's total
     content width ran past the available space, so instead of
     wrapping or shrinking, the row just spilled past the edge of the
     card (and, since nothing stopped it, the whole page), which is
     why Share looked squeezed against the edge and Bookmark vanished
     off-screen entirely. Icon-only Share on mobile (it already
     matches the post-detail/lightbox action rows, which never showed
     the word) plus tighter spacing gives the row enough room to sit
     fully on-screen again. */
  .act.share .act-label { display:none; }
  .acts { gap:0; }
  .act { padding:6px 5px; }
}

@media (max-width:380px) {
  .act { padding:5px 3px; }
  .act svg { width:17px; height:17px; }
}

/* Narrow phones: .gcv-modal sits at 92vw with 20px side padding —
   fine down to ~360px wide, but under 340px that leaves the kind
   tabs' text/icons and the name field crowding the modal's rounded
   edge. Trim the outer padding a bit and let the tabs wrap their
   label smaller rather than clip. */
@media (max-width:340px) {
  .gcv-modal { padding:16px 16px 16px; }
  .gcv-kind-tabs button { padding:8px 6px; font-size:12.5px; gap:4px; }
}

/* ═══════════════════════════════════════════════════════════════
   MOBILE APP CHROME — top bar + bottom tab bar + slide-out drawer,
   built by renderMobileChrome() in common.js and injected on every
   page. Desktop's left icon rail / #hdr is swapped out for this
   below the breakpoint, matching the native X app's mobile-web
   layout rather than just shrinking the desktop one down.
   ═══════════════════════════════════════════════════════════════ */
#m-topbar, #m-tabbar, #m-fab, .m-drawer-bg { display:none; }
/* mobile chrome (topbar + floating dock) is injected by JS after the
   static page markup has already painted — without this it just
   snaps into existence the instant renderMobileChrome() runs, which
   reads as a jarring pop mid-navigation. Fading it in instead makes
   it feel like part of the same soft transition as the page itself. */
/* opacity-only fade — must NOT animate `transform` here. #m-topbar,
   #m-tabbar and #m-fab inside #m-chrome rely on position:fixed being
   relative to the viewport; giving #m-chrome itself any non-none
   transform (even a translateY(0) resting state from fill-mode:both)
   makes it a new containing block for those fixed descendants, which
   silently turns "fixed to the screen" into "fixed to this div" —
   and since #m-chrome is just appended at the end of <body> with
   static position, everything then collapses to wherever that div
   falls in normal flow (the very bottom, stacked in a column) instead
   of floating over the page. */
#m-chrome { animation:oc-fade-in-opacity .22s ease-out both; }
@keyframes oc-fade-in-opacity { from { opacity:0; } to { opacity:1; } }

@media (max-width:700px) {
  #hdr { display:none; }
  #main { margin-left:0; padding-top:53px; padding-bottom:calc(66px + env(safe-area-inset-bottom)); }
  #sidebar, .xright { display:none; }

  /* Every in-page sticky header (#board-hdr's "For you / Following"
     tabs, .sec-bar's back-button/title bars, .search-topbar) sticks
     at top:0 by default, which is correct on desktop but on mobile
     that's the exact same 53px strip #m-topbar (the fixed hamburger
     + logo bar) already occupies. Once one of these bars scrolls up
     and locks into place it lands directly on top of #m-topbar
     instead of below it — and on some mobile browsers the sticky
     bar's own backdrop-filter layer even gets composited above the
     fixed bar despite its lower z-index, which is what produces the
     "logo/hamburger gets swallowed mid-scroll" glitch. Sticking these
     at top:53px instead of top:0 seats them right below #m-topbar so
     the two never occupy the same space in the first place — more
     robust than trying to win a z-index fight against a browser
     compositing quirk. */
  #board-hdr, .sec-bar, .search-topbar { top:53px; }

  #m-topbar {
    display:flex; align-items:center; justify-content:space-between;
    position:fixed; top:0; left:0; right:0; height:53px;
    padding:0 6px; background:var(--translucent-bg); backdrop-filter:blur(10px);
    border-bottom:1px solid var(--line); z-index:900;
  }
  #m-topbar .m-menu-btn, #m-topbar .m-feeds-btn {
    display:flex; align-items:center; justify-content:center;
    width:40px; height:40px; flex:none; position:relative; z-index:1;
    background:none; border:none; border-radius:50%; color:var(--ink);
    cursor:pointer; text-decoration:none;
  }
  #m-topbar .m-menu-btn svg, #m-topbar .m-feeds-btn svg { width:22px; height:22px; }
  #m-topbar .m-menu-btn, #m-topbar .m-feeds-btn { transition:background-color .15s ease; }
  #m-topbar .m-menu-btn:active, #m-topbar .m-feeds-btn:active { background:var(--hover-bg); }
  /* Absolutely centered on the bar itself (not flex-centered between
     siblings) so the logo stays dead-center regardless of what sits
     to its left — previously it was one of three flex children with
     the "Post"/"Log in" pill on the right, so removing that pill and
     centering the logo through flex layout alone would have shifted
     the whole bar's balance instead of just removing the button. */
  #m-topbar .m-logo {
    position:absolute; left:50%; top:50%; transform:translate(-50%,-50%);
    line-height:0;
  }
  #m-topbar .m-logo .logo-mark { width:26px; height:26px; }

  /* Flush, edge-to-edge bottom bar — Bluesky's own bottom nav is a
     plain full-width strip sitting flat against the screen edge (solid
     background, one hairline border on top, icons evenly spaced), not
     a floating rounded "dock" chip with a shadow under it. Rebuilt to
     match that: no border-radius, no box-shadow, no frosted-glass
     blur, spans the full width instead of centering as an island. */
  #m-tabbar {
    display:flex; align-items:center; justify-content:space-around;
    position:fixed; left:0; right:0; bottom:0; z-index:900;
    padding:8px 4px calc(8px + env(safe-area-inset-bottom));
    background:var(--bg); border-top:1px solid var(--line);
    border-radius:0; box-shadow:none;
  }
  #m-tabbar a {
    flex:1; display:flex; align-items:center; justify-content:center;
    position:relative; height:40px; padding:0;
    color:var(--muted); text-decoration:none;
    transition:color .15s ease;
  }
  #m-tabbar a svg { width:24px; height:24px; flex:none; fill:none; stroke:currentColor; stroke-width:2; stroke-linecap:round; stroke-linejoin:round; }
  /* Icon-only bar, same as Bluesky's — the active tab is just a
     bolder/filled icon in ink, no growing pill or revealed label. */
  #m-tabbar a .m-tab-label { position:absolute; width:1px; height:1px; overflow:hidden; clip:rect(0,0,0,0); white-space:nowrap; }
  #m-tabbar a.cur { color:var(--ink); }
  #m-tabbar a.cur svg { stroke-width:2.5; }
  /* Tap/press highlight — a small circle snugly hugging just the icon,
     the same footprint Twitter/X itself uses, instead of a rounded
     rectangle stretched across the whole flex-width tab column. Pops
     in instantly on press (no fade-in delay, so it feels responsive)
     and eases back out smoothly on release via the scale+opacity
     transition, matching the soft in/out wash the reference video
     shows — same pattern as the post action buttons' (.act) own
     press sheen elsewhere in this file, for a consistent feel. */
  #m-tabbar a .m-tab-hit {
    position:relative; isolation:isolate;
    width:40px; height:40px; border-radius:50%;
    display:flex; align-items:center; justify-content:center;
  }
  #m-tabbar a .m-tab-hit::before {
    content:""; position:absolute; inset:0; border-radius:inherit; z-index:-1;
    background:var(--hover-bg); opacity:0; transform:scale(.5);
    transition:opacity .15s ease, transform .15s cubic-bezier(.2,.8,.2,1);
  }
  #m-tabbar a:hover .m-tab-hit::before,
  #m-tabbar a:active .m-tab-hit::before,
  #m-tabbar a:focus-visible .m-tab-hit::before { opacity:1; transform:scale(1); }
  #m-tabbar a .navbadge { position:absolute; top:-2px; right:-4px; margin-left:0; box-shadow:0 0 0 2px var(--bg); z-index:1; }
  /* Last tab is the person's own avatar (Bluesky puts the profile pic,
     not an icon, in the bottom-right slot) — small ring on the active
     state instead of the bolder-stroke treatment icons get. */
  #m-tabbar a.m-tab-avatar img { width:24px; height:24px; border-radius:50%; display:block; }
  #m-tabbar a.m-tab-avatar.cur img { box-shadow:0 0 0 2px var(--ink); }


  #m-fab {
    display:flex; align-items:center; justify-content:center;
    position:fixed; right:16px; bottom:calc(72px + env(safe-area-inset-bottom)); z-index:850;
    width:54px; height:54px; border-radius:999px; background:var(--maroon); color:#fff;
    border:none; box-shadow:0 4px 14px rgba(15,20,25,.2); cursor:pointer;
    transition:background-color .15s ease;
  }
  #m-fab:active { background:var(--maroon-dk); }
  #m-fab:hover { background:var(--maroon-dk); }
  #m-fab svg { width:24px; height:24px; fill:none; stroke:#fff; stroke-width:2.4; stroke-linecap:round; }

  .m-drawer-bg {
    display:block; position:fixed; inset:0; background:rgba(91,112,131,0); z-index:2000;
    /* isolation:isolate pins this as its own stacking context so its
       z-index is compared as a single unit against page content —
       without it, some mobile browsers (esp. combined with the
       backdrop-filter blur used by #board-hdr / #m-topbar) have been
       seen compositing the sticky "For you / Following" bar into a
       layer that paints above this overlay despite its lower z-index,
       which is exactly the "tab bar sits on top of the open menu" bug. */
    isolation:isolate;
    pointer-events:none; transition:background-color .28s cubic-bezier(.2,.8,.2,1);
  }
  /* Belt-and-suspenders: while the drawer is open, completely hide
     (not just lower the z-index of) every other fixed/sticky mobile
     chrome element that could otherwise leak above it — the sticky
     "For you / Following" bar, the top bar, the bottom dock, and the
     compose FAB — and lock page scroll. display:none sidesteps any
     backdrop-filter compositing-layer quirk entirely (a z-index trick
     only wins if the browser's stacking math cooperates; an element
     that isn't rendered at all can never paint on top of anything).
     Toggled by openMobileDrawer()/closeMobileDrawer() in common.js. */
  html.oc-drawer-open { overflow:hidden; }
  html.oc-drawer-open #board-hdr,
  html.oc-drawer-open #m-topbar,
  html.oc-drawer-open #m-tabbar,
  html.oc-drawer-open #m-fab,
  /* Same bug, same fix, on every OTHER page: any page that isn't the
     home feed has its own sticky/backdrop-filter bar in this exact
     shape — the profile page's "Posts / Replies / Scheduled" tab bar
     (#profile-tabs.sec-bar), the same .sec-bar class reused on
     search/lists/notifications/etc, the search page's sticky topbar,
     and the profile page's translucent back-button over the banner —
     and every one of them was missing from this list, which is
     exactly the "profile page content sits on top of the open drawer"
     bug (tab bar + avatar + Edit Profile bleeding through on top of
     the menu). Hiding the same way #board-hdr already was above. */
  html.oc-drawer-open .sec-bar,
  html.oc-drawer-open .search-topbar,
  html.oc-drawer-open .profile-back-btn,
  html.oc-drawer-open .profile-hdr-actions,
  /* Same bug again on the Search/Explore page (search.html), including
     the community-scoped view (search.html?community=<slug>) reached
     from a community's search icon: #search-tabs — the Explore/
     Trending/News/Sports/Entertainment (or Posts/People) row rendered
     by renderTabs() in search.js — sits directly below the now-hidden
     .search-topbar and isn't sticky/backdrop-filter itself, but the
     same mobile-browser compositing quirk pulls it into the same
     leaked-above-the-drawer layer as its sticky neighbor. Left out of
     this list, it's exactly the "tab row bleeding through on top of
     the open menu" bug from a community search page. */
  html.oc-drawer-open #search-tabs,
  /* Same bug again on the Followers/Following page: its tab bar
     (#fl-tabs) only carries the plain .profile-tabs class, not
     .sec-bar like the profile page's version does, so it was left
     out of the list above and kept bleeding "Followers / Following"
     through on top of the open drawer. */
  html.oc-drawer-open .profile-tabs { display:none !important; }
  /* Extra safety net for the same leak: #pv-avatar and .profile-hdr-top
     each open their own local stacking context (position:relative +
     z-index:1) so the avatar can sit over the banner photo — neutralize
     that while the drawer's open too, in case a browser promotes it
     into the same compositing layer as the sticky bars above. */
  html.oc-drawer-open .profile-hdr-top,
  html.oc-drawer-open #pv-avatar { z-index:auto !important; }
  /* Same bug, same fix, on the community page: tapping the hamburger
     there left the community's avatar/•••/Search/Join row (and the
     card behind it) bleeding through on top of the open drawer. That
     row is body[data-page="community"] .community-hero-top, which
     gets position:relative + z-index:1 (see the community-page rules
     below) for the exact same reason .profile-hdr-top does — so it
     needs the same neutralization while the drawer's open. */
  html.oc-drawer-open .community-hero-top { z-index:auto !important; }
  .m-drawer-bg.open { background:rgba(91,112,131,.4); pointer-events:auto; }
  .m-drawer {
    position:absolute; top:0; left:0; bottom:0; width:80vw; max-width:308px;
    background:var(--bg); padding:18px 14px; overflow-y:auto;
    border-radius:0; box-shadow:none; border-right:1px solid var(--line);
    transform:translateX(-100%); transition:transform .26s cubic-bezier(.2,.8,.2,1);
  }
  .m-drawer-bg.open .m-drawer { transform:translateX(0); }
  /* Set by common.js right before a drawer link navigates away, so
     the drawer snaps shut instantly instead of mid-slide — see the
     closeMobileDrawer() call wired to drawer link clicks for why. */
  .m-drawer-bg.no-anim, .m-drawer-bg.no-anim .m-drawer { transition:none !important; }
  .m-drawer .m-drawer-avatar { width:52px; height:52px; display:block; margin-bottom:10px; }
  .m-drawer .m-drawer-name { display:block; font-size:17px; font-weight:800; color:var(--ink); }
  .m-drawer .m-drawer-handle { display:block; font-size:14px; color:var(--muted); margin-bottom:10px; }
  .m-drawer .m-drawer-stats { display:flex; gap:16px; font-size:13.5px; color:var(--muted); margin-bottom:14px; }
  .m-drawer .m-drawer-stats a { color:var(--ink); text-decoration:none; }
  .m-drawer .m-drawer-stats a b { font-weight:800; }
  .m-drawer .m-drawer-stats a:hover { text-decoration:underline; }
  .m-drawer hr { border:none; border-top:1px solid var(--line); margin:10px 4px; }
  /* Bigger, bolder icons with more breathing room per row — matches
     Bluesky's roomy hamburger-menu list: larger glyphs, generous
     row height, semi-bold label. Grouped into labelled sections
     (Account / Discover / About) instead of one long undifferentiated
     list, same idea as a modern settings-screen redesign — small
     caps group headers give the eye somewhere to land. */
  .m-drawer-group + .m-drawer-group { margin-top:6px; }
  .m-drawer-group-label {
    display:block; font-size:11.5px; font-weight:800; letter-spacing:.04em; text-transform:uppercase;
    color:var(--muted); padding:10px 10px 4px;
  }
  .m-drawer-menu { display:flex; flex-direction:column; }
  .m-drawer-menu a, .m-drawer-menu button {
    display:flex; align-items:center; gap:16px; width:100%; text-align:left;
    font-family:inherit; font-size:16.5px; font-weight:600; color:var(--ink);
    background:none; border:none; padding:12px 10px; cursor:pointer; text-decoration:none; border-radius:18px;
    transition:background-color .12s ease;
  }
  .m-drawer-menu a:hover, .m-drawer-menu button:hover { background:var(--hover-bg); text-decoration:none; }
  .m-drawer-menu a:active, .m-drawer-menu button:active { background:var(--maroon-tint); }
  .m-drawer-menu svg { width:23px; height:23px; fill:none; stroke:currentColor; stroke-width:1.8; stroke-linecap:round; stroke-linejoin:round; flex:none; }
  .m-drawer-cta { display:flex; flex-direction:column; gap:10px; margin-top:8px; }
  .m-drawer-cta a { text-align:center; padding:10px; border-radius:999px; font-weight:800; font-size:14.5px; text-decoration:none; }
  .m-drawer-cta a.cta-primary { background:var(--ink); color:var(--bg); }
  .m-drawer-cta a.cta-ghost { border:1px solid var(--border-input); color:var(--ink); }
  .m-drawer-tos { display:block; font-size:13px; color:var(--link); text-decoration:none; padding:6px 8px; }
  .m-drawer-tos:hover { text-decoration:underline; }
  .m-drawer-footer-row { display:flex; gap:8px; padding:4px 8px 8px; }
  .m-drawer-pill { flex:1; text-align:center; padding:7px 0; border-radius:999px; border:1px solid var(--border-input); color:var(--ink); font-size:13px; font-weight:700; text-decoration:none; }
  .m-drawer-pill:hover { background:var(--hover-bg); text-decoration:none; }

  /* Log out — deliberately separated from the menu list above and
     styled as a clear "danger" action (tinted, not the site accent),
     instead of falling through to a bare unstyled <button>. */
  .m-drawer-logout {
    display:flex; align-items:center; justify-content:center; gap:10px;
    width:100%; margin-top:2px; border:1px solid rgba(224,36,36,.25); border-radius:999px;
    font-family:inherit; font-size:15px; font-weight:800; color:#e02424;
    background:rgba(224,36,36,.08); padding:12px; cursor:pointer; text-decoration:none;
    transition:background-color .12s ease;
  }
  .m-drawer-logout:hover { background:rgba(224,36,36,.16); }
  .m-drawer-logout:active { background:rgba(224,36,36,.22); }
  .m-drawer-logout svg { width:19px; height:19px; flex:none; }
}

/* ═══════════════════════════════════════════════════════════════
   ACCOUNTS — avatars, dropdown, login/signup, profile page.
   ═══════════════════════════════════════════════════════════════ */

.avatar {
  border-radius:50%; object-fit:cover; display:inline-block;
  background:var(--hover-bg); border:none; flex:none;
}
/* Gold-verified accounts (organizations) get a squared-off profile
   picture instead of a circle, matching X/Twitter's gold-check
   convention. Applied via avSqClass() next to every avatar <img>
   that's rendered alongside a profile carrying verification_type. */
.avatar-square { border-radius:22%; }
.pfp-sm  { width:16px; height:16px; margin-right:4px; vertical-align:-3px; }
.pfp-md  { width:32px; height:32px; }
.pfp-lg  { width:96px; height:96px; border:4px solid var(--bg); box-shadow:0 0 0 1px var(--line); }

.pfl { color:var(--ink); font-weight:800; text-decoration:none; }
.pfl:hover { text-decoration:underline; }

.post-login-gate {
  background:var(--cream-card); border:none; border-top:8px solid var(--line); padding:20px 16px;
  text-align:center; font-size:14px; color:var(--muted);
}
.post-login-gate a { font-weight:700; color:var(--maroon); }

/* ── auth pages (login / signup) ──
   Split-panel card: a gradient brand panel (desktop only, purely
   decorative — swapped for a compact header on narrow screens) next
   to the actual form. Icons sit inside the email/username field;
   the password field also gets a show/hide toggle button
   (togglePwVis() in js/auth.js). Everything still reads the same
   theme tokens as the rest of the app, so it follows Dim/Lights out
   and the 5 accent colors automatically. */
.auth-wrap {
  flex:1; min-width:0; margin:0; padding:40px 24px; border-left:1px solid var(--line);
  min-height:100vh; display:flex; align-items:center; justify-content:center;
  background:var(--bg);
}
.auth-shell {
  display:flex; width:100%; max-width:400px;
  background:var(--cream-card); border:1px solid var(--line); border-radius:var(--r-md);
  overflow:hidden;
}

/* right form panel */
.auth-card { flex:1 1 auto; background:var(--cream-card); border:none; width:100%; padding:36px 32px; display:flex; flex-direction:column; justify-content:center; }
.auth-card-logo { display:flex; align-items:center; gap:9px; font-weight:800; font-size:16px; color:var(--ink); text-decoration:none; margin-bottom:22px; }
.auth-card-logo .badge { width:30px; height:30px; border-radius:50%; background:var(--hover-bg); display:flex; align-items:center; justify-content:center; flex:none; }
.auth-card-logo .badge .logo-mark { width:20px; height:20px; }
.auth-card-logo em { font-style:normal; color:var(--maroon); }
.auth-card h1 {
  display:block; font-family:inherit; font-weight:800; font-size:25px;
  color:var(--ink); background:none; padding:0; border-bottom:none; margin:0;
}
/* Visually hidden but still readable by screen readers and search engines —
   used for pages whose primary heading is decorative/tab-driven in the UI
   (e.g. the home feed) so every page still has exactly one descriptive <h1>. */
.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;
}
.auth-sub { font-size:14px; color:var(--muted); margin:6px 0 22px; }
.auth-card .auth-body { padding:0; }
.auth-card .auth-body .errmsg, .auth-card .auth-body .auth-ok { margin:0 0 14px; }
.auth-field { margin-bottom:15px; }
.auth-field label { display:block; font-size:13px; font-weight:700; color:var(--muted); margin-bottom:5px; }
.auth-hint { font-size:11.5px; color:var(--muted); margin-top:4px; display:block; }

.auth-input-wrap { position:relative; }
.auth-input-wrap .auth-fi { position:absolute; left:12px; top:50%; transform:translateY(-50%); width:18px; height:18px; color:var(--muted); pointer-events:none; }
.auth-input-wrap .auth-fi svg { width:100%; height:100%; fill:none; stroke:currentColor; stroke-width:1.8; stroke-linecap:round; stroke-linejoin:round; }
.auth-input-wrap input, .auth-input-wrap select { padding-left:38px; }
.auth-input-wrap input[type="password"], .auth-input-wrap input[type="text"].has-pw-toggle { padding-right:40px; }
.auth-input-wrap:focus-within .auth-fi { color:var(--maroon); }

.pw-toggle {
  position:absolute; right:5px; top:50%; transform:translateY(-50%);
  width:28px; height:28px; display:flex; align-items:center; justify-content:center;
  background:none; border:none; padding:0; margin:0; color:var(--muted); cursor:pointer;
  border-radius:50%; transition:background .12s ease, color .12s ease;
}
.pw-toggle:hover { background:var(--maroon-tint); color:var(--maroon); }
.pw-toggle svg { width:18px; height:18px; fill:none; stroke:currentColor; stroke-width:1.8; stroke-linecap:round; stroke-linejoin:round; }
.pw-toggle .eye-off { display:none; }
.pw-toggle.showing .eye-on { display:none; }
.pw-toggle.showing .eye-off { display:block; }

/* "I'm not a robot" reCAPTCHA widget — shared container used on
   signup/login and every post/reply composer (js/common.js
   renderCaptchaIfNeeded()/verifyHuman()). Hidden via inline
   style="display:none" by default and only shown when a check is
   actually still needed right now. */
/* ── CAPTCHA CARD — custom shell around the Cloudflare Turnstile
   widget. Turnstile's own checkbox/challenge UI lives inside a
   cross-origin iframe we can't restyle, but everything around it is
   ours: a bordered card matching the app's own inputs, a shield/label
   header so it reads as "one quick check" rather than a bare box, a
   shimmer placeholder while the widget script loads, and a green
   "verified" collapse once it passes — so the state change is
   obvious instead of the widget just silently vanishing. ── */
.captcha-card {
  margin:10px 0; border:1px solid var(--border-input); border-radius:var(--r-md);
  background:var(--input-bg); overflow:hidden;
  transition:border-color .15s ease, box-shadow .15s ease, background-color .15s ease;
}
.captcha-card:empty { display:none; margin:0; }
.captcha-card.is-error { border-color:var(--like); animation:captchaShake .4s ease; }
.captcha-card-head {
  display:flex; align-items:center; gap:10px; padding:10px 12px;
  border-bottom:1px solid var(--line); background:var(--hover-bg);
}
.captcha-card-icon {
  flex:none; width:30px; height:30px; border-radius:50%; display:flex;
  align-items:center; justify-content:center; background:var(--maroon-tint); color:var(--maroon);
  transition:background-color .15s ease, color .15s ease, transform .25s ease;
}
.captcha-card-icon svg { width:16px; height:16px; }
.captcha-card-icon .cci-check { display:none; }
.captcha-card-text { min-width:0; }
.captcha-card-title { display:block; font-size:13px; font-weight:700; color:var(--ink); line-height:1.3; }
.captcha-card-sub { display:block; font-size:11.5px; color:var(--muted); line-height:1.3; margin-top:1px; }
.captcha-card-body {
  display:flex; align-items:center; justify-content:center; padding:14px; min-height:69px;
}
.captcha-card.is-verified { border-color:var(--green); background:rgba(0,186,124,.06); }
.captcha-card.is-verified .captcha-card-head { border-bottom-color:rgba(0,186,124,.25); }
.captcha-card.is-verified .captcha-card-body { display:none; }
.captcha-card.is-verified .captcha-card-icon { background:rgba(0,186,124,.16); color:var(--green); transform:scale(1.08); }
.captcha-card.is-verified .captcha-card-icon .cci-shield { display:none; }
.captcha-card.is-verified .captcha-card-icon .cci-check { display:flex; }
.captcha-card.is-verified .captcha-card-title { color:var(--green); }
.captcha-card.is-verified .captcha-card-sub { font-size:0; }
.captcha-card.is-verified .captcha-card-sub::after { content:"You're verified — you're all set."; font-size:11.5px; }
.captcha-skeleton {
  width:100%; max-width:300px; height:65px; border-radius:var(--r-sm);
  background:linear-gradient(90deg, var(--line) 25%, var(--hover-bg) 50%, var(--line) 75%);
  background-size:400% 100%; animation:captchaShimmer 1.4s ease infinite;
}
@keyframes captchaShimmer { 0% { background-position:100% 0; } 100% { background-position:0 0; } }

/* ── homemade drag-to-fit puzzle slider (see api/verify-captcha.js) ──
   Replaced a plain click-to-tick checkbox: now a puzzle-piece handle
   has to be DRAGGED into a randomised target zone (a fresh, different
   spot every time the challenge loads) before the check can pass.
   Still purely a client-side friction/UX layer — see loadCaptchaChallenge()
   in js/common.js for what this is (and isn't) actually gating. ── */
.cs-wrap { display:flex; flex-direction:column; gap:8px; width:100%; max-width:300px; }
.cs-label {
  font-size:12px; font-weight:600; color:var(--muted); text-align:center;
  transition:color .15s ease;
}
.cs-track {
  position:relative; height:44px; border-radius:var(--r-sm);
  background:var(--card-bg, var(--bg)); border:1px solid var(--border-input);
  overflow:hidden; touch-action:none;
}
.cs-fill {
  position:absolute; top:0; left:0; bottom:0; width:36px;
  background:var(--maroon-tint); transition:background-color .15s ease;
}
.cs-target {
  position:absolute; top:4px; width:36px; height:36px; border-radius:9px;
  border:2px dashed var(--muted); opacity:.55; pointer-events:none;
  transition:border-color .2s ease, opacity .2s ease;
}
.cs-handle {
  position:absolute; top:4px; left:0; width:36px; height:36px; border-radius:9px;
  background:var(--maroon); color:#fff; display:flex; align-items:center; justify-content:center;
  cursor:grab; box-shadow:0 2px 6px rgba(0,0,0,.25); touch-action:none;
  transition:background-color .15s ease, box-shadow .15s ease, left .18s ease;
}
.cs-handle.dragging { cursor:grabbing; box-shadow:0 3px 10px rgba(0,0,0,.35); transition:background-color .15s ease, box-shadow .15s ease; }
.cs-handle svg { width:17px; height:17px; }
.cs-track.is-shake { animation:captchaShake .4s ease; }
.cs-track.is-shake .cs-target { border-color:var(--like); }
.cs-track.is-checking .cs-handle { background:var(--maroon-dk, var(--maroon)); }
.cs-track.is-ok .cs-fill { background:rgba(0,186,124,.22); }
.cs-track.is-ok .cs-target { border-color:var(--green); opacity:1; }
.cs-track.is-ok .cs-handle { background:var(--green); }
.captcha-card.is-verified .cs-label { color:var(--green); }
/* Honeypot — present in the DOM for bots that fill every field, but
   never visible or reachable for a real person (off-screen, not just
   opacity:0, and pulled out of tab order). */
.captcha-hp {
  position:absolute !important; left:-9999px !important; top:-9999px !important;
  width:1px !important; height:1px !important; opacity:0 !important; pointer-events:none;
}
@keyframes captchaShake {
  10%, 90% { transform:translateX(-1px); } 20%, 80% { transform:translateX(2px); }
  30%, 50%, 70% { transform:translateX(-4px); } 40%, 60% { transform:translateX(4px); }
}

.auth-submit {
  /* Explicit display so this class is never solely dependent on an
     inline style="display:..." elsewhere in the markup (see the
     recoverFromBackgroundResume() comment in js/common.js for why
     that mattered — a JS reflow used to wipe out exactly that inline
     value on the Contact page's "Email us" link, since a plain <a>'s
     default display is `inline`, which ignores width/max-width. This
     is a no-op for the existing <input type="submit"> usages, whose
     default display is already inline-block. */
  display:inline-block;
  width:100%; font-family:inherit; font-size:15px; font-weight:700;
  background:var(--maroon); color:#fff; border:none; border-radius:999px; padding:12px 10px; cursor:pointer;
  margin-top:10px; transition:background .12s ease, filter .12s ease, transform .05s ease;
  /* Explicit (not just inherited-default) overflow/white-space —
     this class is also reused on a plain <a> (e.g. the Contact page's
     "Email ..." button), and an anchor's box model behaves slightly
     differently from a <button>'s. Spelling these out here removes
     any chance of the label getting clipped or collapsed to nothing
     inside the pill on a browser that resolves the inline-block/
     max-width combination differently. */
  overflow:visible; white-space:normal; line-height:1.3;
}
.auth-submit:hover { background:var(--maroon-dk); }
.auth-submit:active { transform:translateY(1px); }
.auth-submit:disabled { opacity:.6; cursor:default; }
.auth-switch { text-align:left; font-size:14px; color:var(--muted); margin-top:18px; }
.auth-switch a { color:var(--maroon); font-weight:700; }
.auth-legal { font-size:11.5px; color:var(--muted); line-height:1.5; margin-top:18px; }
.auth-legal a { color:var(--muted); text-decoration:underline; }
.auth-ok { background:#E8F5EA; border:1px solid var(--green); border-radius:var(--r-sm); color:#0A7A44; font-size:13px; padding:10px 12px; margin-bottom:12px; }
html[data-theme="dim"] .auth-ok, html[data-theme="dark"] .auth-ok { background:rgba(0,186,124,.14); color:#4ADE80; }

@media (max-width:900px) {
  .auth-wrap { padding:28px 18px; }
  .auth-card { padding:36px 30px; }
}
@media (max-width:480px) {
  .auth-wrap { padding:0; border-left:none; }
  .auth-shell { min-height:100vh; border-radius:0; border:none; }
  .auth-card { padding:28px 22px; justify-content:flex-start; padding-top:15vh; }
}

/* ── profile page ── */
.profile-hdr {
  display:block; background:var(--cream-card); position:relative;
  border:none; border-bottom:1px solid var(--line); border-radius:0; box-shadow:none;
  padding:0 16px 14px; margin-bottom:0;
}
.profile-hdr::before { content:""; display:block; height:170px; margin:0 -16px 0; background:var(--banner-img, linear-gradient(135deg,#3F4B5A,#98A2AE)) center/cover no-repeat; }
.profile-id { flex:1; min-width:180px; padding-top:10px; }
.uname-row { display:flex; align-items:flex-start; gap:10px; flex-wrap:wrap; }
.profile-id .uname { font-family:inherit; font-size:20px; font-weight:800; color:var(--ink); overflow-wrap:anywhere; word-break:break-word; min-width:0; flex:1 1 auto; }
.profile-id .handle { font-size:14px; color:var(--muted); margin-bottom:6px; }
.profile-id .bio { font-size:15px; margin-top:8px; white-space:pre-wrap; }
.profile-meta { font-size:14px; color:var(--muted); margin-top:10px; }
.profile-edit-btn {
  font-family:inherit; font-size:14px; font-weight:700; background:var(--bg);
  border:1px solid var(--border-input); border-radius:999px; padding:7px 15px; cursor:pointer;
  color:var(--ink); text-decoration:none; display:inline-block; transition:background-color .15s ease, border-color .15s ease;
  box-shadow:none;
}
.profile-edit-btn:hover { background:var(--hover-bg); text-decoration:none; }

/* Floating circular "back" button, overlaid on the banner photo
   (top-left) instead of a separate full-width bar above the feed —
   translucent dark pill with a blurred backdrop, same idea as most
   native apps' back chevron over a hero image. */
.profile-back-btn {
  position:absolute; top:12px; left:12px; z-index:2;
  width:36px; height:36px; border-radius:50%; display:flex; align-items:center; justify-content:center;
  background:rgba(15,20,25,.5); backdrop-filter:blur(6px); -webkit-backdrop-filter:blur(6px);
  color:#fff; text-decoration:none; box-shadow:0 1px 5px rgba(15,20,25,.25);
  transition:background .12s ease;
}
.profile-back-btn:hover { background:rgba(15,20,25,.68); }
.profile-back-btn svg { width:19px; height:19px; }

/* Row that holds the avatar and the action buttons (message/follow/
   edit) together — avatar on the left, buttons on the right. Only the
   avatar overlaps the banner photo (via its own negative margin,
   below); this row itself stays in normal flow so the Follow/Edit
   profile button always sits clear of the banner instead of getting
   pulled up onto it. */
.profile-hdr-top { display:flex; align-items:flex-end; justify-content:space-between; gap:10px; margin-top:14px; position:relative; z-index:1; }
/* The "···" profile menu (.pc-menu-dd) is position:fixed with real
   viewport coordinates (see positionMenuDd() in common.js), so it
   paints in the right x/y spot — but it's still a DOM descendant of
   this z-index:1 box, so its stacking is scoped inside that local
   context. #profile-tabs (the sticky Posts/Replies bar) is a sibling
   of .profile-hdr with its own z-index:5, compared at the outer
   level — which beat this whole subtree's z-index:1, so the tab bar
   painted straight through the middle of the open menu. Bumping this
   above 5 only while the menu is open (same targeted-escalation
   trick as .pc:has(.pc-menu-wrap.open) above) fixes it without
   touching the tab bar or affecting normal layout. */
.profile-hdr-top:has(.pc-menu-wrap.open) { z-index:6; }
#pv-avatar { position:relative; z-index:1; }

/* ── header action row: message + "···" menu + follow/edit ── */
.profile-hdr-actions { display:flex; align-items:center; gap:10px; flex:none; align-self:flex-end; margin-bottom:8px; }
.profile-hdr-actions .follow-btn, .profile-hdr-actions .profile-edit-btn { margin-top:0; }
.profile-icon-btn {
  width:36px; height:36px; border-radius:50%; border:1px solid var(--border-input);
  display:inline-flex; align-items:center; justify-content:center; background:var(--bg); cursor:pointer;
  color:var(--ink); transition:background-color .15s ease, border-color .15s ease;
  box-shadow:none;
}
.profile-icon-btn:hover { background:var(--hover-bg); border-color:var(--muted); }
.profile-icon-btn:active { background:var(--maroon-tint); }
.profile-icon-btn svg { width:18px; height:18px; fill:none; stroke:currentColor; stroke-width:1.8; stroke-linecap:round; stroke-linejoin:round; }
.profile-hdr-actions .pc-menu-wrap { margin-left:0; }
.profile-hdr-actions .pc-menu-dd { min-width:220px; }

/* ── location / website / joined row ── */
.profile-meta-row { display:flex; flex-wrap:wrap; gap:4px 14px; margin-top:10px; font-size:14px; color:var(--muted); max-width:100%; }
.pmr-item { display:inline-flex; align-items:center; gap:5px; max-width:100%; min-width:0; }
.pmr-item a { display:inline-flex; align-items:center; gap:5px; color:var(--maroon); text-decoration:none; min-width:0; max-width:100%; }
.pmr-item a:hover .pmr-text { text-decoration:underline; }
.pmr-icon { display:inline-flex; align-items:center; justify-content:center; width:15px; height:15px; flex:none; }
.pmr-icon svg { width:15px; height:15px; display:block; }
.pmr-text { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; min-width:0; }

/* ── "Followed by X and Y" mutuals line ── */
.profile-followed-by { font-size:13px; color:var(--muted); margin-top:8px; }

/* follow button + follower/following stats */
.follow-btn {
  font-family:inherit; font-size:14px; font-weight:700;
  background:var(--ink); color:var(--bg); border:1px solid var(--ink); border-radius:999px;
  padding:7px 16px; cursor:pointer; text-decoration:none; display:inline-block; margin-top:12px;
  transition:background-color .15s ease, color .15s ease, border-color .15s ease, filter .15s ease;
  box-shadow:none;
}
.follow-btn:hover { filter:brightness(.88); text-decoration:none; }
.follow-btn:active { filter:brightness(.78); }
.follow-btn.following { background:var(--cream-card); color:var(--ink); border:1px solid var(--border-input); }
.follow-btn.following:hover { background:#FEECEE; color:var(--like); border-color:var(--like); filter:none; }
.follow-btn:disabled { opacity:.6; cursor:default; }
.follow-btn.locked { opacity:1; cursor:default; display:inline-flex; align-items:center; }
.follow-btn.locked:hover { background:var(--cream-card); color:var(--ink); border-color:var(--border-input); filter:none; }

.profile-stats { display:flex; gap:18px; margin-top:10px; font-size:14px; color:var(--muted); }
.stat-item, .stat-item:visited { cursor:pointer; color:var(--ink); font-weight:700; text-decoration:none; }
.stat-item b { color:var(--ink); font-weight:800; }
.stat-item:hover { color:var(--ink); text-decoration:none; }
.stat-item.stat-static { cursor:default; }

.edit-form { display:none; margin-top:0; border-top:none; padding-top:0; }
.edit-form.open { display:block; }
.edit-form label { display:block; font-size:12px; font-weight:700; color:var(--muted); margin:10px 0 4px; }
.edit-form input[type="text"], .edit-form textarea { max-width:100%; }
.edit-form textarea { height:70px; }
.edit-row { display:flex; gap:10px; align-items:center; margin-top:10px; }
.avatar-pick { display:flex; align-items:center; gap:12px; margin-top:8px; }

.empty-note { font-size:13.5px; color:var(--muted); font-style:normal; padding:20px 16px; }

/* Scheduled posts tab (profile.js loadScheduledPosts) */
.sched-item { padding:12px 16px; border-bottom:1px solid var(--line); }
.sched-item-when { font-size:12.5px; color:var(--maroon); font-weight:600; margin-bottom:4px; }
.sched-item-body { font-size:14.5px; white-space:pre-wrap; word-break:break-word; margin-bottom:8px; }
.sched-item-actions { display:flex; gap:8px; }
.sched-item-actions button { font-size:12.5px; padding:5px 12px; border-radius:999px; cursor:pointer; }
.cx-sched-remove-btn { background:none; border:1px solid var(--line); color:var(--muted); }
.cx-sched-remove-btn:hover { border-color:#c0392b; color:#c0392b; }

/* ═══════════════════════════════════════════════════════════════
   SEARCH PAGE
   ═══════════════════════════════════════════════════════════════ */
.search-topbar { padding:10px 16px; border-bottom:1px solid var(--line); position:sticky; top:0; background:var(--translucent-bg); backdrop-filter:blur(8px); z-index:5; }
/* Community-scoped search banner (search.html?community=<slug>) —
   set by renderSearchScope() in search.js. Empty/no markup when the
   search isn't scoped to a community, so it costs nothing otherwise. */
.search-scope {
  display:flex; align-items:center; justify-content:space-between; gap:10px;
  padding:10px 16px; font-size:13px; color:var(--muted); border-bottom:1px solid var(--line);
}
.search-scope b { color:var(--ink); }
.search-scope a { color:var(--link); text-decoration:none; flex:none; }
.search-scope a:hover { text-decoration:underline; }
.search-topbar .xsearch { margin:0; }
.search-topbar form { display:contents; }

/* ═══════════════════════════════════════════════════════════════
   NOTIFICATIONS PAGE
   ═══════════════════════════════════════════════════════════════ */
.notif-item { display:flex; gap:12px; align-items:flex-start; padding:14px 16px; border-bottom:1px solid var(--line); text-decoration:none; color:var(--ink); transition:background-color .12s ease; animation:oc-fade-in .2s ease both; }
.notif-item:hover { background:var(--hover-bg); text-decoration:none; }
.notif-item.unread { background:var(--maroon-tint); }
.notif-item.unread:hover { background:rgba(14,165,233,.16); }
.notif-icon { flex:none; width:28px; height:28px; border-radius:50%; display:flex; align-items:center; justify-content:center; margin-top:2px; }
.notif-icon svg { width:17px; height:17px; fill:none; stroke:currentColor; stroke-width:2; }
.notif-icon.like { background:var(--like-tint); color:var(--like); }
.notif-icon.like svg { fill:var(--like); stroke:var(--like); }
.notif-icon.reply { background:var(--maroon-tint); color:var(--maroon); }
.notif-icon.follow { background:var(--maroon-tint); color:var(--maroon); }
.notif-icon.repost { background:rgba(0,186,124,.10); color:var(--green); }
.notif-icon.quote { background:var(--maroon-tint); color:var(--maroon); }
.notif-icon.quote svg { fill:currentColor; stroke:none; }
.notif-body { flex:1; min-width:0; }
.notif-body .notif-txt { font-size:14.5px; color:var(--ink); margin-top:4px; }
.notif-body .notif-txt b { font-weight:800; }
.notif-body .notif-snip { font-size:13.5px; color:var(--muted); margin-top:2px; white-space:pre-wrap; overflow:hidden; text-overflow:ellipsis; display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; }
.notif-body .notif-time { font-size:12.5px; color:var(--muted); }

/* ═══════════════════════════════════════════════════════════════
   CHAT PAGE
   ═══════════════════════════════════════════════════════════════ */

/* ── conversation list ── */
.conv-row-wrap { position:relative; }
.conv-row-wrap .conv-menu-wrap { position:absolute; top:14px; right:12px; z-index:2; }
.conv-row-wrap .conv-row { padding-right:44px; }
.conv-row { display:flex; gap:12px; align-items:center; padding:12px 16px 12px 14px; border-bottom:1px solid var(--line); text-decoration:none; color:var(--ink); transition:background-color .12s ease; position:relative; animation:oc-fade-in .2s ease both; border-left:3px solid transparent; }
.conv-row:hover { background:var(--hover-bg); text-decoration:none; }
.conv-row .avatar { flex:none; width:50px; height:50px; }
.conv-txt { flex:1; min-width:0; }
.conv-top { display:flex; align-items:baseline; gap:6px; }
.conv-name { font-weight:800; font-size:15px; color:var(--ink); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.conv-handle { font-size:13.5px; color:var(--muted); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; min-width:0; }
.conv-time { font-size:12.5px; color:var(--muted); margin-left:auto; flex:none; }
.conv-snip { font-size:13.5px; color:var(--muted); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; margin-top:2px; }
.conv-row.unread { border-left-color:var(--maroon); background:var(--maroon-tint); }
.conv-row.unread:hover { background:var(--maroon-tint); filter:brightness(0.97); }
.conv-row.unread .conv-snip, .conv-row.unread .conv-name { color:var(--ink); font-weight:700; }
.conv-dot { width:9px; height:9px; border-radius:50%; background:var(--maroon); flex:none; align-self:center; box-shadow:0 0 0 3px var(--maroon-tint); }

/* new-message composer: collapsed to a single pill trigger by
   default; expands into an input+send row when tapped, instead of
   permanently occupying a full-width bar above the conversation
   list. */
.chat-new-trigger { display:flex; align-items:center; gap:10px; margin:10px 16px 6px; padding:10px 16px; border-radius:999px; background:var(--hover-bg); color:var(--muted); border:none; font-size:14.5px; font-family:inherit; cursor:pointer; width:calc(100% - 32px); text-align:left; transition:background .12s ease; }
.chat-new-trigger:hover { background:var(--border-input); }
.chat-new-trigger svg { width:18px; height:18px; flex:none; color:var(--maroon); }
.chat-new { display:flex; gap:10px; align-items:center; padding:10px 16px; border-bottom:1px solid var(--line); animation:oc-fade-in .15s ease both; }
.chat-new .xsearch { flex:1; margin:0; border:1.5px solid transparent; transition:border-color .12s ease, background-color .12s ease; }
.chat-new .xsearch:focus-within { border-color:var(--maroon); background:var(--bg); }
/* A plain "Cancel" text action reads more clearly next to a search
   field than a bare X glyph in a circle — the X was easy to mistake
   for "clear text" (which the field doesn't even have) rather than
   "close this search". Matches the text buttons used everywhere else
   in the app (gi-edit-cancel, etc.) instead of introducing a new,
   icon-only affordance just for this one bar. */
.chat-new-close { flex:none; border:none; background:none; color:var(--maroon); font-family:inherit; font-size:14px; font-weight:700; cursor:pointer; padding:8px 2px; white-space:nowrap; border-radius:var(--r-sm); transition:opacity .12s ease; }
.chat-new-close:hover { opacity:.75; text-decoration:underline; }

.chat-empty { text-align:center; padding:64px 24px; color:var(--muted); }
.chat-empty svg { width:52px; height:52px; color:var(--maroon); opacity:.5; margin-bottom:14px; }
.chat-empty h3 { font-size:17px; color:var(--ink); margin:0 0 6px; }
.chat-empty p { font-size:14px; margin:0; }
.chat-empty .chat-empty-fab-btn {
  margin-top:18px; border:none; border-radius:999px; background:var(--maroon); color:#fff;
  font-family:inherit; font-size:14.5px; font-weight:800; padding:10px 20px; cursor:pointer;
  display:inline-flex; align-items:center; gap:8px; transition:background-color .15s ease, transform .1s ease;
}
.chat-empty .chat-empty-fab-btn:hover { background:var(--maroon-dk); }
.chat-empty .chat-empty-fab-btn:active { transform:scale(.96); }
.chat-empty .chat-empty-fab-btn svg { width:17px; height:17px; color:#fff; opacity:1; margin:0; }

/* ── chat FAB — a floating circular "new chat" button pinned to the
   bottom-right corner of the conversation list, Bluesky-style,
   visible on every breakpoint (unlike #m-fab, which is mobile-only
   and reserved for the post composer). Only rendered on the list
   view — loadThread()/loadGroupThread() remove it via
   .chat-thread-open, same switch the mobile FAB/tab bar already use. */
#chat-fab {
  position:fixed; right:24px; bottom:28px; z-index:60;
  width:56px; height:56px; border-radius:999px; border:none; cursor:pointer;
  background:var(--maroon); color:#fff; display:flex; align-items:center; justify-content:center;
  box-shadow:0 6px 18px rgba(15,20,25,.22); transition:background-color .15s ease, transform .15s cubic-bezier(.34,1.56,.64,1);
}
#chat-fab:hover { background:var(--maroon-dk); transform:scale(1.05); }
#chat-fab:active { transform:scale(.92); }
#chat-fab svg { width:25px; height:25px; fill:none; stroke:#fff; stroke-width:2.2; stroke-linecap:round; stroke-linejoin:round; }
@media (max-width:600px) {
  #chat-fab { right:16px; bottom:calc(76px + env(safe-area-inset-bottom, 0px)); width:52px; height:52px; }
}
body.chat-thread-open #chat-fab { display:none; }

/* ── "new chat" sheet — opened by #chat-fab (or the existing
   chat-new-trigger pill). Same popover/bottom-sheet shell as
   .rp-menu-dd (repost menu) for visual consistency: a small anchored
   card on desktop, a full-width sheet sliding up from the bottom on
   mobile. */
.chat-fab-sheet-bg { display:none; position:fixed; inset:0; z-index:1000; background:rgba(91,112,131,0); transition:background-color .2s ease; }
.chat-fab-sheet-bg.open { display:block; background:rgba(91,112,131,.4); }
.chat-fab-sheet {
  position:fixed; right:24px; bottom:96px; z-index:1001; width:250px;
  background:var(--cream-card); border:1px solid var(--line); border-radius:18px; box-shadow:var(--shadow-pop);
  overflow:hidden; padding:6px; opacity:0; visibility:hidden; pointer-events:none;
  transform:translateY(6px) scale(.97); transform-origin:bottom right;
  transition:opacity .15s ease, transform .15s cubic-bezier(.2,.8,.2,1), visibility 0s linear .15s;
}
.chat-fab-sheet-bg.open .chat-fab-sheet {
  opacity:1; visibility:visible; pointer-events:auto; transform:translateY(0) scale(1);
  transition:opacity .16s ease, transform .16s cubic-bezier(.2,.8,.2,1);
}
.chat-fab-sheet button {
  display:flex; align-items:center; gap:12px; width:100%; text-align:left; background:none; border:none; cursor:pointer;
  border-radius:12px; font-family:inherit; font-size:14.5px; font-weight:700; color:var(--ink); padding:11px 12px;
  transition:background .1s ease;
}
.chat-fab-sheet button:hover { background:var(--hover-bg); }
.chat-fab-sheet button svg { width:19px; height:19px; flex:none; fill:none; stroke:var(--maroon); stroke-width:2; stroke-linecap:round; stroke-linejoin:round; }
.chat-fab-sheet-sub { font-size:11.5px; font-weight:500; color:var(--muted); display:block; margin-top:1px; }
@media (max-width:600px) {
  #chat-fab, .chat-fab-sheet { right:16px; }
  .chat-fab-sheet {
    right:0; left:0; bottom:0; width:auto; border-radius:20px 20px 0 0;
    padding-bottom:calc(10px + env(safe-area-inset-bottom, 0px));
    transform:translateY(100%); transform-origin:bottom center;
  }
  .chat-fab-sheet-bg.open .chat-fab-sheet { transform:translateY(0); }
  .chat-fab-sheet::before { content:""; display:block; width:36px; height:4px; border-radius:99px; background:var(--div); margin:8px auto 6px; }
  .chat-fab-sheet button { padding:13px 16px; font-size:15px; }
}

/* ── create group/channel modal — "modern card" redesign, styled
   after a WeTransfer-style create-transfer sheet: bold oversized
   title with a filled circular close button, a segmented Group/
   Channel switch, flat light-gray "cards" for each section (upload,
   fields, settings) instead of bordered inputs, and a pill button
   that goes from muted/disabled to solid brand color once the form
   is valid. ── */
.gcv-modal { width:400px; max-width:92vw; padding:22px 20px 20px; text-align:left; box-sizing:border-box; }
.gcv-head { display:flex; align-items:center; justify-content:space-between; gap:12px; margin-bottom:14px; }
.gcv-modal h2 { padding:0; margin:0; font-size:20px; font-weight:800; letter-spacing:-.01em; flex:1; text-align:center; }
.gcv-close, .gcv-back {
  flex:none; width:32px; height:32px; border-radius:50%; background:var(--hover-bg); color:var(--ink);
  display:flex; align-items:center; justify-content:center; cursor:pointer; transition:background .12s ease;
}
.gcv-close svg, .gcv-back svg { width:15px; height:15px; }
.gcv-close:hover, .gcv-back:hover { background:var(--div); }

/* Wizard progress — 3 flat segments that fill left-to-right as the
   person advances (gcvGoToStep() toggles .filled), plus a small
   "Step N of 3 · <label>" caption underneath. Kept intentionally
   plain (no numbers/checkmarks in the segments themselves) so it
   reads at a glance without competing with the step content. */
.gcv-progress { display:flex; gap:6px; margin-bottom:8px; }
.gcv-progress-seg { flex:1; height:4px; border-radius:2px; background:var(--div); transition:background-color .18s ease; }
.gcv-progress-seg.filled { background:var(--maroon); }
.gcv-step-label { margin:0 2px 16px; font-size:12px; font-weight:700; color:var(--muted); text-transform:uppercase; letter-spacing:.02em; }

/* Step 2's privacy panel is otherwise just a single setting row
   floating alone, which reads sparse after step 1's denser layout —
   a large centered glyph gives the step some visual weight without
   adding another field to fill in. */
.gcv-privacy-illus { display:flex; align-items:center; justify-content:center; width:72px; height:72px; margin:8px auto 20px; border-radius:50%; background:var(--hover-bg); color:var(--muted); }
.gcv-privacy-illus svg { width:32px; height:32px; }

/* Footer holds whichever of the two buttons the current step needs
   (gcvGoToStep() toggles display) — same visual weight/position
   either way so advancing doesn't shift the modal's height. */
.gcv-footer { margin-top:4px; }
.gcv-kind-tabs { display:flex; gap:4px; margin-bottom:16px; background:var(--hover-bg); border-radius:999px; padding:4px; }
.gcv-kind-tabs button {
  flex:1; display:flex; align-items:center; justify-content:center; gap:6px;
  border:none; background:none; padding:10px 10px; border-radius:999px; font-family:inherit;
  font-size:14px; font-weight:700; color:var(--muted); cursor:pointer; transition:background .15s ease, color .15s ease, box-shadow .15s ease;
}
.gcv-kind-tabs button svg { width:15px; height:15px; flex:none; }
.gcv-kind-tabs button.cur { background:var(--cream-card); color:var(--ink); box-shadow:0 1px 4px rgba(15,20,25,.14); }
.gcv-modal .dc-desc { text-align:left; margin:0 2px 16px; color:var(--muted); line-height:1.4; font-size:13px; }

/* Upload "card" — big flat gray rounded box with a centered circular
   plus button, mirroring a WeTransfer-style "Upload files" tile. */
.gcv-upload-box {
  display:flex; flex-direction:column; align-items:center; justify-content:center; gap:10px;
  background:var(--hover-bg); border-radius:18px; padding:22px 16px; margin-bottom:12px; text-align:center;
}
.gcv-avatar-wrap { position:relative; display:inline-flex; flex:none; box-sizing:border-box; width:56px; height:56px; }
/* Scoped under .gcv-upload-box so this beats the generic `.modal
   label { display:block; margin:... }` rule (higher specificity),
   which was otherwise overriding this label's flex layout and
   squashing the circle into an oval. */
.gcv-upload-box .gcv-avatar-preview {
  width:56px; height:56px; margin:0; box-sizing:border-box; border-radius:50%; display:flex; align-items:center; justify-content:center;
  background:var(--div); color:var(--muted); overflow:hidden; cursor:pointer; transition:background .12s ease;
}
.gcv-avatar-preview:hover { background:var(--hover-bg-strong, var(--div)); }
.gcv-avatar-preview svg { width:30px; height:30px; flex-shrink:0; }
.gcv-avatar-preview img { width:100%; height:100%; object-fit:cover; display:block; }
/* Small circular "add" badge pinned to the bottom-right corner of the
   avatar circle — mirrors the person-silhouette-plus-badge default
   avatar glyph used elsewhere (contacts, empty profile pics): a plain
   picture circle with a small plus badge overlapping its edge, ringed
   in the card background so it reads as a separate layer rather than
   bleeding into the photo/icon beneath it. */
.gcv-avatar-badge {
  position:absolute; right:-2px; bottom:-2px; width:20px; height:20px; border-radius:50%;
  background:var(--maroon); color:#fff; display:flex; align-items:center; justify-content:center;
  box-sizing:border-box; border:2px solid var(--hover-bg); cursor:pointer; transition:background .12s ease; pointer-events:auto;
}
/* `.modal label { margin:0 16px 5px }` outranks the bare-class rule
   above on specificity (class+type beats class alone), so the plain
   `margin:0` here was silently losing and leaving the badge shifted
   off its corner. Scoping under `.gcv-upload-box` (like the avatar
   circle fix above) wins outright instead of relying on !important. */
.gcv-upload-box .gcv-avatar-badge { margin:0; }
.gcv-avatar-badge:hover { background:var(--maroon-dk); }
.gcv-avatar-badge svg { width:10px; height:10px; flex-shrink:0; }
.gcv-upload-txt { font-size:14.5px; font-weight:700; color:var(--ink); }
.gcv-upload-txt small { display:block; font-size:12px; font-weight:500; color:var(--muted); margin-top:2px; }

/* Flat "pill" fields — no visible border, just a filled gray rounded
   box, placeholder doing the labeling (WeTransfer's Email / Title-
   and-message rows) instead of a separate <label> above each one.
   Text is centered in the pill (name/description); the char counter
   is its own small caption under the pill rather than sitting inside
   it, so it never overlaps what's typed. */
.gcv-pill-field { margin-bottom:4px; }
/* Step 1's Name/Description pills read as sitting slightly right of
   center — nudge them left a few px (anchoring the left edge and
   pulling in the right edge only) so they line up visually centered
   under the upload box above them. */
#gcv-step-1 .gcv-pill-field { margin-right:28px; }
/* :not(.gcv-pill-field-search) keeps this centered-text rule from
   also winning (by specificity, regardless of source order) over the
   member-search field's own left-aligned input just below — that
   mismatch was making "Add members by username" render centered
   too. */
.gcv-pill-field:not(.gcv-pill-field-search) input[type="text"], .gcv-pill-field textarea, .gcv-pill-field:not(.gcv-pill-field-search) input:not([type]) {
  width:100%; max-width:100%; box-sizing:border-box; font:inherit; font-size:15px; padding:14px 16px; border-radius:16px;
  border:none; background:var(--hover-bg); color:var(--ink); resize:none; transition:box-shadow .12s ease; text-align:center;
}
.gcv-pill-field textarea { min-height:48px; padding-top:14px; }
.gcv-pill-field input::placeholder, .gcv-pill-field textarea::placeholder { color:var(--muted); }
/* :not(.gcv-pill-field-search) here too — same reason as the
   text-align rule above. Without it, this generic focus rule also
   matched #gcv-member-search (its wrapper carries both
   .gcv-pill-field and .gcv-pill-field-search), painting a sharp-
   cornered 2px box-shadow directly on the plain, non-rounded <input>
   *inside* the already-rounded pill that .gcv-pill-field-search:
   focus-within paints on the wrapper — two visibly different-shaped
   red outlines stacked on top of each other on step 3. */
.gcv-pill-field:not(.gcv-pill-field-search) input:focus, .gcv-pill-field:not(.gcv-pill-field-search) textarea:focus { outline:none; box-shadow:inset 0 0 0 2px var(--maroon); }
.gcv-pill-field-search { display:flex; align-items:center; gap:10px; background:var(--hover-bg); border-radius:16px; padding:0 16px; margin-bottom:10px; }
.gcv-pill-field-search svg { flex:none; width:18px; height:18px; color:var(--muted); }
.gcv-pill-field-search input { flex:1; min-width:0; background:none; border:none; padding:14px 0; font:inherit; font-size:15px; color:var(--ink); text-align:left; }
.gcv-pill-field-search input:focus { outline:none; }
.gcv-pill-field-search:focus-within { box-shadow:inset 0 0 0 2px var(--maroon); }
.gcv-pill-charcount { display:block; text-align:right; font-size:11px; color:var(--muted); margin:4px 4px 10px; }
.gcv-pill-charcount.gcv-charcount-limit { color:var(--maroon); font-weight:700; }

/* Setting row — flat gray pill with an icon chip on the left, label
   in the middle, and the control (toggle here) on the right, same
   shape as a WeTransfer "Expires in / Downloads" row. */
.gcv-setting-row { display:flex; align-items:center; gap:12px; background:var(--hover-bg); border-radius:16px; padding:12px 16px; margin-bottom:10px; }
.gcv-setting-icon { flex:none; width:30px; height:30px; border-radius:50%; background:var(--cream-card); color:var(--muted); display:flex; align-items:center; justify-content:center; }
.gcv-setting-icon svg { width:15px; height:15px; flex-shrink:0; }
.gcv-setting-txt { flex:1; min-width:0; font-size:14px; color:var(--ink); font-weight:700; }
.gcv-setting-txt small { display:block; font-size:11.5px; color:var(--muted); font-weight:500; margin-top:1px; }

.gcv-members-picked { display:flex; flex-wrap:wrap; gap:6px; margin:0 2px 10px; }
.gcv-chip { display:inline-flex; align-items:center; gap:6px; background:var(--hover-bg); border-radius:999px; padding:4px 6px 4px 4px; font-size:12.5px; font-weight:700; }
.gcv-chip img { width:20px; height:20px; border-radius:50%; }
.gcv-chip button { border:none; background:none; color:var(--muted); cursor:pointer; width:16px; height:16px; display:flex; align-items:center; justify-content:center; padding:0; }
.gcv-chip button svg { width:12px; height:12px; }
.gcv-member-results { max-height:150px; overflow-y:auto; background:var(--hover-bg); border-radius:16px; margin:-4px 2px 10px; }
.gcv-member-results:empty { display:none; margin:0; }
.gcv-member-row { display:flex; align-items:center; gap:8px; padding:9px 12px; cursor:pointer; transition:background .1s ease; }
.gcv-member-row:hover { background:var(--div); }
.gcv-member-row img { width:26px; height:26px; border-radius:50%; }
.gcv-member-row .ulrow-txt { min-width:0; }

.gcv-create-btn {
  width:100%; padding:15px 14px; border-radius:16px; font-size:16px; font-weight:800; border:none; cursor:pointer;
  background:var(--maroon); color:#fff; margin-top:6px; transition:background-color .15s ease, opacity .15s ease, color .15s ease;
}
.gcv-create-btn:hover:not(:disabled) { background:var(--maroon-dk); }
.gcv-create-btn:disabled { background:var(--div); color:var(--muted); cursor:default; }
.gcv-modal .errmsg { margin:0 2px 10px; }

/* ── legacy field/toggle styles — still used by the group-info "Edit
   name & description" inline form (giToggleEditDetails), which wasn't
   part of the create-modal redesign above and keeps its original
   bordered-field look. ── */
.gcv-field { margin-bottom:16px; }
.gcv-field label { display:block; font-size:12px; font-weight:700; color:var(--muted); margin-bottom:5px; }
.gcv-field input[type="text"], .gcv-field textarea {
  width:100%; max-width:100%; box-sizing:border-box; font:inherit; font-size:14.5px; padding:10px 14px; border-radius:var(--r-sm);
  border:1px solid var(--div); background:var(--bg); color:var(--ink); resize:none; transition:border-color .12s ease;
}
.gcv-field input[type="text"]:focus, .gcv-field textarea:focus { outline:none; border-color:var(--maroon); }
.gcv-charcount { display:block; text-align:right; font-size:11px; color:var(--muted); margin-top:4px; }
.gcv-charcount.gcv-charcount-limit { color:var(--maroon); font-weight:700; }
.gcv-toggle-row { display:flex; align-items:center; gap:12px; padding:14px 0; border-top:1px solid var(--line); border-bottom:1px solid var(--line); margin-bottom:16px; }
.gcv-toggle-icon { flex:none; width:34px; height:34px; border-radius:50%; background:var(--maroon-tint); color:var(--maroon); display:flex; align-items:center; justify-content:center; }
.gcv-toggle-icon svg { width:17px; height:17px; flex-shrink:0; }
.gcv-toggle-txt { flex:1; min-width:0; font-size:13.5px; color:var(--ink); font-weight:600; }
.gcv-toggle-txt small { display:block; font-size:11.5px; color:var(--muted); font-weight:500; margin-top:1px; }

/* ── group/channel row in the conversation list — same .conv-row
   shell, plus a small kind badge over the group avatar. ── */
.conv-row .conv-kind-badge {
  /* Corner badge over the group/channel avatar — .conv-group-avatar
     is 50x50 starting 14px from the row's left/top padding edge, so
     this sits right at its bottom-right corner (peeking mostly
     outside the circle) instead of dead-center over the picture,
     which used to hide the avatar image entirely. */
  position:absolute; left:50px; top:48px; width:18px; height:18px; border-radius:50%;
  background:var(--maroon); color:#fff; display:flex; align-items:center; justify-content:center;
  box-shadow:0 0 0 2px var(--cream-card);
}
.conv-row .conv-kind-badge svg { width:11px; height:11px; fill:none; stroke:#fff; stroke-width:2.4; stroke-linecap:round; stroke-linejoin:round; }
.conv-group-avatar {
  width:50px; height:50px; border-radius:50%; flex:none; display:flex; align-items:center; justify-content:center;
  background:var(--maroon-tint); color:var(--maroon); font-weight:800; font-size:17px; overflow:hidden;
}
.conv-group-avatar img { width:100%; height:100%; object-fit:cover; }

/* ── group/channel thread header extras ── */
.chat-hdr-sub { font-size:12px; color:var(--muted); display:block; }
.chat-hdr-info-btn { margin-left:auto; flex:none; width:34px; height:34px; border-radius:50%; border:none; background:none; color:var(--muted); display:flex; align-items:center; justify-content:center; cursor:pointer; transition:background .12s ease; }
.chat-hdr-info-btn:hover { background:var(--hover-bg); }
.chat-hdr-info-btn svg { width:19px; height:19px; }
.chat-channel-notice { display:flex; align-items:center; gap:8px; justify-content:center; padding:12px 16px; margin:0 16px 10px; border-radius:var(--r-sm); background:var(--hover-bg); color:var(--muted); font-size:13px; text-align:center; }
.chat-channel-notice svg { width:16px; height:16px; flex:none; }
.gm-sender-name { font-size:12px; font-weight:700; color:var(--maroon); margin:0 0 2px 2px; }

/* ── group info panel ── */
.gi-modal { width:380px; max-width:92vw; padding:0 0 16px; text-align:left; }
.gi-hdr { padding:20px 18px 14px; text-align:center; border-bottom:1px solid var(--line); }
.gi-hdr img, .gi-hdr .conv-group-avatar { width:64px; height:64px; margin:0 auto 10px; font-size:22px; }
.gi-hdr h2 { margin:0 0 3px; }
.gi-hdr p { font-size:12.5px; color:var(--muted); margin:0; }
/* Explicit 64x64 box (matches .gi-hdr img/.conv-group-avatar) so this
   is a real positioning context — a plain inline-block with no
   intrinsic size is what let the camera badge drift below/off the
   avatar instead of docking to its corner (same fix already applied
   to .gcv-avatar-wrap above). */
.gi-avatar-wrap { position:relative; display:inline-block; width:64px; height:64px; }
.gi-avatar-editable .conv-group-avatar, .gi-avatar-editable img { cursor:pointer; }
.gi-avatar-pick {
  position:absolute; right:-2px; bottom:-2px; width:24px; height:24px; border-radius:50%;
  background:var(--maroon); color:#fff; display:flex; align-items:center; justify-content:center;
  cursor:pointer; box-shadow:0 0 0 2px var(--cream-card); transition:background .12s ease;
}
/* Same specificity gotcha as `.gcv-upload-box .gcv-avatar-badge` above:
   `.modal label { margin:0 16px 5px }` (class+type) outranks a bare
   `.gi-avatar-pick` class rule, so its margin was nudging this badge
   off the avatar's corner. Scoping under `.gi-avatar-wrap` wins. */
.gi-avatar-wrap .gi-avatar-pick { margin:0; }
/* The avatar picture itself is *also* wrapped in its own plain
   <label for="gi-avatar-file"> (no class) so a click anywhere on the
   picture opens the file picker too — and that label was catching
   the same `.modal label` margin, shoving the visible avatar sideways
   inside the 64x64 box while the corner badge stayed correctly
   anchored to the box's true edge. Net effect: badge and avatar drift
   apart, which reads as "the badge is off". #gi-avatar-wrap's id
   selector guarantees this wins over `.modal label` regardless of
   source order. */
#gi-avatar-wrap label { margin:0; display:block; line-height:0; }
.gi-avatar-pick:hover { background:var(--maroon-dk); }
.gi-avatar-pick svg { width:13px; height:13px; }
.gi-edit-details-btn {
  border:none; background:none; color:var(--maroon); font-family:inherit; font-size:12.5px; font-weight:700;
  cursor:pointer; padding:4px 0 0; margin:0;
}
.gi-edit-details-btn:hover { text-decoration:underline; }
#gi-details-edit { text-align:left; margin-top:10px; }
#gi-details-edit input[type="text"], #gi-details-edit textarea {
  width:100%; font:inherit; font-size:14px; padding:8px 11px; border-radius:var(--r-sm);
  border:1px solid var(--div); background:var(--bg); color:var(--ink); resize:none; text-align:center;
}
#gi-details-edit input[type="text"]:focus, #gi-details-edit textarea:focus { outline:none; border-color:var(--maroon); }
/* .gcv-field's default spacing was tuned for the compact "new
   group" modal — reused here the Name box, its char count, and the
   Description box all sat right on top of each other. Widen the
   gaps in this panel only, so the create-group modal is untouched. */
#gi-details-edit .gcv-field { margin-bottom:20px; }
#gi-details-edit .gcv-charcount { margin-top:6px; }
#gi-details-edit .gcv-toggle-row { padding-top:8px; }
.gi-edit-actions { display:flex; justify-content:flex-end; gap:8px; margin-top:6px; }
.gi-edit-cancel, .gi-edit-save {
  border:none; border-radius:999px; padding:7px 14px; font-family:inherit; font-size:13px; font-weight:700; cursor:pointer;
}
.gi-edit-cancel { background:none; color:var(--muted); }
.gi-edit-cancel:hover { background:var(--hover-bg); }
.gi-edit-save { background:var(--maroon); color:#fff; }
.gi-edit-save:hover { background:var(--maroon-dk); }
.gi-section-label { font-size:11.5px; font-weight:800; letter-spacing:.3px; text-transform:uppercase; color:var(--muted); padding:14px 18px 4px; }
.gi-member-row { display:flex; align-items:center; gap:10px; padding:8px 18px; }
.gi-member-row img { width:32px; height:32px; border-radius:50%; }
.gi-member-role { margin-left:auto; font-size:11px; font-weight:800; color:var(--muted); text-transform:uppercase; }
.gi-member-remove { flex:none; display:flex; align-items:center; justify-content:center; width:22px; height:22px; padding:0; margin-left:2px; background:none; border:none; border-radius:50%; color:var(--muted); cursor:pointer; }
.gi-member-remove svg { width:12px; height:12px; }
.gi-member-remove:hover { background:rgba(225,29,72,.08); color:var(--like); }
.gi-actions { padding:12px 18px 0; display:flex; flex-direction:column; gap:8px; }
.gi-add-btn { display:flex; align-items:center; gap:8px; width:100%; background:none; border:1px dashed var(--div); border-radius:999px; padding:9px 14px; font-family:inherit; font-size:13.5px; font-weight:700; color:var(--maroon); cursor:pointer; }
.gi-add-btn:hover { background:var(--maroon-tint); }
.gi-add-btn svg { width:16px; height:16px; }
.gi-leave-btn { width:100%; background:none; border:none; padding:9px 14px; font-family:inherit; font-size:13.5px; font-weight:700; color:var(--like); cursor:pointer; text-align:left; border-radius:var(--r-sm); }
.gi-leave-btn:hover { background:rgba(225,29,72,.08); }
.gi-delete-btn { width:100%; background:none; border:none; padding:9px 14px; font-family:inherit; font-size:13.5px; font-weight:800; color:#fff; background:var(--like); cursor:pointer; text-align:left; border-radius:var(--r-sm); }
.gi-delete-btn:hover { background:#c11d4a; }

/* ── one-on-one thread ── */
/* .chat-thread is a fixed-height flex column (height set from JS —
   see sizeChatThread() in chat.js — on desktop; on mobile it's
   handled purely in CSS, see the mobile block below) so the header
   stays put, the message list is the only thing that scrolls, and
   the composer is always pinned to the bottom of the screen —
   instead of just trailing after the last message with dead space
   below it. */
.chat-thread { display:flex; flex-direction:column; overflow:hidden; background:var(--cream-card); }

/* .chat-hdr is the thread's one and only header — the generic
   #chat-sec-bar ("← Name") that every other section on this page
   uses is hidden the moment a thread is open (see the body:has()
   rule below) so there's just one back arrow / one name, not two
   stacked headers. */
body:has(.chat-thread) #chat-sec-bar { display:none; }
.chat-hdr { display:flex; align-items:center; gap:10px; padding:10px 16px; border-bottom:1px solid var(--line); flex:none; background:var(--translucent-bg); backdrop-filter:blur(8px); z-index:5; }
.chat-hdr-back { display:inline-flex; flex:none; align-items:center; justify-content:center; width:32px; height:32px; margin:-4px 0 -4px -6px; border-radius:50%; color:var(--ink); transition:background .12s ease; }
.chat-hdr-back:hover { background:var(--hover-bg); }
.chat-hdr-back svg { width:22px; height:22px; }
.chat-hdr .avatar { width:34px; height:34px; }
.chat-hdr .nm { font-weight:800; font-size:16px; }
.chat-hdr .pc-handle { font-size:13.5px; color:var(--muted); display:block; }

.chat-msgs { flex:1 1 auto; min-height:0; overflow-y:auto; -webkit-overflow-scrolling:touch; display:flex; flex-direction:column; padding:16px; overscroll-behavior:contain; scrollbar-width:none; -ms-overflow-style:none; }
.chat-msgs::-webkit-scrollbar { display:none; }
.chat-daydivider { text-align:center; font-size:12.5px; font-weight:700; color:var(--muted); margin:14px 0 10px; }
.chat-daydivider:first-child { margin-top:0; }
/* WhatsApp-style "N unread messages" pill dropped above the first
   message the recipient hadn't seen yet — snapshotted at load time
   (see unreadMsgIds in loadThread()) before we mark everything read,
   so it doesn't just vanish the instant the thread opens. */
.chat-unread-divider { display:flex; justify-content:center; margin:14px 0 10px; animation:oc-fade-in .2s ease both; }
.chat-unread-divider span {
  background:var(--cream-card); color:var(--muted); font-size:12px; font-weight:700;
  padding:6px 14px; border-radius:999px; box-shadow:0 1px 3px rgba(15,20,25,.12);
}

/* End-to-end encryption notice — a slim, dismissible-feeling pill
   (WhatsApp/Signal convention) sitting above the first message
   rather than a persistent banner that eats space forever. */
.chat-e2e-banner { display:flex; align-items:center; justify-content:center; gap:6px; text-align:center; font-size:12px; line-height:1.35; color:var(--muted); background:var(--hover-bg); border-radius:16px; padding:8px 14px; margin:0 auto 14px; max-width:90%; }
.chat-e2e-banner svg { width:13px; height:13px; flex:none; }
.chat-e2e-banner.pending { opacity:.8; }

/* Messages sent within a few minutes of each other by the same
   person are visually grouped (tight spacing, fully-rounded corners
   throughout) with the speech-bubble "tail" only on the last bubble
   of the group — same convention as iMessage/Telegram — instead of
   every single message getting its own tail and full gap. */
.msg-row { display:flex; align-items:flex-end; gap:6px; margin-top:2px; animation:oc-fade-in .15s ease; }
.msg-row.g-start { margin-top:14px; }
.chat-daydivider + .msg-row.g-start { margin-top:0; }
.msg-row.mine { justify-content:flex-end; }
.msg-bubble { max-width:72%; padding:9px 14px; border-radius:22px; font-size:14.5px; line-height:1.4; white-space:pre-wrap; word-wrap:break-word; box-shadow:0 1px 1px rgba(0,0,0,.04); }
/* Contains the floated .msg-meta (see below) so the bubble's
   background/padding wraps around it instead of the float poking out
   past the bubble's bottom edge. */
.msg-bubble::after { content:""; display:table; clear:both; }
.msg-row.mine .msg-bubble { background:var(--maroon); color:#fff; border-bottom-right-radius:18px; }
.msg-row.theirs .msg-bubble { background:var(--hover-bg); color:var(--ink); border-bottom-left-radius:18px; }
.msg-row.mine.g-end .msg-bubble { border-bottom-right-radius:4px; }
.msg-row.theirs.g-end .msg-bubble { border-bottom-left-radius:4px; }
.msg-undecryptable { display:inline-flex; align-items:center; gap:5px; font-style:italic; opacity:.75; font:inherit; color:inherit; background:none; border:none; padding:0; text-align:left; cursor:pointer; }
.msg-undecryptable:hover { opacity:1; text-decoration:underline; }
.msg-undecryptable svg { width:13px; height:13px; flex:none; }
.msg-deleted-note { font-style:italic; opacity:.65; font-size:13.5px; }

/* Per-message "···" — hidden until the row is hovered/tapped (or its
   own dropdown is open), same reveal pattern chat apps use so the
   thread isn't cluttered with a menu button on every single bubble. */
.msg-menu-wrap { flex:none; opacity:0; transition:opacity .12s ease; align-self:center; }
.msg-row:hover .msg-menu-wrap, .msg-menu-wrap.open { opacity:1; }
.msg-menu-wrap .pc-menu-btn { width:26px; height:26px; }
.msg-menu-wrap .pc-menu-btn svg { width:14px; height:14px; }
.msg-menu-wrap .pc-menu-dd { min-width:170px; }
/* Telegram/WhatsApp-style meta (clock time + read ticks) rendered
   *inside* the bubble at the end of the message text, instead of as
   a separate element floating outside it. float:right on the last
   inline element lets the surrounding text wrap around it naturally
   — short messages sit beside it on one line, longer ones wrap and
   it settles into the bottom-right corner on its own. Always shown
   (real chat apps don't hide the time on every bubble but the last
   one) rather than only on hover/group-end. */
.msg-meta {
  float:right; margin:4px 0 0 8px; font-size:11px; color:var(--muted); white-space:nowrap;
  display:inline-flex; align-items:center; gap:2px;
}
.msg-row.mine .msg-meta { color:rgba(255,255,255,.75); }
.msg-bubble-bare-media .msg-meta,
/* Bare-media bubble (photo/video, no caption) has no text flow for
   the meta to float alongside, so it docks as a small translucent
   chip over the media's bottom-right corner instead. */
.msg-meta-overlay {
  position:absolute; right:8px; bottom:8px; float:none; margin:0;
  background:rgba(15,20,25,.55); color:#fff; padding:2px 7px; border-radius:10px; z-index:1;
}
.msg-bubble-bare-media { position:relative; }
/* Read receipts — one check (still unread) or two (read), WhatsApp-
   style; tinted with the app's accent colour once read instead of
   WhatsApp's blue, to stay on-brand. */
.msg-ticks { width:15px; height:14px; color:inherit; display:inline-flex; align-items:center; flex:none; }
.msg-row.mine .msg-ticks.read, .msg-meta-overlay .msg-ticks.read { color:#8ee7ff; }
.msg-ticks svg { width:100%; height:100%; }

/* Typing indicator — three dots bouncing in sequence, styled as an
   ordinary incoming bubble so it slots into the thread exactly like
   a real message would (and gets pushed out by one the instant it
   arrives). */
/* Explicit intrinsic sizing (not just relying on flex defaults) so
   this bubble can never balloon to fill the message list's height —
   width:fit-content + flex:0 0 auto pins it to just its dots/mic-icon
   content, and max-height is a hard backstop. */
.chat-typing-bubble {
  padding:11px 16px; width:fit-content; max-width:fit-content; height:auto; max-height:38px;
  min-height:38px; flex:0 0 auto; align-self:flex-end; box-sizing:border-box;
  /* Was a plain block, so the dots (display:flex but with no cross-
     axis reference) could end up sitting at the top of the 38px box
     instead of centred, and the whole row wasn't guaranteed to clip
     to the bubble — on some flex contexts that showed as a big pale
     bubble with the dots rendering low/outside it. Making the bubble
     itself the flex container fixes both: it centres its one child
     and clips anything that doesn't fit. */
  display:inline-flex; align-items:center; justify-content:center; overflow:hidden;
}
#chat-typing-row { flex:0 0 auto; }
#chat-typing-row .msg-bubble { flex:0 0 auto; }
.chat-typing-dots { display:flex; align-items:center; gap:4px; line-height:0; }
.chat-typing-dots span { width:6px; height:6px; border-radius:50%; background:var(--muted); opacity:.5; animation:oc-typing-bounce 1.2s infinite ease-in-out; }
.chat-typing-dots span:nth-child(2) { animation-delay:.15s; }
.chat-typing-dots span:nth-child(3) { animation-delay:.3s; }
/* "Recording a voice message…" variant of the typing bubble — a
   small pulsing red dot next to the mic icon instead of the three
   bouncing dots, so it reads as distinct from ordinary typing. */
.chat-recording-indicator { display:flex; align-items:center; gap:7px; color:var(--muted); }
.chat-recording-indicator svg { width:16px; height:16px; flex:none; }
.chat-recording-pulse { width:8px; height:8px; border-radius:50%; background:#e0245e; flex:none; animation:oc-rec-pulse 1.1s ease-in-out infinite; }
@keyframes oc-rec-pulse { 0%,100% { opacity:.35; transform:scale(.8); } 50% { opacity:1; transform:scale(1); } }
@keyframes oc-typing-bounce {
  0%, 60%, 100% { transform:translateY(0); opacity:.5; }
  30% { transform:translateY(-4px); opacity:1; }
}

.chat-composer { display:flex; gap:8px; align-items:flex-end; padding:10px 16px; border-top:1px solid var(--line); flex:none; background:var(--cream-card); }
/* font-size:16px is load-bearing, not decorative — anything smaller
   makes iOS Safari zoom the whole page in on focus, which is what
   made the composer look like it needed scrolling to reach. */
.chat-composer textarea { flex:1; height:40px; max-height:120px; resize:none; border-radius:24px; padding:10px 16px; overflow-y:auto; transition:height .08s ease; font-size:16px; scrollbar-width:none; -ms-overflow-style:none; }
.chat-composer textarea::-webkit-scrollbar { display:none; }
.chat-send-btn { flex:none; width:38px; height:38px; border-radius:50%; border:none; background:var(--maroon); color:#fff; display:inline-flex; align-items:center; justify-content:center; cursor:pointer; transition:background .12s ease, opacity .1s ease, transform .1s ease; }
.chat-send-btn svg { width:17px; height:17px; margin-left:-1px; }
.chat-send-btn:hover { background:var(--maroon-dk); }
.chat-send-btn:active { transform:scale(.92); }
.chat-send-btn:disabled { opacity:.4; cursor:default; }

/* ── attach / mic toolbar buttons — same visual language as .pf-ic
   in the post composer (js/common.js), duplicated rather than shared
   since chat's toolbar sits in a different stylesheet section and
   has its own disabled state (mid-recording). ── */
.chat-tool-btn { flex:none; width:36px; height:36px; border-radius:50%; border:none; background:transparent; color:var(--maroon); display:inline-flex; align-items:center; justify-content:center; cursor:pointer; transition:background .12s ease; }
.chat-tool-btn svg { width:19px; height:19px; display:block; }
.chat-tool-btn:hover:not(:disabled) { background:var(--maroon-tint); }
.chat-tool-btn:disabled { color:#9EC8DE; cursor:default; }

/* ── pending-attachment preview, shown above the composer between
   picking/recording a file and actually hitting send ── */
.chat-attach-preview { padding:8px 16px 0; flex:none; }
.chat-attach-thumb { position:relative; display:inline-block; max-width:200px; max-height:160px; border-radius:14px; overflow:hidden; border:1px solid var(--line); }
.chat-attach-thumb img, .chat-attach-thumb video { display:block; max-width:200px; max-height:160px; object-fit:cover; }
.chat-attach-remove { position:absolute; top:6px; right:6px; width:26px; height:26px; border-radius:50%; border:none; background:rgba(0,0,0,.55); color:#fff; display:inline-flex; align-items:center; justify-content:center; cursor:pointer; }
.chat-attach-remove svg { width:14px; height:14px; }
.chat-attach-voice { position:relative; display:inline-flex; align-items:center; gap:10px; background:var(--hover-bg); border-radius:22px; padding:6px 40px 6px 6px; }
.chat-attach-voice .chat-attach-remove { position:static; margin-left:2px; background:transparent; color:var(--muted); }
.chat-attach-uploading { font-size:13px; color:var(--muted); padding:6px 2px; }

/* ── voice-note recording bar — replaces the composer row while
   MediaRecorder is active ── */
.chat-record-bar { display:flex; align-items:center; gap:10px; padding:9px 8px 9px 16px; margin:8px 10px; border-radius:999px; flex:none; background:var(--hover-bg); }
/* Both of the above set display:flex directly on the class, which (being
   author CSS) otherwise wins over the hidden attribute's UA-stylesheet
   display:none — same gotcha already worked around for .op-relevant-menu,
   .cx-sched/.cx-poll, .lb-panel-toggle, .lb-mobile-bar and .ttv-spinner
   above. Without this, toggling .hidden in JS (startVoiceRecording() /
   stopChatRecordUi() in chat.js) silently did nothing and the record bar
   rendered permanently open regardless of recording state. */
.chat-composer[hidden], .chat-record-bar[hidden] { display:none; }
.chat-record-dot { width:10px; height:10px; border-radius:50%; background:#e0245e; animation:oc-record-pulse 1.2s infinite ease-in-out; flex:none; }
@keyframes oc-record-pulse { 0%,100% { opacity:1; } 50% { opacity:.35; } }
.chat-record-time { font-variant-numeric:tabular-nums; font-weight:600; }
.chat-record-hint { color:var(--muted); font-size:13px; flex:1; text-align:center; }
.chat-record-cancel, .chat-record-stop { flex:none; width:34px; height:34px; border-radius:50%; border:none; display:inline-flex; align-items:center; justify-content:center; cursor:pointer; }
.chat-record-cancel { background:transparent; color:var(--muted); }
.chat-record-cancel svg { width:17px; height:17px; }
.chat-record-stop { background:var(--maroon); color:#fff; }
.chat-record-stop svg { width:14px; height:14px; }

/* ── media inside a message bubble ── */
.msg-bubble .pm { margin:0 0 6px; border-radius:14px; overflow:hidden; max-width:260px; }
.msg-bubble .pm img.chat-media-img { display:block; max-width:100%; max-height:320px; object-fit:cover; cursor:pointer; }
.msg-bubble .pm .ttv { max-width:260px; border-radius:14px; }
.msg-bubble-bare-media { background:transparent !important; padding:0 !important; box-shadow:none !important; }
.msg-bubble-bare-media .pm { margin:0; }

/* ── compact voice-note player (used both in the pending-attachment
   preview and in sent/received bubbles) ── */
.voice-msg { display:inline-flex; align-items:center; gap:8px; min-width:190px; }
.voice-play-btn { flex:none; width:30px; height:30px; border-radius:50%; border:none; background:rgba(255,255,255,.22); color:inherit; display:inline-flex; align-items:center; justify-content:center; cursor:pointer; }
.msg-row.theirs .voice-play-btn { background:var(--maroon-tint); color:var(--maroon); }
.voice-play-btn svg { width:14px; height:14px; margin-left:1px; }
/* Bar-style waveform (see voiceWaveformSvg() in chat.js) — two
   identical SVGs stacked, the top one clipped to the played fraction
   by width so the "played" bars read as a distinct, brighter colour
   from the "unplayed" ones, same visual language as WhatsApp/
   Telegram/Signal voice notes. */
.voice-wave { position:relative; flex:1; height:22px; overflow:hidden; }
.voice-wave-svg { display:block; width:100%; height:100%; fill:rgba(255,255,255,.35); }
.msg-row.theirs .voice-wave-svg { fill:var(--line); }
.voice-wave-fg-clip { position:absolute; inset:0; width:100%; height:100%; clip-path:inset(0 100% 0 0); }
.voice-wave-fg-clip .voice-wave-svg { fill:currentColor; }
.voice-time { flex:none; font-size:11.5px; font-variant-numeric:tabular-nums; opacity:.85; }

/* ── mobile: full-screen immersive thread ──
   Below 700px, an open thread (.chat-thread) takes over the whole
   screen — same convention as X/WhatsApp/iMessage on mobile web —
   instead of squeezing into the space left by the fixed top bar and
   bottom tab bar. That removes the composer's dependency on those
   other elements' heights entirely (previously it was positioned by
   subtracting fixed 53px/50px guesses from 100vh in JS, which fell
   apart the moment the on-screen keyboard opened and ate into the
   viewport without 100vh shrinking to match). Height instead comes
   off --vvh, the live window.visualViewport height that common.js
   keeps in sync — the same mechanism the global compose modal
   already relies on — so the composer rides right above the keyboard
   as it opens/closes rather than disappearing underneath it. :has()
   lets the header/tab bar hide purely in CSS, no JS state to keep in
   sync with which page/view is showing. */
@media (max-width:700px) {
  /* :has() drives this normally, but a handful of still-in-the-wild
     mobile browsers (older in-app/WebView shells in particular) don't
     support it — on those, none of these rules would ever match, the
     old fixed top/bottom bars would stay put, and the composer could
     end up rendered partly under them, which reads exactly like "have
     to scroll to reach it". loadThread()/loadConversationList() in
     chat.js toggle a matching .chat-thread-open class on <body> as a
     belt-and-suspenders fallback so this doesn't depend on :has()
     support at all. */
  body:has(.chat-thread) #m-topbar, body.chat-thread-open #m-topbar,
  body:has(.chat-thread) #m-tabbar, body.chat-thread-open #m-tabbar,
  body:has(.chat-thread) #m-fab, body.chat-thread-open #m-fab,
  body:has(.chat-thread) #chat-sec-bar, body.chat-thread-open #chat-sec-bar { display:none !important; }
  body:has(.chat-thread) #main, body.chat-thread-open #main { padding-top:0; padding-bottom:0; }
  body:has(.chat-thread) #feed, body.chat-thread-open #feed { border:none; min-height:0; }

  .chat-thread {
    position:fixed; inset:0; z-index:940;
    /* 100dvh first so a pre-JS/pre-vvh first paint (before common.js's
       syncViewportHeight() has run) still fills the real visible
       viewport instead of the address-bar-inclusive 100vh, on browsers
       new enough to support dvh — var(--vvh) then takes over as the
       live, keyboard-aware source of truth once JS is running. */
    height:100vh; height:100dvh; height:var(--vvh, 100dvh);
  }
  .chat-hdr { padding-top:calc(10px + env(safe-area-inset-top)); }
  .chat-composer { padding-bottom:calc(10px + env(safe-area-inset-bottom)); }

  .msg-bubble { max-width:80%; }
}


/* ═══════════════════════════════════════════════════════════════
   SETTINGS PAGE
   ═══════════════════════════════════════════════════════════════ */
/* ── Bluesky-style Settings menu: avatar/name header card, then a flat
   list of tappable rows (icon + label + chevron) that expand an
   accordion panel below themselves instead of showing every field on
   one long page. ── */
.settings-header-card { display:flex; align-items:center; gap:14px; padding:18px 16px; border-bottom:1px solid var(--line); }
.settings-header-name { font-size:17px; font-weight:800; color:var(--ink); }
.settings-header-handle { font-size:14px; color:var(--muted); }
.settings-menu { border-bottom:1px solid var(--line); }
.settings-menu-row {
  display:flex; align-items:center; gap:14px; width:100%; text-align:left;
  font-family:inherit; font-size:15.5px; font-weight:600; color:var(--ink);
  background:none; border:none; border-top:1px solid var(--line); padding:15px 16px;
  cursor:pointer;
}
.settings-menu-row:first-child { border-top:none; }
.settings-menu-row .smr-icon { flex:none; width:22px; height:22px; color:var(--muted); }
.settings-menu-row .smr-icon svg { width:100%; height:100%; fill:none; stroke:currentColor; stroke-width:1.8; }
.settings-menu-row .smr-label { flex:1; }
.settings-menu-row .smr-chevron { flex:none; width:18px; height:18px; color:var(--muted); transition:transform .15s ease; }
.settings-menu-row .smr-chevron svg { width:100%; height:100%; }
.settings-menu-row.open .smr-chevron { transform:rotate(90deg); }
.settings-menu-row:hover { background:var(--hover-bg); }
.settings-panel { display:none; padding:4px 16px 18px; border-top:1px solid var(--line); background:var(--hover-bg); }
.settings-panel.open { display:block; }

.settings-section { padding:16px; border-bottom:1px solid var(--line); }
.settings-section h2 { font-size:16px; font-weight:800; margin-bottom:10px; }
.settings-section .sub { font-size:13.5px; color:var(--muted); margin-bottom:12px; }
.settings-row { display:flex; align-items:center; justify-content:space-between; gap:12px; padding:10px 0; border-radius:8px; transition:background-color .3s ease; }
.settings-row-highlight { background-color:var(--accent-bg,rgba(29,155,240,0.12)); }
.settings-row .lbl { font-size:14.5px; }
.settings-row .val { font-size:14px; color:var(--muted); }

/* ── Appearance / theme swatch picker (Settings → Appearance) ── */
.theme-picker { display:flex; gap:12px; margin-top:4px; }
.theme-swatch {
  flex:1; border:2px solid var(--border-input); border-radius:var(--r-sm); padding:0; overflow:hidden;
  cursor:pointer; font-family:inherit; text-align:left; transition:border-color .12s ease;
}
.theme-swatch.active { border-color:var(--maroon); }
.theme-swatch .ts-preview { height:52px; display:flex; align-items:center; gap:6px; padding:0 10px; }
.theme-swatch .ts-preview span { display:block; width:16px; height:16px; border-radius:50%; }
.theme-swatch .ts-label { display:block; font-size:13px; font-weight:700; padding:8px 10px; color:var(--ink); background:var(--cream-card); }
.theme-swatch[data-theme-opt="auto"] .ts-preview { background:linear-gradient(135deg, #FFFFFF 50%, #15202B 50%); }
.theme-swatch[data-theme-opt="auto"] .ts-preview span { background:linear-gradient(135deg, #0F1419 50%, #F7F9F9 50%); }
.theme-swatch[data-theme-opt="light"] .ts-preview { background:#FFFFFF; }
.theme-swatch[data-theme-opt="light"] .ts-preview span { background:#0F1419; }
.theme-swatch[data-theme-opt="dim"] .ts-preview { background:#15202B; }
.theme-swatch[data-theme-opt="dim"] .ts-preview span { background:#F7F9F9; }
.theme-swatch[data-theme-opt="dark"] .ts-preview { background:#000000; }
.theme-swatch[data-theme-opt="dark"] .ts-preview span { background:#E7E9EA; }

/* ── Appearance / accent color picker (Settings → Appearance) ── */
.accent-picker { display:flex; gap:14px; margin-top:12px; }
.accent-swatch {
  width:32px; height:32px; border-radius:50%; border:2px solid transparent; padding:2px;
  cursor:pointer; background:var(--accent-swatch-color); position:relative; flex:none;
  transition:transform .12s ease;
}
.accent-swatch:hover { transform:scale(1.08); }
.accent-swatch.active { border-color:var(--ink); }
.accent-swatch.active::after {
  content:""; position:absolute; inset:6px; border-radius:50%; border:2px solid #fff;
}
.accent-swatch[data-accent-opt="blue"]   { --accent-swatch-color:#0EA5E9; }
.accent-swatch[data-accent-opt="red"]    { --accent-swatch-color:#DC2626; }
.accent-swatch[data-accent-opt="green"]  { --accent-swatch-color:#16A34A; }
.accent-swatch[data-accent-opt="purple"] { --accent-swatch-color:#7C3AED; }
.accent-swatch[data-accent-opt="orange"] { --accent-swatch-color:#F97316; }

/* ── TOGGLE SWITCH — used on settings.html for notification prefs ── */
.toggle { position:relative; display:inline-block; width:40px; height:24px; flex:none; cursor:pointer; }
.toggle input { opacity:0; width:0; height:0; position:absolute; }
.toggle-track {
  position:absolute; inset:0; background:#CFD9DE; border-radius:999px;
  transition:background-color .15s ease;
}
.toggle-track::before {
  content:""; position:absolute; left:3px; top:3px; width:18px; height:18px;
  background:#fff; border-radius:50%; box-shadow:0 1px 2px rgba(0,0,0,.25);
  transition:transform .15s ease;
}
.toggle input:checked + .toggle-track { background:var(--maroon); }
.toggle input:checked + .toggle-track::before { transform:translateX(16px); }
.toggle input:focus-visible + .toggle-track { box-shadow:0 0 0 2px var(--maroon-tint); }

#ep-hdr, #fl-hdr, #thread-hdr { align-items:center; gap:0; }
.sec-bar-title { display:flex; flex-direction:column; line-height:1.25; }
.sec-bar-title .sub { font-size:12.5px; }

/* ── "back arrow" circle button — editprofile.html / followlist.html /
   thread.html etc. Bumped up from a tiny 18px text arrow to a proper
   40px circular button with a crisp SVG chevron, so it's an easy,
   obvious tap target instead of a small glyph. ── */
.ep-back {
  width:40px; height:40px; border-radius:50%; display:flex; align-items:center; justify-content:center;
  color:var(--ink); text-decoration:none; margin-right:12px; flex:none;
  transition:background-color .12s ease, transform .1s cubic-bezier(.2,.8,.2,1);
}
.ep-back svg { width:21px; height:21px; }
.ep-back:hover { background:var(--hover-bg); text-decoration:none; }
.ep-back:active { transform:scale(.9); background:var(--hover-bg); }

/* ── SUSPENDED ACCOUNT NOTICE — shown in place of a profile's normal
   header/timeline when profiles.banned is true (see loadProfile() in
   js/profile.js). Matches X's own "Account suspended" screen. ── */
.susp-notice { padding:40px 16px 0; }
.susp-notice h1 { font-size:23px; font-weight:800; color:var(--ink); margin:0 0 8px; }
.susp-notice p { font-size:15px; color:var(--muted); line-height:1.4; margin:0; }
.susp-notice p a { color:var(--link); text-decoration:none; }
.susp-notice p a:hover { text-decoration:underline; }

/* ── EDIT PROFILE PAGE ── */
.ep-banner-wrap {
  position:relative; height:150px; background:var(--banner-img, linear-gradient(135deg,#BEE3F8,#E6D9F7)) center/cover no-repeat;
  margin-bottom:52px;
}
.ep-avatar {
  position:absolute; left:16px; bottom:-44px; width:88px; height:88px;
  border:4px solid var(--bg); box-shadow:0 0 0 1px var(--line); background:var(--hover-bg);
}
.ep-banner-pick, .ep-avatar-pick {
  position:absolute; display:flex; align-items:center; justify-content:center; cursor:pointer;
  background:rgba(15,20,25,.55); color:#fff; border-radius:50%; transition:background-color .12s ease;
}
.ep-banner-pick { top:50%; left:50%; transform:translate(-50%,-50%); width:40px; height:40px; }
.ep-banner-pick svg { width:20px; height:20px; }
.ep-avatar-pick { left:16px; bottom:-44px; width:88px; height:88px; opacity:0; }
.ep-avatar-pick:hover, .ep-banner-pick:hover { background:rgba(15,20,25,.7); }
.ep-avatar-pick:hover { opacity:1; }
.ep-avatar-pick svg { width:26px; height:26px; }
.ep-form { padding:0 16px 16px; }
.ep-form label { display:block; font-size:12px; font-weight:700; color:var(--muted); margin:14px 0 4px; }
.ep-form input[type="text"], .ep-form textarea { max-width:100%; }
.ep-form textarea { height:80px; }

/* ── FOLLOWERS / FOLLOWING LIST PAGE ── */
.fl-row { display:flex; align-items:center; gap:8px; padding:4px 16px; }
.fl-row .follow-btn { margin-top:0; flex:none; }

/* ═══════════════════════════════════════════════════════════════
   MEDIA LIGHTBOX — full-screen photo/video viewer (see renderMedia()
   / openLightbox() in common.js). Always a near-black stage
   regardless of the active theme (same as X's own photo modal);
   the side panel and mobile bar borrow the app's normal tokens so
   text/icons stay legible in Default, Dim, and Lights out alike.
   ═══════════════════════════════════════════════════════════════ */
.lb-bg {
  display:none; position:fixed; inset:0; z-index:3000;
  background:#000; flex-direction:column;
}
.lb-bg.open { display:flex; }

.lb-topbar {
  position:absolute; top:0; left:0; right:0; z-index:5;
  display:flex; align-items:center; justify-content:space-between;
  padding:10px 12px;
}
.lb-icon-btn {
  width:38px; height:38px; border-radius:50%; border:none; cursor:pointer;
  display:flex; align-items:center; justify-content:center;
  background:rgba(15,20,25,.55); color:#fff; transition:background-color .12s ease;
}
.lb-icon-btn:hover { background:rgba(15,20,25,.75); }
.lb-icon-btn svg { width:19px; height:19px; }
.lb-panel-toggle[hidden] { display:none; }
.lb-bg.panel-collapsed .lb-panel-toggle svg { transform:rotate(180deg); }

.lb-body { flex:1; display:flex; min-height:0; }

.lb-stage {
  flex:1; position:relative; display:flex; align-items:center; justify-content:center;
  overflow:hidden; touch-action:none; overscroll-behavior:contain;
}
.lb-media-wrap {
  max-width:100%; max-height:100%; display:flex; align-items:center; justify-content:center;
  will-change:transform; transition:transform .15s ease;
}
.lb-media-wrap.dragging { transition:none; }
.lb-media-wrap img, .lb-media-wrap video {
  max-width:calc(100vw - 32px); max-height:100vh; object-fit:contain;
  -webkit-user-drag:none; user-select:none; touch-action:none; display:block;
}
.lb-media-wrap img { cursor:zoom-in; }
.lb-media-wrap.dragging img { cursor:grabbing; }

.lb-sidebar {
  width:500px; max-width:38vw; flex-shrink:0; height:100%; overflow-y:auto;
  background:var(--bg); border-left:1px solid var(--line);
}
.lb-bg.panel-collapsed .lb-sidebar { display:none; }
.lb-sb-head { display:flex; align-items:flex-start; gap:10px; padding:14px 16px 0; }
.lb-sb-head .pc-avatar { width:40px; height:40px; }
.lb-sb-head .op-detail-names { padding-top:2px; }
.lb-sb-head .op-detail-names .nm { font-size:15px; }
.lb-sb-head .op-detail-names .verified-badge { width:16px; height:16px; margin-left:0; vertical-align:middle; flex:none; }
.lb-sb-head .op-detail-names .pc-handle { font-size:14px; }
.lb-sidebar .op-detail-body { padding:0 16px; margin-top:12px; font-size:17px; }
.lb-sidebar .op-detail-meta { padding:0 16px 12px; margin-top:12px; }
.lb-sidebar .op-detail-divider { margin:0 16px; }
.lb-sidebar .op-stats, .lb-sidebar .acts { padding:10px 16px; max-width:none; margin-top:0; }
.lb-sb-replybox {
  display:flex; align-items:center; gap:10px; padding:12px 16px; text-decoration:none;
  color:var(--muted); font-size:15px; border-bottom:1px solid var(--line);
}
.lb-sb-replybox img { width:32px; height:32px; border-radius:50%; }
.lb-sb-replybox:hover span { text-decoration:underline; }
.lb-sb-viewall {
  display:block; padding:14px 16px; color:var(--link); text-decoration:none; font-size:15px; font-weight:700;
}
.lb-sb-viewall:hover { text-decoration:underline; }

.lb-mobile-bar {
  display:none;
  padding:10px 16px calc(10px + env(safe-area-inset-bottom));
  background:linear-gradient(to top, rgba(0,0,0,.75), rgba(0,0,0,0));
}
.lb-mobile-bar[hidden] { display:none; }
.lb-mobile-bar .acts { max-width:none; }
.lb-mobile-bar .act { color:#fff; }
.lb-mobile-bar .act.liked { color:var(--like); }
.lb-mobile-bar .act.share:hover, .lb-mobile-bar .act.share:active { color:var(--share); }
.lb-mobile-bar .act.bookmarked, .lb-mobile-bar .act.bookmark:hover, .lb-mobile-bar .act.bookmark:active { color:var(--save); }

/* Wraps the caption overlay + action bar together at the foot of the
   stage so they stack (caption above actions) instead of both
   independently pinning to bottom:0 and overlapping. */
.lb-bottom-dock { position:absolute; left:0; right:0; bottom:0; z-index:4; display:flex; flex-direction:column; }

/* ── floating attribution card, overlaid directly on the media
   itself (mobile only) — avatar/name/handle/Follow + caption, same
   idea as X's fullscreen video view. Sits just above the action bar
   with its own gradient so it stays legible over any image/video. ── */
.lb-caption-overlay {
  display:none; padding:34px 14px 10px; pointer-events:none;
  background:linear-gradient(to top, rgba(0,0,0,.78), rgba(0,0,0,.35) 60%, rgba(0,0,0,0));
}
.lb-caption-overlay[hidden] { display:none !important; }
.lb-cap-row { display:flex; align-items:center; gap:8px; pointer-events:auto; }
.lb-cap-row .avatar { width:32px; height:32px; flex:none; }
.lb-cap-names { min-width:0; flex:1; display:flex; flex-direction:column; line-height:1.2; }
.lb-cap-names .nm { color:#fff; font-weight:800; font-size:14.5px; text-decoration:none; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.lb-cap-names .verified-badge { width:14px; height:14px; vertical-align:middle; margin-left:3px; }
.lb-cap-names .handle { color:rgba(255,255,255,.75); font-size:13px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.lb-cap-follow {
  flex:none; font-family:inherit; font-size:13px; font-weight:800; color:#fff; cursor:pointer;
  background:rgba(255,255,255,.16); border:1px solid rgba(255,255,255,.5); border-radius:999px;
  padding:6px 14px;
}
.lb-cap-follow.following { background:rgba(255,255,255,.05); }
.lb-cap-follow:disabled { opacity:.6; }
.lb-cap-text {
  color:#fff; font-size:14px; line-height:1.35; margin-top:8px; pointer-events:auto;
  white-space:pre-wrap; overflow:hidden; display:-webkit-box; -webkit-line-clamp:3; -webkit-box-orient:vertical;
}

@media (max-width:700px) {
  .lb-caption-overlay { display:block; }
}

@media (max-width:700px) {
  .lb-sidebar, .lb-panel-toggle { display:none !important; }
  .lb-mobile-bar { display:flex; }
  .lb-media-wrap img, .lb-media-wrap video { max-width:100vw; }
}

/* ═══════════════════════════════════════════════════════════════
   TTV — custom video player (replaces native <video controls>)
   "Reel rail" skin: no bottom scrim bar. A hairline white progress
   track sits flush along the top edge, a bare center triangle (no
   backdrop circle) handles tap-to-play/pause, the time readout sits
   bottom-left with a text-shadow for contrast, and mute/speed/
   fullscreen/PiP live in a small vertical icon rail docked to the
   right edge — each icon chip carries its own translucent
   background since there's no scrim behind it. White/icons
   regardless of light/dim/dark site theme, same as before.
   ═══════════════════════════════════════════════════════════════ */
.ttv {
  position:relative; display:block; width:max-content; max-width:100%;
  border-radius:20px; overflow:hidden; background:#000; outline:none;
  line-height:0; -webkit-tap-highlight-color:transparent;
}
.ttv:focus-visible { box-shadow:0 0 0 2px var(--maroon); }
.ttv-video { display:block; max-width:100%; max-height:380px; width:auto; height:auto; background:#000; outline:none; -webkit-tap-highlight-color:transparent; }
.ttv-video:focus, .ttv-video:focus-visible { outline:none; }
/* Suppresses any browser-native video overlay chrome (fullscreen/PiP/
   cast bubbles some Chromium-based mobile browsers draw on top of an
   HTML5 <video> regardless of the page's own controls) so only our
   custom .ttv-rail buttons ever show. */
.ttv-video::-webkit-media-controls,
.ttv-video::-webkit-media-controls-enclosure,
.ttv-video::-webkit-media-controls-panel,
.ttv-video::-webkit-media-controls-picture-in-picture-button,
.ttv-video::-webkit-media-controls-overflow-button,
.ttv-video::-internal-media-controls-overflow-button { display:none !important; opacity:0 !important; pointer-events:none !important; }
.rc .ttv-video, .qp-embed .ttv-video { max-height:280px; }
.lb-media-wrap .ttv { max-width:none; border-radius:0; }
.lb-media-wrap .ttv-video { max-height:calc(100vh - 40px); max-width:calc(100vw - 40px); }

.ttv-overlay {
  position:absolute; inset:0; cursor:pointer; background:transparent;
  display:flex; align-items:center; justify-content:center;
}
/* Dual-tone conic spinner (brand accent fading into white) instead
   of a flat white ring — reads as "loading", not "generic spinner". */
.ttv-spinner {
  position:absolute; top:50%; left:50%; transform:translate(-50%,-50%);
  width:34px; height:34px; border-radius:50%;
  background:conic-gradient(from 0deg, var(--maroon), rgba(255,255,255,.9) 40%, rgba(255,255,255,0) 78%);
  -webkit-mask:radial-gradient(farthest-side, transparent calc(100% - 3.5px), #000 calc(100% - 3.5px));
          mask:radial-gradient(farthest-side, transparent calc(100% - 3.5px), #000 calc(100% - 3.5px));
  animation:ttv-spin .85s cubic-bezier(.5,0,.5,1) infinite;
  filter:drop-shadow(0 2px 8px rgba(0,0,0,.35));
}
.ttv-spinner[hidden] { display:none; }
@keyframes ttv-spin { to { transform:translate(-50%,-50%) rotate(360deg); } }

/* ── BIG PLAY/PAUSE — bare triangle, no backdrop circle ──────────
   Tap-anywhere-on-the-video is the primary interaction (bound to
   .ttv-overlay in the JS), so this button is just a big invisible
   hit target with the icon centered inside it; a drop-shadow on the
   glyph itself (not a filled disc behind it) is what keeps it
   legible over bright video. Centered with absolute + transform
   (viewport/box-relative, independent of the video's own size or
   flex behavior) — this is what keeps it dead-centered on the
   player in every context, including fullscreen. */
.ttv-big-play {
  position:absolute; inset:0; margin:auto;
  width:56px; height:56px; border:none; cursor:pointer; background:none;
  color:#fff; display:flex; align-items:center; justify-content:center;
  transition:transform .15s ease, opacity .15s ease;
}
.ttv-big-play:hover { transform:scale(1.06); }
.ttv-big-play:active { transform:scale(.94); transition-duration:.1s; }
.ttv-big-play-icon {
  width:34px; height:34px; transform:translateX(.5px);
  filter:drop-shadow(0 1px 3px rgba(0,0,0,.45)); position:relative; z-index:1;
}
.ttv.ttv-playing .ttv-big-play { display:none; }
.ttv.ttv-buffering .ttv-big-play { display:none; }

/* full-bleed overlay wrapper — top progress line, bottom-left time,
   right-edge icon rail. No background of its own (each child carries
   its own contrast treatment); fades out together while playing +
   idle, same as before. */
.ttv-controls {
  position:absolute; inset:0; z-index:2; pointer-events:none;
  opacity:1; transition:opacity .18s ease;
}
.ttv-controls > * { pointer-events:auto; }
.ttv.ttv-playing.ttv-idle .ttv-controls { opacity:0; }
.ttv.ttv-playing.ttv-idle { cursor:none; }
.ttv.ttv-menu-open .ttv-controls { opacity:1; }

/* hairline progress track flush along the top edge */
.ttv-progress {
  position:absolute; left:8px; right:8px; top:0; height:16px; cursor:pointer; touch-action:none;
}
.ttv-progress-track {
  position:absolute; left:0; right:0; top:6px;
  height:2.5px; border-radius:2.5px; background:rgba(255,255,255,.3);
  transition:height .1s;
}
.ttv-progress:hover .ttv-progress-track, .ttv-progress.ttv-dragging .ttv-progress-track { height:4.5px; }
.ttv-progress-buffered {
  position:absolute; left:0; top:0; bottom:0; width:0%; border-radius:2.5px;
  background:rgba(255,255,255,.45);
}
.ttv-progress-played {
  position:absolute; left:0; top:0; bottom:0; width:0%; border-radius:2.5px;
  background:#fff;
}
.ttv-progress-handle {
  position:absolute; top:0; left:0%; width:11px; height:11px; border-radius:50%;
  background:#fff; transform:translate(-50%,-50%) scale(0);
  transition:transform .1s; box-shadow:0 0 0 1px rgba(0,0,0,.2);
}
.ttv-progress:hover .ttv-progress-handle, .ttv-progress.ttv-dragging .ttv-progress-handle { transform:translate(-50%,-50%) scale(1); }
.ttv-progress-preview {
  position:absolute; top:16px; transform:translateX(-50%);
  background:rgba(15,20,25,.9); color:#fff; font-size:12px; font-weight:700;
  padding:3px 6px; border-radius:6px; white-space:nowrap; pointer-events:none;
  opacity:0; transition:opacity .1s;
}
.ttv-progress:hover .ttv-progress-preview, .ttv-progress.ttv-dragging .ttv-progress-preview { opacity:1; }

/* time readout, bottom-left — text-shadow instead of a background
   chip since there's no scrim bar behind it anymore */
.ttv-time {
  position:absolute; left:10px; bottom:10px; pointer-events:none; /* label only — taps under it fall through to tap-to-play */
  color:#fff; font-size:12.5px; font-weight:700; user-select:none;
  font-variant-numeric:tabular-nums; white-space:nowrap;
  text-shadow:0 1px 4px rgba(0,0,0,.65), 0 0 1px rgba(0,0,0,.5);
}

/* vertical icon rail, docked to the right edge */
.ttv-rail { position:absolute; right:8px; bottom:12px; display:flex; flex-direction:column; align-items:center; gap:8px; }
.ttv-btn {
  flex:none; width:32px; height:32px; border-radius:50%; border:none; cursor:pointer;
  background:rgba(0,0,0,.4); color:#fff; display:flex; align-items:center; justify-content:center;
  position:relative;
}
.ttv-btn:hover { background:rgba(0,0,0,.6); }
.ttv-btn svg { width:17px; height:17px; }
.ttv-speed { width:32px; padding:0; font-size:12px; font-weight:700; border-radius:50%; }

/* Inline (non-fullscreen) rail is trimmed down to just the
   fullscreen toggle — mute/speed/PiP only reappear once the player
   is actually fullscreened (see the .ttv:fullscreen override
   below). Fullscreen/shorts-mode layout is untouched. */
.ttv-mute, .ttv-pip, .ttv-speed { display:none; }
.ttv:fullscreen .ttv-mute, .ttv:fullscreen .ttv-pip, .ttv:fullscreen .ttv-speed,
.ttv:-webkit-full-screen .ttv-mute, .ttv:-webkit-full-screen .ttv-pip, .ttv:-webkit-full-screen .ttv-speed {
  display:flex;
}

.ttv-menu {
  display:none; position:absolute; right:46px; bottom:12px; z-index:6;
  background:rgba(21,24,28,.96); backdrop-filter:blur(6px); border-radius:16px;
  min-width:120px; padding:6px; box-shadow:0 4px 18px rgba(0,0,0,.4);
}
.ttv.ttv-menu-open .ttv-menu { display:block; }
.ttv-menu-opt {
  display:flex; align-items:center; justify-content:space-between; gap:10px;
  width:100%; background:none; border:none; color:#fff; font-size:13px; font-weight:600;
  padding:7px 10px; border-radius:12px; cursor:pointer; text-align:left; font-family:inherit;
}
.ttv-menu-opt:hover { background:rgba(255,255,255,.12); }
.ttv-menu-opt .ck { width:14px; height:14px; opacity:0; }
.ttv-menu-opt.active .ck { opacity:1; }

/* FULLSCREEN — the browser only stretches the .ttv wrapper itself to
   fill the screen; without this the <video> stayed capped at its
   feed-sized max-height (380px) and sat small in a corner, while the
   center play button (centered on the wrapper) ended up floating in
   the middle of the empty black space instead of over the video.
   Flex-centering the wrapper and letting the video grow to fill it
   (object-fit:contain, so aspect ratio is preserved with letterbox
   bars instead of stretching) keeps the video and the button locked
   to the same center point. */
.ttv:fullscreen, .ttv:-webkit-full-screen {
  width:100% !important; height:100% !important; max-width:none !important; border-radius:0 !important;
  display:flex; align-items:center; justify-content:center;
}
.ttv:fullscreen .ttv-video, .ttv:-webkit-full-screen .ttv-video {
  max-width:100% !important; max-height:100% !important; width:auto !important; height:auto !important; object-fit:contain;
}
.ttv:fullscreen .ttv-big-play, .ttv:-webkit-full-screen .ttv-big-play {
  width:96px !important; height:96px !important;
}
.ttv:fullscreen .ttv-big-play-icon, .ttv:-webkit-full-screen .ttv-big-play-icon {
  width:40px !important; height:40px !important;
}

/* FULLSCREEN CONTROLS — same rail layout, scaled up a little and
   always visible (no idle fade-out) since there's no page content
   underneath to reveal. */
.ttv:fullscreen .ttv-controls, .ttv:-webkit-full-screen .ttv-controls {
  opacity:1 !important;
}
.ttv:fullscreen.ttv-playing.ttv-idle, .ttv:-webkit-full-screen.ttv-playing.ttv-idle { cursor:default; }

.ttv:fullscreen .ttv-progress, .ttv:-webkit-full-screen .ttv-progress { left:16px; right:16px; top:0; height:20px; }
.ttv:fullscreen .ttv-progress-track, .ttv:-webkit-full-screen .ttv-progress-track,
.ttv:fullscreen .ttv-progress:hover .ttv-progress-track, .ttv:-webkit-full-screen .ttv-progress:hover .ttv-progress-track,
.ttv:fullscreen .ttv-progress.ttv-dragging .ttv-progress-track, .ttv:-webkit-full-screen .ttv-progress.ttv-dragging .ttv-progress-track {
  top:9px; height:3px;
}

.ttv:fullscreen .ttv-time, .ttv:-webkit-full-screen .ttv-time {
  left:20px; bottom:24px; font-size:14px;
}
.ttv:fullscreen .ttv-rail, .ttv:-webkit-full-screen .ttv-rail { right:20px; bottom:24px; gap:12px; }
.ttv:fullscreen .ttv-btn, .ttv:-webkit-full-screen .ttv-btn { width:38px; height:38px; }
.ttv:fullscreen .ttv-btn svg, .ttv:-webkit-full-screen .ttv-btn svg { width:20px; height:20px; }
.ttv:fullscreen .ttv-speed, .ttv:-webkit-full-screen .ttv-speed { width:38px; font-size:13px; }
.ttv:fullscreen .ttv-menu, .ttv:-webkit-full-screen .ttv-menu { right:58px; bottom:24px; }

/* SHORTS MODE — see ttvEnterShorts()/ttvExitShorts() in
   video-player.js. Only ever visible while a .ttv with post context
   is browser-fullscreened (`.ttv-shorts-active:fullscreen`); the rest
   of the time it's display:none, so a normal inline player in the
   feed looks exactly as it did before — no rail, no avatar, no
   caption overlay. Going fullscreen is what turns a feed video into
   the vertical Shorts-style experience; leaving fullscreen hands it
   straight back. */
.ttv-shorts { display:none; }
.ttv.ttv-shorts-active:fullscreen .ttv-shorts,
.ttv.ttv-shorts-active:-webkit-full-screen .ttv-shorts {
  display:block; position:absolute; inset:0; z-index:5; pointer-events:none;
}

/* Playback controls (mute/speed/exit-fullscreen/PiP) move to the left
   edge in shorts mode so they don't collide with the action rail,
   which owns the right edge the way it does on TikTok/Shorts. */
.ttv.ttv-shorts-active:fullscreen .ttv-rail,
.ttv.ttv-shorts-active:-webkit-full-screen .ttv-rail { right:auto; left:16px; gap:10px; }
/* Time readout used to sit bottom-right, inherited from the base
   fullscreen rule — directly under the like/reply/share/save rail,
   which the "save" button's label text would render on top of.
   Move it up to the top-right corner in shorts mode instead, clear
   of both rails entirely. */
.ttv.ttv-shorts-active:fullscreen .ttv-time,
.ttv.ttv-shorts-active:-webkit-full-screen .ttv-time { left:auto; right:16px; bottom:auto; top:16px; z-index:6; }
.ttv.ttv-shorts-active:fullscreen .ttv-menu,
.ttv.ttv-shorts-active:-webkit-full-screen .ttv-menu { left:58px; right:auto; }

/* .ttv-shorts-meta (handle/caption) kept its feed-mode left:16px inset,
   which is exactly where the playback rail lands once it moves to the
   left edge above — the old mute/speed/exit/PiP button stack sat right
   on top of the handle and caption text. Give it the same clearance on
   the left that it already had on the right (84px, sized for the like/
   reply/share/save rail) so the two don't overlap. */
.ttv.ttv-shorts-active:fullscreen .ttv-shorts-meta,
.ttv.ttv-shorts-active:-webkit-full-screen .ttv-shorts-meta { left:84px; }

.ttv-shorts-rail {
  position:absolute; right:14px; bottom:140px;
  display:flex; flex-direction:column; align-items:center; gap:18px;
  pointer-events:auto;
}
.ttv-shorts-avatar {
  display:block; width:42px; height:42px; border-radius:50%;
  border:2px solid #fff; overflow:hidden; margin-bottom:2px;
  background:#333;
}
.ttv-shorts-avatar img { width:100%; height:100%; object-fit:cover; display:block; }

.ttv-shorts-btn {
  all:unset; cursor:pointer;
  display:flex; flex-direction:column; align-items:center; gap:4px;
}
.ttv-shorts-btn svg { width:26px; height:26px; fill:none; stroke:#fff; stroke-width:1.8; filter:drop-shadow(0 1px 3px rgba(0,0,0,.45)); }
.ttv-shorts-count { font-size:11px; font-weight:700; color:#fff; text-shadow:0 1px 3px rgba(0,0,0,.5); }

.ttv-shorts-like svg { transition:fill .15s, stroke .15s, transform .15s; }
.ttv-shorts-like.active svg { fill:#fe2c55; stroke:#fe2c55; transform:scale(1.08); }
.ttv-shorts-bookmark svg { transition:fill .15s; }
.ttv-shorts-bookmark.active svg { fill:#fff; }
.ttv-shorts-share svg { fill:#fff; stroke:none; }

.ttv-shorts-meta {
  position:absolute; left:16px; right:84px; bottom:60px;
  pointer-events:auto;
}
.ttv-shorts-handle {
  display:block; color:#fff; font-weight:700; font-size:14px;
  text-decoration:none; margin-bottom:6px; text-shadow:0 1px 3px rgba(0,0,0,.5);
}
.ttv-shorts-caption {
  margin:0; color:#f2f2f2; font-size:13px; line-height:1.4;
  text-shadow:0 1px 3px rgba(0,0,0,.5);
  display:-webkit-box; -webkit-line-clamp:3; -webkit-box-orient:vertical; overflow:hidden;
}

/* ── CROP MODAL — shared "Edit media" step (crop-modal.js) used
   before any image becomes an avatar or banner: profile, community,
   or List. Dark, full-bleed, matches Twitter's own edit-media UI. ── */
.cm-modal-bg {
  position:fixed; inset:0; background:rgba(0,0,0,.75); z-index:1200;
  display:none; align-items:center; justify-content:center; padding:16px;
}
.cm-modal-bg.open { display:flex; }
.cm-modal {
  width:100%; max-width:520px; background:#000; border-radius:var(--r-lg,24px);
  overflow:hidden; display:flex; flex-direction:column;
}
.cm-modal-hdr {
  display:flex; align-items:center; justify-content:space-between;
  padding:10px 14px; color:#fff;
}
.cm-back {
  background:none; border:none; color:#fff; cursor:pointer; padding:8px;
  border-radius:50%; display:flex; align-items:center; justify-content:center;
  transition:background-color .12s ease;
}
.cm-back:hover { background:rgba(255,255,255,.1); }
.cm-back svg { width:20px; height:20px; }
.cm-title { font-size:15px; font-weight:800; }
.cm-apply {
  background:#fff; color:#000; border:none; border-radius:999px;
  font-size:14px; font-weight:700; padding:7px 16px; cursor:pointer;
}
.cm-apply:hover { background:#e2e2e2; }
.cm-stage {
  position:relative; height:min(60vh,420px); background:#333;
  overflow:hidden; display:flex; align-items:center; justify-content:center;
  cursor:grab; touch-action:none;
}
.cm-stage:active { cursor:grabbing; }
.cm-img {
  position:absolute; top:50%; left:50%; max-width:none;
  user-select:none; -webkit-user-drag:none;
}
.cm-frame {
  position:absolute; top:50%; left:50%; pointer-events:none;
  width:var(--cm-frame-w); height:var(--cm-frame-h);
  transform:translate(-50%,-50%);
  box-shadow:0 0 0 2000px rgba(0,0,0,.55);
  border:1px solid rgba(255,255,255,.9);
}
.cm-frame-square { border-radius:50%; }
.cm-frame-wide { border-radius:10px; }
.cm-controls {
  display:flex; align-items:center; gap:10px; padding:14px 20px 18px; background:#000;
}
.cm-zoom-ic { width:15px; height:15px; color:#71767b; flex:none; }
.cm-zoom-ic-lg { width:19px; height:19px; }
.cm-controls input[type="range"] {
  flex:1; accent-color:var(--maroon); cursor:pointer;
}

@media (max-width:520px) {
  .cm-modal { border-radius:0; max-width:none; height:100%; }
  .cm-modal-bg { padding:0; }
  .cm-stage { height:auto; flex:1; }
}

/* ═══════════════════════════════════════════════════════════════
   REFERENCE REDESIGN
   Two looks, mapped to two page groups so the rest of the app is
   untouched:
     • Profile page      → monochrome, X/Bluesky-style header —
       circular avatar overlapping the banner, underline tabs,
       solid black "Follow" pill.
     • Community pages   → warm "Skale" look — squircle community
       icon in an orange gradient, faint sunburst banner lines,
       bold stat row, solid black pill CTAs/active tabs.
   ═══════════════════════════════════════════════════════════════ */

/* ── PROFILE: avatar is circular by default, matching the small
   avatars elsewhere — except gold-verified accounts, which get the
   squared-off treatment here too via .avatar-square (see badgeType()/
   avSqClass() in js/common.js), same as posts/replies/search results.
   Just made larger and pulled further up over the banner to match
   the photo's proportions. Only the avatar itself overlaps the banner
   (via its own negative margin) — .profile-hdr-top stays in normal
   flow so the Follow/Edit profile button next to it always sits
   clear of the banner instead of getting dragged up onto it. ── */
.profile-hdr .avatar.pfp-lg {
  width:112px; height:112px; border-radius:50%;
  border:4px solid var(--bg); box-shadow:0 1px 3px rgba(15,20,25,.15);
  margin-top:-62px;
}
.profile-hdr .avatar.pfp-lg.avatar-square { border-radius:22%; }
.profile-hdr-top { margin-top:14px; }
.profile-hdr::before { height:220px; }
@media (max-width:480px) {
  .profile-hdr .avatar.pfp-lg { width:88px; height:88px; margin-top:-46px; }
  .profile-hdr-top { margin-top:12px; }
  .profile-hdr::before { height:180px; }
}

/* ── PROFILE: flat underline tabs, evenly spread across the full
   width (Posts / Replies / Media / Likes) instead of the pill/
   segmented track used elsewhere (search, follower lists). ── */
.profile-tabs .xtabs {
  background:none; border:none; border-bottom:1px solid var(--line);
  border-radius:0; padding:0; gap:0;
}
.profile-tabs .xtab {
  padding:14px 4px; border-radius:0; font-weight:700; color:var(--muted); font-size:14.5px;
}
.profile-tabs .xtab:hover { color:var(--ink); background:var(--hover-bg); }
.profile-tabs .xtab.active { background:none; color:var(--ink); box-shadow:none; position:relative; }
.profile-tabs .xtab.active:hover { background:var(--hover-bg); }
.profile-tabs .xtab.active::after {
  content:""; position:absolute; left:14px; right:14px; bottom:0;
  height:3px; border-radius:3px 3px 0 0; background:var(--ink);
}

/* ── HOME: same flat underline tab style as the profile page's
   Posts/Replies tabs, instead of the pill/segmented track — the
   sliding pill indicator is switched off here and the active tab
   gets a static underline like .profile-tabs above. ── */
body[data-page="home"] #board-hdr .xtabs {
  background:none; border:none; border-bottom:1px solid var(--line);
  border-radius:0; padding:0; gap:0;
}
body[data-page="home"] #board-hdr .xtab {
  padding:14px 4px; border-radius:0; font-weight:700; color:var(--muted); font-size:14.5px;
}
body[data-page="home"] #board-hdr .xtab:hover { color:var(--ink); background:var(--hover-bg); }
body[data-page="home"] #board-hdr .xtab.active { background:none; color:var(--ink); box-shadow:none; position:relative; }
body[data-page="home"] #board-hdr .xtab.active:hover { background:var(--hover-bg); }
body[data-page="home"] #board-hdr .xtab.active::after {
  content:""; position:absolute; left:14px; right:14px; bottom:0;
  height:3px; border-radius:3px 3px 0 0; background:var(--ink);
}
body[data-page="home"] #board-hdr .xtabs-indicator { display:none; }

/* ── COMMUNITY / COMMUNITIES: squircle community icon in the warm
   orange gradient from the reference, plus a faint sunburst-line
   banner. Scoped to these two pages only — the accent color a
   person picked in Settings still governs the rest of the app. ── */
body[data-page="community"] .comm-avatar,
body[data-page="communities"] .comm-avatar {
  border-radius:24%;
  background:linear-gradient(135deg,#FF8A50,#FF3D1F);
}
body[data-page="community"] .comm-avatar img,
body[data-page="communities"] .comm-avatar img { border-radius:22%; }
body[data-page="community"] .comm-avatar-pick { border-radius:24%; }

body[data-page="community"] .comm-avatar-wrap,
body[data-page="community"] .comm-avatar-lg,
body[data-page="community"] .community-hero .comm-avatar {
  width:76px; height:76px; font-size:28px;
}
body[data-page="community"] .community-hero .comm-avatar {
  border:4px solid var(--bg); box-shadow:0 1px 4px rgba(15,20,25,.15);
}
body[data-page="community"] .community-hero-top {
  margin-top:-38px; position:relative; z-index:1;
}
body[data-page="community"] .community-banner-wrap {
  height:150px;
  background-image:
    var(--banner-img, none),
    repeating-conic-gradient(from 0deg at 16% 128%, rgba(255,90,31,.18) 0deg 1deg, transparent 1deg 8deg);
  background-position:center, 16% 128%;
  background-size:cover, 220% 220%;
  background-color:var(--hover-bg);
  background-repeat:no-repeat, no-repeat;
}
body[data-page="community"] .community-hero-name { font-size:21px; }
body[data-page="community"] .community-hero-meta {
  display:flex; gap:16px; font-size:14px; color:var(--ink); margin-top:8px;
}
body[data-page="community"] .community-hero-meta b { color:var(--ink); font-weight:800; }
body[data-page="community"] .comm-join-btn,
body[data-page="community"] .comm-leave-btn { padding:9px 20px; font-weight:800; }

/* Board tabs (Top/Latest/Media/About, All/Joined) → plain track with
   a solid black active pill, matching the reference's
   Community/Design/Education row instead of the app's usual green. */
body[data-page="community"] .sec-bar { padding:8px 16px; font-size:13px; border-bottom:none; }
/* Was `background:none; backdrop-filter:none` — #board-hdr is still
   `position:sticky;top:0` (set once, above), so stripping its
   background here left the pinned tab row with nothing behind it:
   scrolling content ran straight through the transparent bar and
   got visually cut by the opaque active-tab pill sitting on top of
   it. Every other sticky header in the app (.search-topbar,
   .chat-hdr, the default #board-hdr) uses this same translucent
   blur so scrolled content fades under it instead — matching that
   here fixes the overlap instead of just hiding it. */
body[data-page="community"] #board-hdr,
body[data-page="communities"] #board-hdr {
  background:var(--translucent-bg); backdrop-filter:blur(8px); -webkit-backdrop-filter:blur(8px);
  border:none; padding:10px 16px 4px;
}
body[data-page="community"] .xtabs, body[data-page="communities"] .xtabs { padding:3px; }
body[data-page="community"] .xtab.active, body[data-page="communities"] .xtab.active { background:var(--ink); color:var(--bg); }
body[data-page="community"] .xtab.active:hover, body[data-page="communities"] .xtab.active:hover { background:var(--ink); filter:brightness(.85); }

@media (max-width:480px) {
  body[data-page="community"] .comm-avatar-wrap,
  body[data-page="community"] .comm-avatar-lg,
  body[data-page="community"] .community-hero .comm-avatar { width:60px; height:60px; font-size:22px; }
  body[data-page="community"] .community-hero-top { margin-top:-30px; }
  body[data-page="community"] .community-banner-wrap { height:110px; }
  body[data-page="community"] .community-hero-actions .profile-icon-btn { width:32px; height:32px; }
  body[data-page="community"] .community-hero-actions .profile-icon-btn svg { width:16px; height:16px; }
  body[data-page="community"] .comm-join-btn, body[data-page="community"] .comm-leave-btn { padding:7px 14px; font-size:13px; }
}

/* ══════════════════════════════════════════════════════════════════════
   HELP CENTER — hub, category, and article pages (help/*.html).
   Deliberately steps outside the app's saturated accent color: every
   category gets its own quiet, editorial tone (moss / plum / rust /
   bronze / slate-teal) used sparingly — a thin top rule, an icon fill
   on hover, a kicker dot — never as a wall of text color. Headings and
   body copy stay on --ink/--muted throughout, so nothing in here reads
   as "an accent-colored link" unless it actually is one. */

:root {
  --cat-using:      #3F6C51; --cat-using-tint:     rgba(63,108,81,.13);
  --cat-account:    #6B4F84; --cat-account-tint:   rgba(107,79,132,.13);
  --cat-safety:     #A85C3B; --cat-safety-tint:    rgba(168,92,59,.13);
  --cat-rules:      #8A7B3D; --cat-rules-tint:     rgba(138,123,61,.13);
  --cat-resources:  #3C7078; --cat-resources-tint: rgba(60,112,120,.13);
}
html[data-theme="dim"], html[data-theme="dark"] {
  --cat-using:#5C9575; --cat-account:#9179B0; --cat-safety:#CB8562; --cat-rules:#B5A35F; --cat-resources:#5E9BA3;
}
[data-cat="using"]      { --cat-c:var(--cat-using);     --cat-tint:var(--cat-using-tint); }
[data-cat="account"]    { --cat-c:var(--cat-account);   --cat-tint:var(--cat-account-tint); }
[data-cat="safety"]     { --cat-c:var(--cat-safety);    --cat-tint:var(--cat-safety-tint); }
[data-cat="rules"]      { --cat-c:var(--cat-rules);     --cat-tint:var(--cat-rules-tint); }
[data-cat="resources"]  { --cat-c:var(--cat-resources); --cat-tint:var(--cat-resources-tint); }

/* ── hero / search ───────────────────────────────────────────────── */
.help-hero {
  padding:40px 20px 28px; text-align:center; border-bottom:1px solid var(--line);
  background:
    radial-gradient(480px 200px at 18% 0%, var(--hover-bg), transparent 70%),
    radial-gradient(480px 200px at 82% 0%, var(--hover-bg), transparent 70%);
}
.help-hero h1 { margin:0 0 8px; font-size:30px; font-weight:800; letter-spacing:-.02em; }
.help-hero p { margin:0 auto; color:var(--muted); font-size:15px; max-width:440px; line-height:1.5; }

.help-search-wrap { position:relative; max-width:520px; margin:22px auto 0; text-align:left; }
.help-search-box {
  display:flex; align-items:center; gap:10px; background:var(--cream-card);
  border:1.5px solid var(--line); border-radius:999px; padding:13px 18px;
  box-shadow:0 1px 2px rgba(15,20,25,.04); transition:border-color .15s ease, box-shadow .15s ease;
}
.help-search-box:focus-within {
  border-color:var(--border-input); box-shadow:0 4px 18px rgba(15,20,25,.10);
}
.help-search-box svg { flex:none; width:18px; height:18px; stroke:var(--muted); fill:none; stroke-width:2; }
.help-search-box input {
  flex:1; border:none; background:none; outline:none; font:inherit; font-size:15px; color:var(--ink);
}
.help-search-box input::placeholder { color:var(--muted); }
.help-search-clear {
  flex:none; display:none; width:20px; height:20px; border:none; border-radius:50%; cursor:pointer;
  background:var(--hover-bg); color:var(--muted); align-items:center; justify-content:center; font-size:13px;
}
.help-search-box.has-query .help-search-clear { display:flex; }

.help-search-results {
  position:absolute; left:0; right:0; top:calc(100% + 8px); z-index:40; background:var(--cream-card);
  border:1px solid var(--line); border-radius:var(--r-md); box-shadow:var(--shadow-pop);
  max-height:400px; overflow-y:auto; display:none; padding:6px;
}
.help-search-results.open { display:block; }
.help-search-result {
  display:flex; align-items:flex-start; gap:10px; padding:10px 12px; border-radius:var(--r-sm);
  text-decoration:none; color:var(--ink); cursor:pointer;
}
.help-search-result:hover, .help-search-result.active { background:var(--hover-bg); }
.help-search-result .dot { flex:none; width:8px; height:8px; border-radius:50%; margin-top:6px; background:var(--cat-c,var(--muted)); }
.help-search-result .txt { min-width:0; }
.help-search-result .txt strong { display:block; font-size:14px; font-weight:700; }
.help-search-result .txt span { display:block; font-size:12.5px; color:var(--muted); margin-top:2px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.help-search-result .cat-label { flex:none; font-size:11px; font-weight:700; color:var(--cat-c,var(--muted)); background:var(--cat-tint,var(--hover-bg)); padding:3px 8px; border-radius:999px; margin-top:1px; }
.help-search-empty { padding:18px 14px; text-align:center; font-size:13.5px; color:var(--muted); }

/* ── breadcrumb ──────────────────────────────────────────────────── */
.help-breadcrumb {
  display:flex; flex-wrap:wrap; align-items:center; gap:6px; padding:16px 20px 0;
  font-size:13px; color:var(--muted); font-weight:600;
}
.help-breadcrumb a { color:var(--muted); text-decoration:none; }
.help-breadcrumb a:hover { color:var(--ink); text-decoration:underline; }
.help-breadcrumb .sep { color:var(--line); }
.help-breadcrumb .current { color:var(--ink); }

/* ── category grid (hub page) ───────────────────────────────────── */
.help-grid { display:grid; grid-template-columns:repeat(auto-fill,minmax(230px,1fr)); gap:14px; padding:26px 20px; }
.help-card {
  position:relative; display:block; text-decoration:none; color:var(--ink); background:var(--cream-card);
  border:1px solid var(--line); border-top:3px solid var(--cat-c,var(--line)); border-radius:var(--r-lg);
  padding:20px; overflow:hidden; transition:transform .15s ease, box-shadow .15s ease, border-color .15s ease;
}
.help-card:hover { text-decoration:none; transform:translateY(-3px); box-shadow:0 10px 24px rgba(15,20,25,.08); }
.help-card:active { transform:translateY(-1px) scale(.99); }
.help-card-icon {
  width:42px; height:42px; border-radius:50%; background:var(--cat-tint,var(--hover-bg)); color:var(--cat-c,var(--ink));
  border:1px solid var(--line); display:flex; align-items:center; justify-content:center; margin-bottom:14px;
  transition:background-color .15s ease, color .15s ease;
}
.help-card:hover .help-card-icon { background:var(--cat-c,var(--ink)); color:#fff; border-color:transparent; }
.help-card-icon svg { width:20px; height:20px; stroke:currentColor; fill:none; stroke-width:1.8; }
.help-card h3 { margin:0 0 5px; font-size:16.5px; font-weight:800; letter-spacing:-.01em; }
.help-card p { margin:0; font-size:13.5px; color:var(--muted); line-height:1.5; }
.help-card .help-card-count {
  display:inline-block; margin-top:14px; font-size:11px; font-weight:700; color:var(--muted);
  text-transform:uppercase; letter-spacing:.05em;
}

/* ── category hero ───────────────────────────────────────────────── */
.help-cat-hero { display:flex; align-items:center; gap:16px; padding:26px 20px 20px; }
.help-cat-hero .help-card-icon {
  margin:0; width:52px; height:52px; background:var(--cat-tint,var(--hover-bg)); color:var(--cat-c,var(--ink));
}
.help-cat-hero .help-card-icon svg { width:24px; height:24px; }
.help-cat-hero h1 { margin:0; font-size:23px; font-weight:800; letter-spacing:-.01em; }
.help-cat-hero p { margin:5px 0 0; font-size:14px; color:var(--muted); line-height:1.5; }

/* ── article index list (category page) ─────────────────────────── */
.help-article-list { display:flex; flex-direction:column; padding:6px 20px 16px; gap:6px; }
.help-article-row {
  display:flex; align-items:center; gap:14px; padding:13px 14px;
  border:1px solid transparent; border-radius:var(--r-md); text-decoration:none; color:var(--ink);
  transition:background-color .12s ease, border-color .12s ease;
}
.help-article-row:hover { background:var(--hover-bg); border-color:var(--line); text-decoration:none; }
.help-article-row .n {
  flex:none; width:26px; height:26px; border-radius:50%; background:var(--cat-tint,var(--hover-bg));
  color:var(--cat-c,var(--muted)); font-size:12px; font-weight:800; display:flex; align-items:center; justify-content:center;
}
.help-article-row span.t { flex:1; font-size:15px; font-weight:600; }
.help-article-row svg {
  flex:none; width:16px; height:16px; stroke:var(--muted); fill:none; stroke-width:2; transition:transform .15s ease;
}
.help-article-row:hover svg { transform:translateX(3px); stroke:var(--cat-c,var(--muted)); }

/* ── article body ─────────────────────────────────────────────────── */
.help-article-body { padding:8px 20px 8px; max-width:700px; }
.help-kicker {
  display:inline-flex; align-items:center; gap:7px; font-size:12px; font-weight:800; color:var(--cat-c,var(--muted));
  text-transform:uppercase; letter-spacing:.06em; margin-bottom:10px;
}
.help-kicker .dot { width:7px; height:7px; border-radius:50%; background:var(--cat-c,var(--muted)); }
.help-article-body h1 { font-size:26px; font-weight:800; margin:0 0 10px; line-height:1.28; letter-spacing:-.015em; }
.help-article-body h2 { font-size:18px; font-weight:800; margin:30px 0 10px; scroll-margin-top:20px; }
.help-article-body p { font-size:15.5px; line-height:1.7; margin:0 0 15px; color:var(--ink); }
.help-article-body ul, .help-article-body ol { margin:0 0 15px; padding-left:22px; }
.help-article-body li { font-size:15.5px; line-height:1.7; margin-bottom:7px; }
.help-article-body li::marker { color:var(--cat-c,var(--muted)); font-weight:700; }
.help-article-body strong {
  font-weight:700; background:var(--cat-tint,transparent); padding:0 3px; margin:0 -1px; border-radius:4px;
}
.help-meta {
  display:flex; flex-wrap:wrap; align-items:center; gap:6px; font-size:13px; color:var(--muted);
  margin:0 0 20px; padding-bottom:18px; border-bottom:1px solid var(--line);
}
.help-meta .sep { color:var(--line); }

/* auto-generated "On this page" box (help.js) */
.help-toc { margin:0 0 22px; padding:14px 16px; background:var(--hover-bg); border-radius:var(--r-md); border:1px solid var(--line); }
.help-toc-title { font-size:12px; font-weight:800; color:var(--muted); text-transform:uppercase; letter-spacing:.05em; margin-bottom:8px; }
.help-toc ol { list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:6px; }
.help-toc a {
  display:block; font-size:13.5px; font-weight:600; color:var(--ink); text-decoration:none; padding:2px 0;
  border-left:2px solid var(--line); padding-left:10px; transition:border-color .12s ease, color .12s ease;
}
.help-toc a:hover { border-left-color:var(--cat-c,var(--ink)); color:var(--cat-c,var(--ink)); }

/* auto-generated feedback widget (help.js) */
.help-feedback {
  display:flex; align-items:center; flex-wrap:wrap; gap:12px; margin:32px 20px 0; padding:16px 18px;
  border:1px solid var(--line); border-radius:var(--r-lg); background:var(--cream-card);
}
.help-feedback .q { font-size:14px; font-weight:700; color:var(--ink); }
.help-feedback .btns { display:flex; gap:8px; }
.help-feedback button {
  font:inherit; font-size:13.5px; font-weight:700; color:var(--ink); background:var(--hover-bg);
  border:1px solid var(--line); border-radius:999px; padding:7px 16px; cursor:pointer; transition:background-color .12s ease;
}
.help-feedback button:hover { background:var(--line); }
.help-feedback button.chosen { background:var(--cat-c,var(--ink)); color:#fff; border-color:transparent; }
.help-feedback .thanks { font-size:13.5px; color:var(--muted); }

/* related articles */
.help-related { margin:28px 20px 8px; }
.help-related h3 { margin:0 0 12px; font-size:13px; font-weight:800; color:var(--muted); text-transform:uppercase; letter-spacing:.05em; }
.help-related ul { list-style:none; margin:0; padding:0; display:grid; grid-template-columns:1fr 1fr; gap:10px; }
.help-related li { margin:0; }
.help-related a {
  display:flex; align-items:center; gap:8px; height:100%; color:var(--ink); text-decoration:none; font-size:14px; font-weight:600;
  padding:13px 14px; border:1px solid var(--line); border-radius:var(--r-md); transition:border-color .12s ease, background-color .12s ease;
}
.help-related a:hover { background:var(--hover-bg); border-color:var(--cat-c,var(--line)); text-decoration:none; }
.help-related a::after { content:"→"; margin-left:auto; flex:none; color:var(--muted); }
.help-related a:hover::after { color:var(--cat-c,var(--muted)); }

@media (max-width:640px) {
  .help-related ul { grid-template-columns:1fr; }
}
@media (max-width:520px) {
  .help-grid { grid-template-columns:1fr 1fr; gap:10px; padding:18px; }
  .help-card { padding:15px; }
  .help-hero { padding:30px 16px 22px; }
  .help-hero h1 { font-size:24px; }
  .help-cat-hero { padding:20px 16px 14px; gap:12px; }
  .help-cat-hero .help-card-icon { width:44px; height:44px; }
  .help-cat-hero h1 { font-size:19px; }
  .help-article-body, .help-breadcrumb, .help-article-list { padding-left:16px; padding-right:16px; }
  .help-feedback, .help-related { margin-left:16px; margin-right:16px; }
}
