/* ============================================================
 * FORK CUSTOMIZATION
 * ------------------------------------------------------------
 * All visual styling resolves through these custom properties.
 * To rebrand a fork (different accent, different mood, lighter
 * background, your operator name's palette), override only the
 * values below — every selector below references the variable,
 * never a literal hex. The one exception is favicon.svg, which
 * is served standalone; copy + recolor it in the same place.
 *
 * Light mode (M8/B0):
 *   - Pre-click default: prefers-color-scheme media query picks
 *     OS theme automatically.
 *   - Post-click: the toggle stamps .theme-light or .theme-dark
 *     on <html>; that wins over the OS hint forever (per-browser,
 *     stored in localStorage). No mid-session reverts.
 *   - Forks override the dark palette under :root and the light
 *     palette under :root.theme-light + the same media
 *     query. Operator-supplied colorsLight values land via an
 *     inline <style> in <head> alongside the existing colors.
 * ============================================================ */
/* DEFAULT: tokyo-night — modern deep navy with soft blues. Sits between
 * github-dark and mocha-purple in saturation. Other presets are below
 * the light block; copy any one-line ":root { ... }" block over this
 * one to swap. */
:root {
  --bg: #1a1b26;
  --bg-soft: #24283b;
  --border: #414868;
  /* Type */
  --text: #c0caf5;
  --text-dim: #a9b1d6;
  /* Accents */
  --accent: #7aa2f7;       /* primary — links, mark, active states */
  --accent-warm: #ff9e64;  /* secondary — flair pills, NSFW badges, mod highlights */
  /* Vote signal — distinct from accent so the up/down sense is obvious */
  --up: #9ece6a;
  --down: #7aa2f7;
  --flair-text: #ffffff;   /* flair pill text — light by default; owners pick any background */
  --note-bg: #3a3320;      /* sticky-note fill — muted amber tint over dark bg */
}

/* Light palette — paper-light preset. Two activation paths:
 *   1. OS hint via @media when the user hasn't clicked the toggle.
 *   2. .theme-light class stamped by static/theme.js after click.
 * Both resolve to the same variable values. .theme-dark is the
 * sticky-dark override for users on a light-default OS who clicked
 * the toggle to dark — wins back over the media query.
 *
 * Class-based, not attribute-based: iOS Safari has long-standing
 * CSSOM-invalidation bugs around `[attr]` selectors when attribute
 * values churn (rapid theme toggles fail to repaint past the first
 * transition). Class selectors don't have this problem in any
 * engine. */
/* DEFAULT: zinc-cool — soft cool gray, low brightness for long sessions. */
:root.theme-light {
  --bg: #eef0f2;
  --bg-soft: #e3e6ea;
  --border: #c7ccd2;
  --text: #202428;
  --text-dim: #4d545b;
  --accent: #0066cc;
  --accent-warm: #955800;
  --up: #117833;
  --down: #0066cc;
  --flair-text: #ffffff;
  --note-bg: #fff8c5;      /* sticky-note fill — pale yellow over light bg */
}
@media (prefers-color-scheme: light) {
  :root:not(.theme-dark) {
    --bg: #eef0f2;
    --bg-soft: #e3e6ea;
    --border: #c7ccd2;
    --text: #202428;
    --text-dim: #4d545b;
    --accent: #0066cc;
    --accent-warm: #955800;
    --up: #117833;
    --down: #0066cc;
    --flair-text: #ffffff;
    --note-bg: #fff8c5;
  }
}

/* ---------- LIGHT PALETTE PRESETS ----------
 * Operators: copy any block over the :root.theme-light + media
 * blocks above to swap. Mirror the same values into both blocks so the
 * post-click and OS-hint paths stay aligned.
 *
 * github-light — cool gray, neutral, brightest. Industry-familiar.
 * --bg:#f6f8fa; --bg-soft:#eaeef2; --border:#d0d7de; --text:#1f2328; --text-dim:#57606a;
 * --accent:#0969da; --accent-warm:#9a6700; --up:#1a7f37; --down:#0969da; --flair-text:#ffffff;
 *
 * notion-cream — warm off-white, ink-feel.
 * --bg:#f9f7f0; --bg-soft:#f0ede4; --border:#d8d4c8; --text:#37352f; --text-dim:#787570;
 * --accent:#1a73e8; --accent-warm:#b06000; --up:#0f7b35; --down:#1a73e8; --flair-text:#ffffff;
 *
 * solarized-light — warm cream, paper-feel.
 * --bg:#fdf6e3; --bg-soft:#eee8d5; --border:#93a1a1; --text:#586e75; --text-dim:#93a1a1;
 * --accent:#268bd2; --accent-warm:#b58900; --up:#859900; --down:#268bd2; --flair-text:#fdf6e3;
 *
 * stone-warm — slight warm gray, less yellow than solarized.
 * --bg:#f5f4ed; --bg-soft:#ebe9e0; --border:#cfccc1; --text:#1c1917; --text-dim:#57534e;
 * --accent:#1564c0; --accent-warm:#a05a00; --up:#137034; --down:#1564c0; --flair-text:#ffffff;
 */

/* ------------------------------------------------------------
 * THEME PRESETS — copy any block over the :root above to swap.
 * No build step. Reload the page; everything re-skins.
 * ------------------------------------------------------------ */

/* :root { --bg:#0d1117; --bg-soft:#161b22; --border:#30363d; --text:#c9d1d9; --text-dim:#8b949e; --accent:#58a6ff; --accent-warm:#d29922; --up:#3fb950; --down:#58a6ff; } */
/* github-dark — original plato default. Cool charcoal, blue accent. Conservative, industry-familiar.                                                            */

/* :root { --bg:#1a1a1a; --bg-soft:#222;    --border:#333;    --text:#e6e1cf; --text-dim:#a0998c; --accent:#ffb454; --accent-warm:#ff8f40; --up:#7fd962; --down:#73d0ff; } */
/* warm-amber — ayu-dark, amber accent. Good for mailing-list-style discussion.                                                                                */

/* :root { --bg:#0f1419; --bg-soft:#161e25; --border:#283037; --text:#bfbdb6; --text-dim:#727169; --accent:#39bae6; --accent-warm:#ffb454; --up:#aad94c; --down:#39bae6; } */
/* cool-cyan — deep ocean palette, cyan accent.                                                                                                                */

/* :root { --bg:#1e1e2e; --bg-soft:#181825; --border:#313244; --text:#cdd6f4; --text-dim:#7f849c; --accent:#cba6f7; --accent-warm:#fab387; --up:#a6e3a1; --down:#89dceb; } */
/* mocha-purple — catppuccin mocha, purple accent. Higher contrast for long reads.                                                                             */

/* :root { --bg:#2d2a2e; --bg-soft:#221f22; --border:#403e41; --text:#fcfcfa; --text-dim:#939293; --accent:#78dce8; --accent-warm:#fc9867; --up:#a9dc76; --down:#78dce8; } */
/* monokai-pro — vibrant accents on warm dark bg. Closer to a code-editor aesthetic.                                                                           */

/* :root { --bg:#2e3440; --bg-soft:#3b4252; --border:#4c566a; --text:#d8dee9; --text-dim:#81a1c1; --accent:#88c0d0; --accent-warm:#d08770; --up:#a3be8c; --down:#88c0d0; } */
/* nord — frosty arctic palette, calm cool blues. Lower vibrancy, easy on eyes for marathon read sessions.                                                       */

/* :root { --bg:#282828; --bg-soft:#3c3836; --border:#504945; --text:#ebdbb2; --text-dim:#a89984; --accent:#83a598; --accent-warm:#d79921; --up:#b8bb26; --down:#83a598; } */
/* gruvbox-dark — retro warm earth tones, popular among vim/terminal users. Yellow-amber accent + muted greens.                                                  */

/* :root { --bg:#011627; --bg-soft:#0b2942; --border:#1d3b53; --text:#d6deeb; --text-dim:#7e8aa0; --accent:#82aaff; --accent-warm:#ffcb6b; --up:#a6e22e; --down:#82aaff; } */
/* night-owl — cobalt-deep blue, popular for streamers/recordings. Highest contrast accent against the deepest bg.                                               */


* { box-sizing: border-box; }

html, body { background: var(--bg); margin: 0; }

body {
  font: 14px/1.55 'JetBrains Mono', ui-monospace, 'SF Mono', Menlo, Consolas, monospace;
  color: var(--text);
  max-width: 880px;
  margin: 0 auto;
  padding: 2rem 1rem 4rem;
}

a { color: var(--accent); text-decoration: none; }
/* Global hover hint: dotted underline. Forum-wide consistency — every
   class-specific hover rule below uses the same `underline dotted`
   so the hover affordance reads the same anywhere a user moves the
   cursor. If you add a new hover rule, match the pattern. */
a:hover { text-decoration: underline dotted; }

.muted { color: var(--text-dim); }

header {
  border-bottom: 1px solid var(--border);
  padding-bottom: 1rem;
  margin-bottom: 1.5rem;
}

header h1 {
  margin: 0;
  font-size: 1.2rem;
  font-weight: 700;
}
header h1 .logo-mark {
  vertical-align: -1px;
  margin-right: 0.45rem;
}

/* Brand mark wrapped in a home link. Clicking anywhere on the dots (or
 * dots+wordmark in the footer) navigates to /. No underline, color stays
 * the same on hover (the dots already brighten on the rightmost; we don't
 * want color-shift fighting that). */
.logo-home, .logo-home:hover {
  text-decoration: none;
  color: inherit;
}
.logo-home { display: inline-flex; align-items: center; gap: 0.4rem; }
.logo-home .wordmark { color: var(--text-dim); letter-spacing: 0.04em; }
.logo-home:hover .logo-mark circle { opacity: 1; }

/* The plato mark: three blue dots, ascending opacity (0.4 → 0.7 → 1.0).
 * Reads as "a thought becoming clear" — pairs with the locked tagline
 * "opinion is the medium between knowledge and ignorance." Animation
 * budget for the entire app = 1: this mark waving when something is in
 * flight. Nothing else animates. */
.logo-mark circle { fill: var(--accent); }
.logo-mark[data-loading] circle {
  animation: dot-wave 1.2s infinite ease-in-out;
}
.logo-mark[data-loading] circle:nth-child(2) { animation-delay: 0.15s; }
.logo-mark[data-loading] circle:nth-child(3) { animation-delay: 0.3s; }
@keyframes dot-wave {
  0%, 60%, 100% { opacity: 0.35; }
  30% { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .logo-mark[data-loading] circle { animation: none; opacity: 1; }
  .comment-just-added { background: var(--bg-soft); }
}

/* Newly-inserted comment via comment.js: brief background fade so the
 * user sees where their submission landed. Removed after 1.5s by JS. */
.comment-just-added {
  background: var(--bg-soft);
  transition: background 1.2s ease-out;
}

/* Mod state: removed (hard) and collapsed (soft). PRD §Moderation says
 * removed shows a stub so discussion structure isn't deceptively hole-
 * filled; collapsed folds behind a <details> toggle so the read-out is
 * still possible. */
.comment-removed, .post-removed {
  font-style: italic;
  padding: 0.4rem 0;
}
/* Mod state chips. Soft-removal = clickable [+] expands hidden body in
 * place of the [collapsed by mod] label. Hard-removal = static [−]
 * stub, no body. Score-collapsed comments use the same chip shape with
 * a "(score) collapsed by community" label and stay clickable. */
.mod-state.mod-soft-removed > summary,
.comment-collapsed > summary {
  cursor: pointer;
  list-style: none;
  color: var(--text-dim);
  font-size: 0.85rem;
  padding: 0.3rem 0;
}
.mod-state.mod-soft-removed > summary::-webkit-details-marker,
.comment-collapsed > summary::-webkit-details-marker { display: none; }
.mod-state.mod-soft-removed > summary:hover,
.comment-collapsed > summary:hover { color: var(--accent); }
.mod-state.mod-soft-removed[open] > summary .label,
.comment-collapsed[open] > summary .label { display: none; }
.mod-state.mod-soft-removed[open] > summary,
.comment-collapsed[open] > summary { margin-bottom: 0.4rem; }
.mod-state .sigil { font-family: ui-monospace, monospace; }
.mod-hard-removed { font-size: 0.85rem; padding: 0.3rem 0; font-style: italic; }
.mod-marker {
  font-size: 0.78rem;
  font-weight: normal;
  margin-left: 0.4rem;
}

/* Inline mod controls — small, muted, sit at the bottom of post and
 * comment slots. Only rendered when canModerate(db, sub, handle) is
 * truthy on the server. The buttons are <button> in tiny forms; CSS
 * undoes the default accent button styling so they read as muted text
 * actions, not primary CTAs. */
.mod-controls {
  display: flex;
  gap: 0.4rem;
  margin-top: 0.3rem;
  font-size: 0.78rem;
}
.mod-controls .mod-form { display: inline; margin: 0; }
/* Mod controls render as warm-accent text verbs (collapse · remove · ban)
 * across all viewports. The previous outlined-pill chrome visually rhymed
 * with the solid-filled flair chip on the meta row below, so a fast scan
 * read mod tools and flair tags as the same kind of thing. Warm-accent is
 * plato's reserved "mod highlight" color (palette comment line 35) — using
 * it for the verb is the same temperature-shift trick `read more →` uses
 * to signal a different action register from neutral text. Middot between
 * verbs matches every other action strip in plato. */
.mod-controls .mod-btn {
  background: transparent;
  color: var(--accent-warm);
  border: none;
  padding: 0;
  font-size: 0.78rem;
  font-weight: normal;
  line-height: 1.2;
  border-radius: 0;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  font-family: inherit;
}
.mod-controls .mod-btn:hover {
  color: var(--accent-warm);
  text-decoration: underline dotted;
  text-underline-offset: 0.2em;
}
.mod-controls .mod-btn-disabled {
  opacity: 0.4;
  cursor: not-allowed;
}
.mod-controls .mod-btn-disabled:hover {
  text-decoration: none;
}
/* Separator between mod-control children. Assumes element-only children
 * (currently <details class="mod-confirm"> + <span class="mod-btn"> for
 * the disabled fallback). A raw text node sibling would skip the
 * separator; an empty element sibling would gain a phantom one. */
.mod-controls > * + *::before {
  content: "·";
  color: var(--text-dim);
  margin-right: 0.4rem;
}

/* Sub-create thresholds: align labels + inputs in two clean rows. */
.sub-thresholds .threshold-row {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  margin: 0.3rem 0;
}
.sub-thresholds .threshold-row > span {
  flex: 0 0 11rem;
  color: var(--text-dim);
  white-space: nowrap;
}
.sub-thresholds .threshold-row > input[type="number"] {
  flex: 0 0 5rem;
  padding: 0.2rem 0.4rem;
}
.sub-thresholds > p { margin: 0.5rem 0 0; font-size: 0.82rem; }

/* Login affordance for anonymous users — same nav corner as logout. */
.login-trigger { position: relative; }
.login-trigger > summary {
  cursor: pointer;
  list-style: none;
  color: var(--accent);
}
.login-trigger > summary::-webkit-details-marker { display: none; }
.login-trigger[open] > summary { color: var(--accent-warm); }
.login-form {
  display: flex;
  gap: 0.4rem;
  align-items: center;
  position: absolute;
  right: 0;
  top: calc(100% + 0.3rem);
  z-index: 20;
  background: var(--bg);
  border: 1px solid var(--border);
  padding: 0.5rem;
  white-space: nowrap;
}
.login-form input[type="email"] {
  flex: 1;
  min-width: 12rem;
}

/* Mod confirm form: each action button (remove, collapse, etc.) is a
 * <details> summary; clicking expands a small reason form below. Hard
 * removal requires a reason; soft removal is optional. The expand-form
 * pattern is the friction that keeps mods deliberate. No JS needed. */
.mod-confirm { display: inline-block; }
.mod-confirm > summary { list-style: none; cursor: pointer; }
.mod-confirm > summary::-webkit-details-marker { display: none; }
/* Open-state feedback: the summary verb gets weight so a mod sees which
 * action they've expanded without scanning down to the form. Border-color
 * is no longer relevant (verb has no border) and color is already warm. */
.mod-confirm[open] > summary { font-weight: 600; }
.mod-confirm > .mod-form {
  display: flex;
  gap: 0.4rem;
  align-items: center;
  flex-wrap: wrap;
  margin: 0.4rem 0 0;
  padding: 0.4rem;
  border: 1px solid var(--border);
  border-radius: 3px;
  background: var(--bg-soft);
}
.mod-confirm > .mod-form textarea {
  flex: 1;
  min-width: 200px;
  min-height: 2rem;
  font-size: 0.78rem;
  padding: 0.3rem 0.4rem;
  resize: vertical;
}
.mod-confirm > .mod-form button { font-size: 0.78rem; padding: 0.2rem 0.7rem; }

/* Mod log table — chronological public audit per PRD §Public mod log. */
table.modlog { width: 100%; border-collapse: collapse; margin-top: 1rem; font-size: 0.85rem; }
table.modlog th, table.modlog td { padding: 0.45rem 0.6rem; border-bottom: 1px solid var(--border); text-align: left; }
table.modlog th { color: var(--text-dim); font-weight: normal; text-transform: uppercase; letter-spacing: 0.06em; font-size: 0.72rem; }
.mod-action { font-weight: 600; }
.mod-action-collapse, .mod-action-uncollapse { color: var(--text-dim); }
.mod-action-remove, .mod-action-ban         { color: var(--accent-warm); }
.mod-action-unremove, .mod-action-unban,
.mod-action-promote_mod, .mod-action-demote_mod, .mod-action-transfer_owner { color: var(--accent); }

/* Flag button — small details dropdown next to the mod controls. PRD
 * §Spam 7: separate from downvote, category-driven. Closed list of 5
 * categories; optional 280-char note. */
.post-actions { display: flex; gap: 0.4rem; align-items: center; margin-top: 0.15rem; flex-wrap: wrap; }
.post-actions .mod-controls { gap: 0.4rem; margin-top: 0; }
/* Middot between .post-actions siblings so an author+mod row reads
 * `edit · collapse · remove · ban` instead of `edit  collapse · remove`
 * (gap-only spacing between edit and the mod-controls block made the
 * boundary look like an accidental indent). The ::before lands as the
 * first flex item of the second child (.mod-controls), getting flex-gap
 * to each side from .post-actions / .mod-controls respectively — no
 * margin needed. Mutually-exclusive-with-flag means flag rows never
 * have multiple children, so this only fires when edit + mod-controls
 * coexist. */
.post-actions > * + *::before {
  content: "·";
  color: var(--text-dim);
}
.flag-form-wrap { display: inline-block; }
.flag-trigger {
  cursor: pointer;
  list-style: none;
  font-size: 0.78rem;
  font-weight: normal;
  border: 1px solid var(--border);
  padding: 0.15rem 0.5rem;
  border-radius: 3px;
  color: var(--text-dim);
  display: inline-flex;
  align-items: center;
  line-height: 1.2;
}
.flag-trigger::-webkit-details-marker { display: none; }
.flag-trigger:hover { color: var(--accent-warm); border-color: var(--accent-warm); }
.flag-trigger-disabled {
  opacity: 0.45;
  cursor: not-allowed;
  border: 1px solid var(--border);
  padding: 0.15rem 0.5rem;
  border-radius: 3px;
  font-size: 0.78rem;
  display: inline-block;
}

.pager {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  margin: 1rem 0;
  font-size: 0.85rem;
}
.pager .pager-disabled { opacity: 0.4; }

/* Click-to-filter toggles in the modlog header. The label is the
 * affordance — click an inactive name to scope; click the active one
 * (warm) to drop the filter. Same pattern lands on user/mod chips in
 * M5 (m5-mod-surface-spec.md). */
.mod-subs .sub-toggle { color: var(--text-dim); }
.mod-subs .sub-toggle:hover { color: var(--accent); text-decoration: none; }
.mod-subs .sub-toggle-active {
  color: var(--accent-warm);
  font-weight: 600;
}
.mod-subs .sub-toggle-active:hover { color: var(--accent-warm); text-decoration: line-through; }

/* Above MODLOG_SUB_CHIP_LIMIT (currently 20), the chip strip flips to a
 * <select>-based form so the row doesn't wrap into a wall of names. */
.mod-subs-form {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  margin: 0.4rem 0;
  flex-wrap: wrap;
}
.mod-subs-form label { color: var(--text-dim); }
.mod-subs-select {
  background: var(--bg);
  color: var(--text);
  border: 1px solid var(--border);
  padding: 0.15rem 0.3rem;
  font: inherit;
  max-width: 60vw;
}

/* Generalized click-to-filter toggle for mod and user labels in the
 * /modlog audit table. Same affordance as the subs strip — click again
 * (warm) to drop the filter. */
.filter-toggle { color: var(--text); text-decoration: none; }
.filter-toggle:hover { color: var(--accent); }
.filter-toggle-active {
  color: var(--accent-warm);
  font-weight: 600;
}
.filter-toggle-active:hover { color: var(--accent-warm); text-decoration: line-through; }

/* /modlog mode + filter bar buttons. Bordered pills, active = warm. */
.modlog-modes, .modlog-filters {
  display: flex;
  gap: 0.4rem;
  align-items: center;
  flex-wrap: wrap;
  margin: 0.6rem 0;
}
.mode-btn, .filter-btn {
  font-size: 0.78rem;
  padding: 0.2rem 0.6rem;
  border: 1px solid var(--border);
  border-radius: 3px;
  color: var(--text-dim);
  text-decoration: none;
}
.mode-btn:hover, .filter-btn:hover {
  color: var(--accent);
  border-color: var(--accent);
  text-decoration: none;
}
.mode-btn-active, .filter-btn-active {
  color: var(--accent-warm);
  border-color: var(--accent-warm);
  font-weight: 600;
}
button.filter-btn {
  background: transparent;
  font: inherit;
  cursor: pointer;
  font-size: 0.78rem;
  line-height: 1.2;
}
.filter-form { display: inline; margin: 0; }
.modlog-filters .filter-sep { color: var(--text-dim); padding: 0 0.2rem; }
.modlog-summary { margin: 0.4rem 0; }
.memlog-inflight {
  border: 1px solid var(--border);
  background: var(--bg-soft);
  padding: 0.6rem 0.9rem;
  margin: 0.6rem 0;
  border-radius: 3px;
}
.memlog-inflight h3 { font-size: 0.95rem; font-weight: 600; margin: 0 0 0.4rem; }
.memlog-inflight ul { margin: 0; padding-left: 1.2rem; font-size: 0.9rem; }
.memlog-inflight li { margin: 0.2rem 0; }
.memlog-inflight p { margin: 0.4rem 0 0; font-size: 0.85rem; }
.memlog-inflight .memlog-inflight-state {
  color: var(--accent-warm);
  font-weight: 600;
}
.memlog-inflight .memlog-inflight-url {
  font-size: 0.85rem;
  word-break: break-all;
}
.event-count {
  font-variant-numeric: tabular-nums;
  color: var(--accent-warm);
  font-weight: 600;
}

/* /modlog open mode — vertical <details> list. The whole summary line
 * is the click target so an expand affordance can't be missed. Layout:
 * sub · when · user · target · pending count · chevron. Hard remove
 * button wears `.warn` for restraint. */
.modlog-open-list { display: flex; flex-direction: column; gap: 0.4rem; }
.modlog-open-row {
  border: 1px solid var(--border);
  border-radius: 3px;
  background: var(--bg-soft);
}
.modlog-open-row > summary {
  cursor: pointer;
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
  align-items: center;
  padding: 0.5rem 0.7rem;
  list-style: none;
}
.modlog-open-row > summary::-webkit-details-marker { display: none; }
.modlog-open-row > summary:hover { background: var(--bg); }
.modlog-open-row[open] > summary {
  color: var(--accent-warm);
  border-bottom: 1px solid var(--border);
}
.modlog-open-row[open] .open-row-chevron { transform: rotate(180deg); }
.open-row-chevron { margin-left: auto; transition: transform 0.15s ease; }
.open-row-target { flex: 1 1 auto; }

.modlog-body-wrap { margin-bottom: 0.5rem; }
.modlog-body-excerpt {
  margin: 0.4rem 0;
  padding: 0.4rem 0.6rem;
  border-left: 2px solid var(--border);
  color: var(--text-dim);
  white-space: pre-wrap;
}
.modlog-resolve-body {
  padding: 0.5rem;
  margin-top: 0.4rem;
  border: 1px solid var(--border);
  border-radius: 3px;
  background: var(--bg-soft);
}
.modlog-resolve-form { display: flex; flex-direction: column; gap: 0.4rem; margin-top: 0.4rem; }
.modlog-resolve-form input[type="text"] { width: 100%; }
.modlog-resolve-buttons { display: flex; gap: 0.4rem; flex-wrap: wrap; }
.modlog-resolve-buttons button { font-size: 0.78rem; padding: 0.2rem 0.6rem; }
.modlog-resolve-buttons button.warn { color: var(--accent-warm); border-color: var(--accent-warm); }
.flag-form {
  display: flex;
  gap: 0.4rem;
  align-items: center;
  margin-top: 0.4rem;
  padding: 0.4rem;
  border: 1px solid var(--border);
  border-radius: 3px;
  background: var(--bg-soft);
  flex-wrap: wrap;
}
.flag-form select, .flag-form input { font-size: 0.78rem; padding: 0.2rem 0.4rem; }
.flag-form button { font-size: 0.78rem; padding: 0.2rem 0.7rem; }

/* Site footer — the ∴plato attribution mark. Stays in every fork: the
 * operator can change wordmark + quote pool but keeps the glyph + "plato"
 * as upstream lineage. */
.site-footer {
  max-width: 880px;
  margin: 3rem auto 1.5rem;
  padding: 0.8rem 0 0;
  border-top: 1px solid var(--border);
  font-size: 0.78rem;
  color: var(--text-dim);
  text-align: center;
}
.site-footer .logo-home { vertical-align: -1px; }
.site-footer .wordmark {
  color: var(--text-dim);
  letter-spacing: 0.04em;
}
.site-footer .hosted-by {
  margin-left: 0.4rem;
  font-size: 0.78rem;
}
.site-footer .quote {
  display: block;
  margin-top: 0.3rem;
  font-style: italic;
  color: var(--text-dim);
}

/* Inline page-subtitle / tagline next to the brand title in the h1.
 * Smaller and muted so the title still reads as the heading; the
 * `· prefix in the markup gives the visual separator. */
header.site .brand .brand-subtitle {
  font-size: 0.85rem;
  font-weight: 400;
  color: var(--text-dim);
  white-space: normal;
}

header.site {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 1rem;
}

header.site .brand { min-width: 0; }

.status {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.8rem;
  white-space: nowrap;
  flex-shrink: 0;
}
.status img {
  width: 16px;
  height: 16px;
  border-radius: 3px;
  background: var(--bg-soft);
}
.status strong {
  color: var(--text);
  font-weight: inherit;
}
.status form.inline {
  display: inline;
  margin: 0;
}
button.link {
  background: transparent;
  color: var(--text-dim);
  border: 0;
  padding: 0;
  font: inherit;
  cursor: pointer;
}
button.link:hover { color: var(--accent); text-decoration: underline dotted; }

/* Theme toggle (M8/B0). Hidden when JS is unavailable — without
 * JS the click handler can't flip data-theme, so the button would
 * be dead chrome. The inline anti-flash <script> stamps `has-js`
 * on <html> before first paint, so this hide rule is invisible to
 * the JS path. */
html:not(.has-js) .theme-toggle { display: none; }
.theme-toggle { font-variant: small-caps; letter-spacing: 0.04em; }

h3.section {
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-dim);
  font-weight: 600;
  margin: 1.5rem 0 0.5rem;
}

/* M1 components — reference: docs/design/1-terminal.html */

form { display: grid; gap: 8px; margin: 1rem 0 1.5rem; }
input, textarea, select, button {
  font: inherit;
  background: var(--bg-soft);
  color: var(--text);
  border: 1px solid var(--border);
  padding: 0.5rem 0.6rem;
  border-radius: 3px;
}
select {
  appearance: none;
  -webkit-appearance: none;
  padding-right: 1.8rem;
  background-image: linear-gradient(45deg, transparent 50%, var(--text-dim) 50%),
                    linear-gradient(135deg, var(--text-dim) 50%, transparent 50%);
  background-position: calc(100% - 0.85rem) 50%, calc(100% - 0.55rem) 50%;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
}
select option { background: var(--bg-soft); color: var(--text); }
input:focus, textarea:focus, select:focus, button:focus {
  outline: 1px solid var(--accent);
  outline-offset: -1px;
}
textarea { min-height: 120px; font-family: inherit; resize: vertical; }
button {
  cursor: pointer;
  background: var(--accent);
  color: var(--bg);
  border-color: var(--accent);
  font-weight: 600;
  padding: 0.4rem 0.9rem;
}
button:hover { filter: brightness(1.1); }

.post {
  padding: 0.55rem 0;
  border-bottom: 1px solid var(--border);
  display: grid;
  /* minmax(0, 1fr) lets the body track shrink below its content's
   * intrinsic width — needed because a long post title in the .body
   * column would otherwise force the grid wider than the viewport,
   * dragging every paragraph in the rendered article past the right
   * edge and producing page-level horizontal scroll on mobile. */
  grid-template-columns: auto minmax(0, 1fr);
  gap: 0.5rem;
}
.post > .body { min-width: 0; }

.vote {
  display: flex;
  flex-direction: column;
  align-items: center;
  align-self: center;
  color: var(--text-dim);
  font-size: 0.75rem;
  min-width: 2.2rem;
  gap: 1px;
}
.vote .score { font-weight: 600; font-size: 0.9rem; }
.vote .score-zero { color: var(--text-dim); }
.vote .score-pos  { color: var(--up); }
.vote .score-neg  { color: var(--down); }
.vote .arrow,
.vote button.arrow {
  font-size: 0.95rem;
  line-height: 1;
  padding: 1px 4px;
  cursor: pointer;
  background: transparent;
  border: 0;
  color: var(--text-dim);
}
.vote .arrow.up.active,   .vote button.arrow.up.active   { color: var(--up);   opacity: 1; }
.vote .arrow.down.active, .vote button.arrow.down.active { color: var(--down); opacity: 1; }
.vote button.arrow:hover { color: var(--text); }
.vote .arrow.muted { cursor: default; }
.vote form.inline { display: inline; margin: 0; }

.post .body h2 { margin: 0; font-size: 1.2rem; font-weight: 700; line-height: 1.25; flex: 1; }
.post .body h2 a { color: var(--text); }
.post .body h2 a:hover { color: var(--accent); }
.post-title-line { display: flex; align-items: baseline; gap: 0.5rem; margin-bottom: 0.1rem; flex-wrap: wrap; }
/* min-width:0 lets the h1 shrink below intrinsic content width inside
 * the flex row; overflow-wrap:anywhere catches the rare title that's
 * one long unbreakable token (a URL, a hash, a kebab-cased phrase).
 * Without these the title would push the grid track past viewport on
 * narrow screens and force the post body to scroll sideways. */
.post-title-line h1 { flex: 1 1 auto; min-width: 0; margin: 0; font-size: 1.6rem; line-height: 1.2; overflow-wrap: anywhere; }
.post-title-line .post-actions { margin-top: 0; flex-shrink: 0; }

/* Per-paragraph bidi detection inside user content. The `dir="auto"` on
   the container (article, .comment-body, .preview, h1/h2 titles) only
   detects from the *first* strong char of the whole element; this rule
   makes every block-level child detect independently from its own first
   strong char. So a post that mixes Arabic and English paragraphs gets
   each paragraph rendered in its own direction. UI chrome stays LTR. */
article > *, .comment-body > *, .preview > *, .sub-sticky-note > * { unicode-bidi: plaintext; }

.preview {
  margin-top: 0.5rem;
  color: var(--text);
  line-height: 1.55;
  font-size: 0.9rem;
}
.preview p { margin: 0.4rem 0; }
.preview p:first-child { margin-top: 0; }
.preview p:last-child { margin-bottom: 0; }
.preview pre {
  background: var(--bg-soft);
  border: 1px solid var(--border);
  padding: 0.5rem 0.7rem;
  border-radius: 4px;
  overflow-x: auto;
  font-size: 0.85rem;
}
.preview code {
  background: var(--bg-soft);
  padding: 0.1rem 0.3rem;
  border-radius: 3px;
  font-size: 0.9em;
}
.preview pre code { background: transparent; padding: 0; }
.preview blockquote {
  border-left: 2px solid var(--border);
  padding-left: 0.7rem;
  color: var(--text-dim);
  margin: 0.5rem 0;
}
.more {
  font-size: 0.82rem;
  color: var(--accent);
}

.sort-nav {
  display: flex;
  gap: 0.9rem;
  font-size: 0.82rem;
  margin: 0.3rem 0 0.8rem;
}

/* Home active-subs block: vertical list of the top 4 subs by 24h post count */
.active-subs { margin-bottom: 1.5rem; }
.active-subs-list { font-size: 0.92rem; }
.active-sub-row {
  display: flex;
  gap: 0.6rem;
  padding: 0.35rem 0;
  border-bottom: 1px dotted var(--border);
  align-items: baseline;
}
.active-sub-row .desc { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.active-sub-row .stats { font-size: 0.82rem; white-space: nowrap; font-variant-numeric: tabular-nums; }
.active-subs-foot { margin: 0.4rem 0 0; font-size: 0.85rem; }
/* `+ new sub` mirrors the .filter-btn pill so the home action row reads as one
 * consistent button family with the home/modlog/memlog filter chips. */
.new-sub {
  display: inline-block;
  font-size: 0.78rem;
  padding: 0.2rem 0.6rem;
  border: 1px solid var(--border);
  border-radius: 3px;
  color: var(--text-dim);
  text-decoration: none;
}
.new-sub:hover {
  color: var(--accent);
  border-color: var(--accent);
  text-decoration: none;
}

/* Collapsed new-post form — looks like a tappable action, not a section label */
.new-post-toggle { margin: 1.5rem 0 0.6rem; }
.new-post-toggle > summary {
  cursor: pointer;
  list-style: none;
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.3rem 0.75rem;
  border: 1px solid var(--accent);
  border-radius: 4px;
  background: var(--accent);
  color: var(--bg);
  font-size: 0.85rem;
  font-family: inherit;
  letter-spacing: 0.03em;
}
.new-post-toggle > summary::-webkit-details-marker { display: none; }
.new-post-toggle > summary:hover { opacity: 0.85; }
.new-post-toggle[open] > summary { margin-bottom: 0.8rem; }

.post-page { border-bottom: 0; }
.post-page article { margin-top: 1rem; }

.comment-tree { margin-top: 0.6rem; }

.comment {
  position: relative;
  padding: 0.5rem 0;
}

.comment-tree > .comment + .comment {
  border-top: 1px solid var(--border);
}

/* Replies container: vertical guide line on the left, indent for children. */
.replies {
  position: relative;
  margin-left: 0.6rem;
  padding-left: 1.2rem;
  margin-top: 0.3rem;
}
.replies::before {
  content: '';
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0.6rem;
  width: 1px;
  background: var(--border);
}

/* Horizontal stub from the parent's vertical guide to each reply. 90° L. */
.replies > .comment::before {
  content: '';
  position: absolute;
  left: -1.2rem;
  top: 1.2rem;
  width: 1rem;
  height: 1px;
  background: var(--border);
}
.replies > .comment + .comment {
  border-top: 0;  /* connector, not divider, between siblings under the line */
}

.more-replies {
  margin-left: 0.6rem;
  padding: 0.3rem 0 0.3rem 1.2rem;
  position: relative;
}
.more-replies::before {
  content: '';
  position: absolute;
  left: 0;
  top: 0;
  width: 1px;
  height: 1rem;
  background: var(--border);
}
.more-replies > summary {
  cursor: pointer;
  font-size: 0.82rem;
  color: var(--accent);
  list-style: none;
}
.more-replies > summary::-webkit-details-marker { display: none; }
.more-replies > summary:hover { text-decoration: underline dotted; }

/* Sticky composer at the bottom of the post page. Always-visible reply
 * affordance — user doesn't need to scroll past long threads to comment.
 * Top-level only; per-comment replies still use their inline forms. */
.composer-bar {
  position: fixed;
  bottom: 1rem;
  left: 50%;
  transform: translateX(-50%);
  width: calc(100% - 2rem);
  max-width: 880px;
  background: var(--bg);
  border: 1px solid var(--border);
  padding: 0.5rem 0.75rem;
  z-index: 100;
}
.composer-bar > * {
  margin: 0;
}
.composer-bar form {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  align-items: stretch;
  margin: 0;
}
.composer-bar .guest-notice {
  flex-basis: 100%;
  order: 1;
  margin: 0;
  padding: 0.35rem 0.5rem;
  background: var(--bg-alt, var(--bg));
  border: 1px solid var(--accent);
  color: var(--text);
  font-size: 0.85rem;
  line-height: 1.3;
}
.composer-bar textarea {
  flex: 1;
  min-height: 2.4rem;
  height: 2.4rem;
  max-height: 12rem;
  resize: none;
  padding: 0.4rem 0.6rem;
  transition: height 0.15s ease-out;
}
.composer-bar textarea:focus {
  height: 6rem;
}
.composer-bar p { margin: 0; padding: 0.4rem 0; text-align: center; }

/* Long comments collapse behind read-more. summary shows the first ~280
 * chars; expanding swaps in the full rendered body. Top-level comments
 * (depth 0) stay uncapped since they're direct replies to the post.
 *
 * Toggle UX: closed state shows the preview ellipsis + "read more";
 * open state hides the preview + the read-more label and surfaces
 * "show less" instead, so the user has an obvious affordance to fold
 * back. The summary itself stays clickable in both states. */
.comment-long > summary {
  cursor: pointer;
  list-style: none;
  white-space: pre-wrap;
}
.comment-long > summary::-webkit-details-marker { display: none; }
.comment-long > summary .read-more,
.comment-long[open] > summary .show-less { color: var(--accent); }
.comment-long > summary .show-less { display: none; }
.comment-long[open] > summary .comment-preview,
.comment-long[open] > summary .read-more { display: none; }
.comment-long[open] > summary .show-less { display: inline; }
.composer-bar .textarea-wrap { flex: 1; }
.composer-bar button { white-space: nowrap; align-self: center; margin-left: auto; }

/* Inline per-comment reply form mirrors the composer-bar shape so the
 * "comment" and "reply" actions read as the same idiom: textarea takes
 * the row, pill-shaped button anchors to the right edge and centers
 * vertically between the textarea and the char counter. The hidden
 * parent_id input stays in flow but takes no visible space. */
.reply-form {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  align-items: stretch;
  margin: 0.4rem 0 0;
}
.reply-form .textarea-wrap { flex: 1; min-width: 0; }
.reply-form button { white-space: nowrap; align-self: center; margin-left: auto; }

/* Page padding so the last comment isn't hidden behind the bar. Apply to
 * body when the bar is present — guarded by a sub-tree class on the post
 * page so other pages aren't affected. */
body:has(.composer-bar) { padding-bottom: 5rem; }

/* Memlog stays at the standard plato column width. The table makes
 * the early columns nowrap-tight so the snippet column absorbs the
 * leftover space and truncates with ellipsis instead of wrapping the
 * row onto a second line. */
.memlog-page table.modlog { table-layout: fixed; }
.memlog-page table.modlog th,
.memlog-page table.modlog td {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.memlog-page table.modlog th:nth-child(1),
.memlog-page table.modlog td:nth-child(1) { width: 4rem; }   /* type */
.memlog-page table.modlog th:nth-child(2),
.memlog-page table.modlog td:nth-child(2) { width: 5rem; }   /* when */
.memlog-page table.modlog th:nth-child(3),
.memlog-page table.modlog td:nth-child(3) { width: 7rem; }   /* kind */
.memlog-page table.modlog th:nth-child(4),
.memlog-page table.modlog td:nth-child(4) { width: 7rem; }   /* from */
.memlog-page table.modlog th:nth-child(5),
.memlog-page table.modlog td:nth-child(5) { width: 8rem; }   /* where */
/* Snippet column absorbs the leftover row width AND wraps onto
 * multiple lines so long failure reasons (e.g. "already imported as
 * //x on YYYY-MM-DD. you can request a new one anytime.") read
 * fully instead of truncating with an ellipsis. The other columns
 * keep their nowrap so timestamp + chip widths stay rigid. */
.memlog-page table.modlog th:nth-child(6),
.memlog-page table.modlog td:nth-child(6) {
  width: auto;
  white-space: normal;
  overflow: visible;
  text-overflow: clip;
  word-break: break-word;
}

.comment-header {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  margin-bottom: 0.3rem;
}
.comment-header .vote {
  flex-direction: row;
  gap: 0.3rem;
  min-width: 0;
}
.comment-header .meta { flex: 1; min-width: 0; }
.comment-header .post-actions { margin-top: 0; flex-shrink: 0; }
.comment-body {
  font-size: 0.92rem;
  line-height: 1.55;
}
.comment-body p { margin: 0.3rem 0; }
.comment-body p:first-child { margin-top: 0; }
.comment-body p:last-child { margin-bottom: 0; }

.comment-collapsed > summary { cursor: pointer; padding: 0.2rem 0; }
.comment-collapsed[open] > summary { color: var(--text); }

.reply { margin-top: 0.4rem; }
.reply > summary { cursor: pointer; font-size: 0.82rem; }
.reply-form { margin-top: 0.4rem; }
.reply-form textarea { min-height: 60px; }

.meta {
  color: var(--text-dim);
  font-size: 0.78rem;
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  align-items: center;
}
.meta img {
  width: 18px;
  height: 18px;
  border-radius: 3px;
  background: var(--bg-soft);
}
.meta .name { color: var(--text); }
.meta .when { color: var(--text-dim); }
.meta a { color: var(--text-dim); }
.meta a:hover { color: var(--accent); text-decoration: none; }

article { line-height: 1.7; margin-top: 1rem; }
article h1, article h2, article h3 { margin-top: 1.5rem; }
article p { margin: 0.6rem 0; }
article code {
  background: var(--bg-soft);
  padding: 0.1rem 0.35rem;
  border-radius: 3px;
  font-size: 0.92em;
}
article pre {
  background: var(--bg-soft);
  border: 1px solid var(--border);
  padding: 0.7rem 0.9rem;
  border-radius: 4px;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}
article pre code { background: transparent; padding: 0; }
article blockquote {
  border-left: 2px solid var(--border);
  padding-left: 0.9rem;
  color: var(--text-dim);
  margin: 0.7rem 0;
}
article hr { border: 0; border-top: 1px solid var(--border); margin: 1.5rem 0; }
article a { color: var(--accent); }
article ul, article ol { padding-left: 1.5rem; }

/* ============================================================
 * UX-A: post-meta tweaks (M5/B8)
 * ------------------------------------------------------------
 * - .reply-count: SVG bubble + N. Zero state stays muted but
 *   readable (--text-dim was nearly invisible on dark mode).
 * - .sep: separator dots between meta fields, muted.
 * ============================================================ */
.meta { display: inline-flex; align-items: center; gap: 0.35rem; flex-wrap: wrap; }
.meta .sep { color: var(--text-dim); margin: 0 0.05rem; }
.meta .when { color: var(--text-dim); }
/* `.meta a` has higher specificity than `.reply-count`, so qualify the
 * selector with the tag to win the cascade and let the active-conv blue
 * actually paint. */
a.reply-count {
  display: inline-flex; align-items: center; gap: 0.25rem;
  color: var(--accent);
  text-decoration: none;
  font-weight: 600;
}
a.reply-count .reply-icon { vertical-align: -2px; opacity: 0.85; }
a.reply-count .reply-count-n { font-variant-numeric: tabular-nums; }
a.reply-count.reply-count-zero { color: var(--text-dim); font-weight: normal; }
a.reply-count.reply-count-zero .reply-icon { opacity: 0.55; }
a.reply-count:hover { text-decoration: underline dotted; }

/* UX-C: sub color accent on feed. 8-color palette indexed by hash of
 * sub_name (see subColorIndex in app.js). Forks override here. */
:root {
  --sub-color-0: #58a6ff;  /* blue */
  --sub-color-1: #d29922;  /* amber */
  --sub-color-2: #56d364;  /* green */
  --sub-color-3: #f778ba;  /* pink */
  --sub-color-4: #a371f7;  /* purple */
  --sub-color-5: #79c0ff;  /* sky */
  --sub-color-6: #ff7b72;  /* coral */
  --sub-color-7: #d2a8ff;  /* lilac */
}
.sub-link { font-weight: 500; }
.sub-link.sub-0 { color: var(--sub-color-0); }
.sub-link.sub-1 { color: var(--sub-color-1); }
.sub-link.sub-2 { color: var(--sub-color-2); }
.sub-link.sub-3 { color: var(--sub-color-3); }
.sub-link.sub-4 { color: var(--sub-color-4); }
.sub-link.sub-5 { color: var(--sub-color-5); }
.sub-link.sub-6 { color: var(--sub-color-6); }
.sub-link.sub-7 { color: var(--sub-color-7); }

/* UX-B: domain hint after outbound links — text only, no favicon image
 * (privacy: per-link favicon proxies leak viewer→Google). Inserted by
 * the markdown renderer, scoped to outbound `<a>` only via .ext-host. */
.ext-host {
  color: var(--text-dim);
  font-size: 0.85em;
  margin-left: 0.2rem;
  white-space: nowrap;
}
.ext-host::before { content: "↗ "; opacity: 0.7; }

/* UX-D: /communities directory page */
table.communities { width: 100%; border-collapse: collapse; margin-top: 0.6rem; font-size: 0.9rem; table-layout: auto; }
table.communities th, table.communities td { padding: 0.4rem 0.5rem; border-bottom: 1px solid var(--border); text-align: left; vertical-align: top; }
table.communities th { color: var(--text-dim); font-weight: 500; font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.05em; }
table.communities td.num { font-variant-numeric: tabular-nums; text-align: right; }
table.communities td:first-child { white-space: nowrap; }
table.communities td.desc-cell { white-space: normal; overflow-wrap: anywhere; }
table.communities .col-active,
table.communities .col-owner { white-space: nowrap; }
table.communities td.col-owner { font-variant-numeric: tabular-nums; }
table.communities tr:hover td { background: var(--bg-soft); }
#community-filter {
  background: var(--bg-soft); border: 1px solid var(--border); color: var(--text);
  padding: 0.2rem 0.5rem; border-radius: 3px; font: inherit; font-size: 0.85rem;
  margin-left: 0.4rem; min-width: 12rem;
}


/* UX-E: home top nav (Posts | Comments tabs + sort + date) */
.home-nav { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem; margin: 0.6rem 0 0.4rem; padding: 0.5rem 0; border-bottom: 1px solid var(--border); font-size: 0.85rem; }
/* `.filter-group` is a semantic wrapper around a category of chips, but
 * for layout we want chips + separators to wrap individually as siblings
 * (snake-wrap: left-to-right, then to next line) rather than each group
 * claiming its own row when widths are tight. `display: contents` makes
 * the wrapper transparent for layout while preserving the DOM grouping. */
.home-nav .filter-group { display: contents; }
.filter-btn-disabled { opacity: 0.35; cursor: default; pointer-events: none; }

/* memlog (per-user notification log) chrome. */
.memlog-link { color: var(--accent); text-decoration: none; }
.memlog-link strong { color: var(--accent); font-weight: 600; }
.memlog-link:hover strong { text-decoration: underline dotted; }
.memlog-chip {
  color: var(--accent);
  font-weight: 600;
  text-decoration: none;
  margin-left: 0.15rem;
}
.memlog-chip:hover { text-decoration: underline dotted; }
table.modlog tr.memlog-row-read td { opacity: 0.55; }
table.modlog tr.memlog-row-read:hover td { opacity: 1; }

/* rssvp — plato voice for RSS feeds. Accent-colored to match link
   affordance; the substring "rss" is preserved so reader users still
   recognize it. .rssvp-mark = the inline label/heading; .rssvp-link =
   the action-row link on /sub/<name>; .rssvp-copy = click-to-copy
   button wrapping the URL on /memlog. */
.rssvp-mark { color: var(--accent); font-weight: 600; }
.rssvp-link { color: var(--accent); cursor: pointer; }
.rssvp-link:hover { text-decoration: underline dotted; }
.rssvp-link.rssvp-copied { color: var(--up); text-decoration: none; }
.memlog-feeds > summary,
.memlog-export > summary { cursor: pointer; padding: 0.25rem 0; }
.memlog-feeds > summary:hover .rssvp-mark,
.memlog-export > summary:hover .rssvp-mark { text-decoration: underline dotted; }
.rssvp-list { list-style: none; padding-left: 0; margin: 0.5rem 0; }
.rssvp-list li { margin: 0.35rem 0; }
.rssvp-copy {
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  color: inherit;
  cursor: pointer;
  text-align: left;
}
.rssvp-copy code { color: inherit; background: none; padding: 0; }
.rssvp-copy:hover code { text-decoration: underline dotted; }
.rssvp-copy.rssvp-copied code { color: var(--up); text-decoration: none; }
.rssvp-desc { color: var(--text); font-weight: 600; }

/* Edit window affordances */
.edited-note { margin: 0.2rem 0; font-size: 0.8rem; }
/* .action-link is the broader sibling of .mod-btn (author `edit`,
 * sub-admin verbs on /sub/<n>/edit, modlog `revoke`). Same warm-accent
 * text-verb register so a strip mixing author + mod actions reads as
 * one vocabulary instead of "old outlined pill + new text verb." */
.action-link {
  font-size: 0.78rem;
  color: var(--accent-warm);
  text-decoration: none;
  background: transparent;
  border: none;
  padding: 0;
  border-radius: 0;
  display: inline-flex;
  align-items: center;
  line-height: 1.2;
  cursor: pointer;
  font-family: inherit;
}
.action-link:hover {
  color: var(--accent-warm);
  text-decoration: underline dotted;
  text-underline-offset: 0.2em;
}

/* Inline two-step confirmation: <details> wraps a destructive form so the
   submit only appears after the user opens the disclosure. Replaces native
   confirm() popups for mod actions (demote, step-down, disable-sub).
   Summary is styled as a blue pill — visually paired with the promote
   button (.mod-action-pill) so all mod-management triggers read as a set. */
.inline-confirm { display: inline-block; }
.inline-confirm > summary {
  list-style: none;
  cursor: pointer;
  font-size: 0.78rem;
  color: var(--accent);
  text-decoration: none;
  border: 1px solid var(--accent);
  padding: 0.2rem 0.7rem;
  border-radius: 999px;
  display: inline-flex;
  align-items: center;
  line-height: 1.2;
  background: transparent;
}
.inline-confirm > summary::-webkit-details-marker { display: none; }
.inline-confirm > summary::marker { content: ''; }
.inline-confirm > summary:hover { background: var(--accent); color: var(--bg); }
.inline-confirm[open] > summary { background: var(--accent); color: var(--bg); }

/* Promote button — same pill shape as the inline-confirm triggers so all
   mod-management actions read as a single visual family. */
.mod-action-pill {
  font-size: 0.78rem;
  padding: 0.2rem 0.7rem;
  border-radius: 999px;
  border: 1px solid var(--accent);
  background: transparent;
  color: var(--accent);
  cursor: pointer;
  line-height: 1.2;
}
.mod-action-pill:hover { background: var(--accent); color: var(--bg); }

/* Co-mod list: pseudonym + demote pill sit tight on a single row, with
   the expanded confirm form (when [open]) wrapping below the trigger. */
.co-mod-list { list-style: none; padding: 0; margin: 0.4rem 0 0.8rem; }
.co-mod-list > li {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.3rem 0;
  flex-wrap: wrap;
}
.co-mod-list > li > strong { margin-right: 0; }
.co-mod-list > li > .inline-confirm { margin-left: 0; }
.co-mod-list-owner { padding: 0.3rem 0; margin: 0.4rem 0 0.8rem; }

/* Live character counter under any textarea[data-charcount]. Visible
   surrogate for the maxlength attribute so users can see how much room
   remains as they type / paste. Goes accent-warm at 90%, accent-warn
   at the cap. */
.textarea-wrap { position: relative; display: block; width: 100%; }
.textarea-wrap > textarea { width: 100%; box-sizing: border-box; }
.char-counter {
  font-size: 0.72rem;
  text-align: right;
  margin-top: 0.15rem;
  font-variant-numeric: tabular-nums;
}
.char-counter-near { color: var(--accent-warm); }
.char-counter-full { color: var(--accent-warm); font-weight: 600; }

/* Inline mod indicator before sub names for subs you mod / co-mod. */
.mod-indicator {
  color: var(--accent);
  font-weight: 600;
  margin-right: 0.15rem;
  font-family: var(--font-mono, monospace);
}
.inline-confirm-form {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  margin-top: 0.4rem;
  padding: 0.5rem 0.6rem;
  border: 1px solid var(--border);
  border-radius: 3px;
  background: var(--bg-soft, transparent);
}
.inline-confirm-form > span { flex: 1 1 14rem; font-size: 0.85rem; }
.inline-confirm-form > button { white-space: nowrap; }
.inline-confirm-cancel {
  font-size: 0.78rem;
  text-decoration: none;
  padding: 0.15rem 0.4rem;
}
.inline-confirm-cancel:hover { color: var(--accent); }

/* Outbound-link badges on feed post rows */
.links { color: var(--text-dim); font-size: 0.75rem; margin-top: 0.25rem; display: flex; gap: 0.9rem; flex-wrap: wrap; align-items: baseline; }
.links .lh { display: inline-flex; align-items: center; }
.links .more { font-size: 0.75rem; }

/* Flair pill — colored background per the sub owner's choice; text uses
   a hardcoded light shade because the owner picks any color and we can't
   compute contrast ahead of time. Operators can override via .flair-pill
   in a fork stylesheet if their default palette demands dark text. */
.flair-pill {
  display: inline-block;
  font-size: 0.7rem;
  font-weight: 600;
  text-transform: lowercase;
  color: var(--flair-text);
  padding: 0.05rem 0.45rem;
  border-radius: 999px;
  text-decoration: none;
  line-height: 1.4;
  margin-left: 0.25rem;
}
.flair-pill:hover { text-decoration: none; filter: brightness(1.15); }
.flair-pill-all   { background: var(--bg-soft); color: var(--text-dim); border: 1px solid var(--border); }
.flair-pill-active { outline: 2px solid var(--text); outline-offset: 1px; }

.flair-filter { display: flex; gap: 0.4rem; flex-wrap: wrap; margin: 0.4rem 0 0.6rem; }

.flair-editor { border: 1px solid var(--border); padding: 0.5rem 0.7rem; margin: 0.6rem 0; }
.flair-editor .flair-row {
  display: flex; align-items: center; gap: 0.4rem; margin: 0.3rem 0; flex-wrap: wrap;
}
.flair-editor .flair-row-label { flex: 1 1 12rem; font-size: 0.85rem; padding: 0.2rem 0.4rem; }
.flair-editor .flair-row-color {
  flex: 0 0 2rem; width: 2rem; height: 1.7rem; padding: 0; border: 1px solid var(--border);
  border-radius: 3px; background: transparent; cursor: pointer;
}
.flair-palette { display: inline-flex; gap: 0.2rem; align-items: center; flex-wrap: wrap; }
.flair-swatch {
  width: 1.1rem; height: 1.1rem; border-radius: 3px; border: 1px solid var(--border);
  padding: 0; cursor: pointer; opacity: 0.85;
}
.flair-swatch:hover { opacity: 1; outline: 1px solid var(--text); outline-offset: 1px; }

/* Flair preview pill shown next to the post-form flair select via JS. */
.flair-form-row { display: inline-flex; align-items: center; gap: 0.4rem; margin: 0.3rem 0; }
.flair-form-row[hidden] { display: none; }
.flair-form-preview {
  display: inline-flex; align-items: center; gap: 0.4rem; margin-left: 0.4rem;
  font-size: 0.78rem;
}
.flair-form-preview .flair-pill { margin-left: 0; }

/* Sensitive checkbox sits under the body textarea on the post form. */
.post-form-row { display: flex; align-items: center; gap: 0.4rem; margin: 0.3rem 0; }
.post-form-row input[type="checkbox"] { margin: 0; }

/* End-of-page pagination footer. Renders below the feed list on home and
   sub pages once a feed grows past 50 items. Terminal-honest pause point —
   no infinite scroll. Both prev/next stay in DOM (disabled when unavailable)
   so the eye lands on a stable shape regardless of position. */
.page-nav {
  display: flex; align-items: center; justify-content: center;
  gap: 1rem; margin: 1.4rem 0 0.6rem; padding: 0.5rem 0;
  border-top: 1px solid var(--border);
  font-size: 0.85rem;
}
.page-nav .page-link {
  color: var(--accent);
  text-decoration: none;
  border: 1px solid var(--border);
  padding: 0.2rem 0.6rem;
  border-radius: 3px;
}
.page-nav .page-link:hover { border-color: var(--accent); }
.page-nav .page-link-disabled {
  color: var(--text-dim);
  border: 1px solid var(--border);
  padding: 0.2rem 0.6rem;
  border-radius: 3px;
  opacity: 0.5;
  cursor: default;
}
.page-nav .page-indicator { color: var(--text-dim); font-variant-numeric: tabular-nums; }

/* Inline rejection banner on the prefilled retry form. Loud enough to
   be obvious, restrained enough to not look like a system error page. */
.post-error-banner {
  border: 1px solid var(--accent-warm);
  color: var(--accent-warm);
  background: var(--bg-soft);
  padding: 0.5rem 0.7rem;
  margin: 0.4rem 0;
  border-radius: 3px;
  font-family: monospace;
  font-size: 0.9rem;
  display: flex;
  align-items: center;
  gap: 0.7rem;
  flex-wrap: wrap;
}
.post-error-banner > .post-error-message { flex: 1 1 18rem; }
.post-error-copy {
  font: inherit;
  font-size: 0.78rem;
  color: var(--accent);
  background: transparent;
  border: 1px solid var(--accent);
  border-radius: 999px;
  padding: 0.15rem 0.7rem;
  cursor: pointer;
  white-space: nowrap;
}
.post-error-copy:hover { background: var(--accent); color: var(--bg); }
.post-error-copy:disabled { opacity: 0.6; cursor: default; }

/* Sensitive content advisory — generic, not NSFW. The default rules ban
   porn (see operator-guide), so calling this "NSFW" would invite content
   the rules forbid. Operators/communities define what counts. */
.sensitive-banner {
  border: 1px solid var(--accent-warm);
  color: var(--accent-warm);
  padding: 0.4rem 0.7rem;
  margin: 0.4rem 0;
  border-radius: 3px;
  font-size: 0.85rem;
  font-family: monospace;
}
.sensitive-banner strong { text-decoration: underline dotted; text-underline-offset: 0.2em; }
.sensitive-mark { color: var(--accent-warm); font-family: monospace; font-size: 0.85em; }
.new-account-mark { color: var(--text-dim); font-family: monospace; font-size: 0.85em; }
/* Subscribed-state marker on the subs index. Hidden on desktop (the
 * subscribe column already conveys this); revealed at narrow widths
 * where the column hides, so the reader still knows which subs they're
 * in. Shape (monospace, 0.85em, bracketed text) mirrors .sensitive-mark
 * and .new-account-mark; color stays --text-dim since subscription is a
 * neutral state, not a warning. */
.subscribed-mark { display: none; color: var(--text-dim); font-family: monospace; font-size: 0.85em; }

/* Imported-sub banner (M7/B5). Sits below the sensitive-banner and above
   the sub-state-banner on /sub/<name>. Visually quieter than sensitive
   (no warning posture) — it's information about provenance, not a
   safety advisory. Uses the dim/border palette so the eye reads it as
   chrome the first time and skims past on later visits. */
.imported-banner {
  border: 1px solid var(--border);
  color: var(--text-dim);
  padding: 0.4rem 0.7rem;
  margin: 0.4rem 0;
  border-radius: 3px;
  font-size: 0.85rem;
  font-family: monospace;
}
.imported-banner a { color: var(--accent); }
.imported-banner strong { color: var(--text); }

/* Eval-image banner — full-width strip at the very top of every page.
   Only present on the docker eval image (PLATO_EVAL_BANNER=1). Reuses
   the sticky-note --note-bg fill so the "evaluation" signal doesn't
   add a second yellow shade to the palette. Centered, small, links
   inherit --accent. */
.eval-banner {
  background: var(--note-bg);
  color: var(--text);
  border-bottom: 1px solid var(--accent-warm);
  padding: 0.4rem 1rem;
  text-align: center;
  font-size: 0.85rem;
  font-family: monospace;
}
.eval-banner a { color: var(--accent); }

/* Sub sticky note (M8/B1) — the per-sub mod-voice slot above the feed.
   Full-frame yellow notice: filled background (--note-bg, theme-aware),
   warm-accent border on all four sides, normal text color for high
   contrast. Reads at-a-glance as "moderator speaking" — visually
   different from posts, the sensitive-banner (red-rim warning), and
   the imported-banner (dim chrome). */
.sub-sticky-note {
  border: 1px solid var(--accent-warm);
  background: var(--note-bg);
  color: var(--text);
  padding: 0.5rem 0.8rem;
  margin: 0.5rem 0 0.7rem;
  border-radius: 3px;
  font-size: 0.95rem;
}
.sub-sticky-note p { margin: 0.3rem 0; }
.sub-sticky-note p:first-child { margin-top: 0; }
.sub-sticky-note p:last-child  { margin-bottom: 0; }
.sub-sticky-note a { color: var(--accent); }

/* Sub edit page: the sticky-note form sits inside an h3 section like
   the rest of the manage page. Textarea inherits the form-styling but
   wants a couple of rows by default. */
.sub-sticky-edit-form { display: grid; gap: 0.3rem; margin-bottom: 0.8rem; }
.sub-sticky-edit-form textarea {
  min-height: 3.2em;
  font-family: monospace;
  font-size: 0.92rem;
}
.sub-sticky-edit-form button { justify-self: end; }

/* Inline [imported] tag in modlog action cells. Same dim posture as the
   imported-banner — informational, not warning. */
.imported-tag {
  color: var(--text-dim);
  font-size: 0.85em;
  margin-right: 0.2em;
}

/* Imported author label — every pseudonym whose handle came from an
   archive (handles.imported_from_fingerprint non-null). Two visual
   channels: opacity for the at-a-glance "background / inactive"
   posture, italic as the non-color hook so colorblind / high-contrast
   readers still get a signal. The aria-label on the span carries the
   meaning to assistive tech. The imported-banner + imported-chip carry
   provenance to readers who don't know the convention yet. */
.imported-author {
  opacity: 0.6;
  font-style: italic;
}

/* Compact [i] chip on inner sub-scoped pages (post detail, modlog,
   sub-edit). The full imported-banner only renders on the sub index;
   this chip carries the same provenance via its title attribute on
   hover so the signal persists across every page within an imported
   sub. Same dim palette as imported-banner. */
.imported-chip {
  color: var(--text-dim);
  font-size: 0.85em;
  font-family: monospace;
  margin-left: 0.3em;
  cursor: help;
}

/* Two-tab nav for /sub/create (create new / import from URL). M7/B5. */
.sub-create-tabs { margin: 0.5rem 0 1rem; display: flex; gap: 0.4rem; }
.sub-create-tabs a {
  padding: 0.3rem 0.7rem;
  border: 1px solid var(--border);
  border-radius: 3px;
  text-decoration: none;
  color: var(--text-dim);
  font-size: 0.85rem;
  font-family: monospace;
}
.sub-create-tabs a.active {
  color: var(--text);
  border-color: var(--accent);
  background: var(--bg-soft);
}

/* Sub create / import submit — pill-shape to match the tab strip
 * above. Width is auto (overrides form { display: grid }) so the
 * button doesn't stretch full-width like a primary CTA, and the
 * action lives on the right edge where the user finishes scanning
 * the form. */
.sub-create-form button {
  justify-self: end;
  padding: 0.3rem 0.9rem;
  font-size: 0.85rem;
  font-weight: normal;
  background: var(--bg-soft);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 3px;
}
.sub-create-form button:hover {
  border-color: var(--accent);
  color: var(--accent);
  filter: none;
}

/* Sub manage (owner edit) save: same right-edge placement, but
 * a hair wider so the bare word "save" doesn't sit cramped inside
 * the pill border. justify-self overrides form { display: grid }'s
 * full-width stretch. */
.sub-edit-form button.mod-action-pill {
  justify-self: end;
  padding: 0.3rem 1.4rem;
}

/* Inline subscribe / unsubscribe form on /sub/<name>. Sits in the top
   action row alongside ← home, public //modlog, edit sub. The form is
   invisible chrome around a single button so the row reads like the
   adjacent text links. */
.subscribe-form { display: inline; margin: 0; padding: 0; }
/* Sub-page action row. <p> can't legally contain <form> (form is flow,
   not phrasing content) — the HTML parser auto-closes the <p> at the
   <form> boundary, pushing subscribe onto a new line. <div> here keeps
   the row contiguous; margin mirrors the default <p> spacing so the
   visual rhythm is unchanged. */
.sub-action-row { margin: 1em 0; }
.subscribe-btn {
  background: none; border: none; color: var(--accent); cursor: pointer;
  padding: 0; font: inherit; text-decoration: none;
}
.subscribe-btn:hover { text-decoration: underline dotted; color: var(--text); }
.subscribe-btn:disabled { color: var(--text-dim); cursor: not-allowed; text-decoration: line-through; }
.subscribe-btn:disabled:hover { color: var(--text-dim); text-decoration: line-through; }

/* Sub-export "request archive" pill (M7/B2-b). Same visual language as
   .subscribe-btn — a quiet inline pill, struck-through when disabled. A
   distinct class lets future CSS diverge without touching subscribe. */
.export-form { display: inline; margin: 0; padding: 0; }
.export-btn {
  background: none; border: none; color: var(--accent); cursor: pointer;
  padding: 0; font: inherit; text-decoration: none;
}
.export-btn:hover { text-decoration: underline dotted; color: var(--text); }
.export-btn:disabled { color: var(--text-dim); cursor: not-allowed; text-decoration: line-through; }
.export-btn:disabled:hover { color: var(--text-dim); text-decoration: line-through; }

/* My-modlog inline revoke button. Renders only on rows where the
   current handle is the actor and the action's effect is still in
   place. POSTs to /sub/<sub>/mod with the inverse action. */
.modlog-revoke { display: inline; margin: 0; }
.modlog-revoke button { background: transparent; cursor: pointer; padding: 0.1rem 0.4rem; }
.modlog-revoke button:hover { color: var(--accent-warm); border-color: var(--accent-warm); }

/* Cross-sub comment feed (home /comments tab) */
.comment-feed .comment-row { padding: 0.7rem 0; border-bottom: 1px solid var(--border); }
.comment-feed .comment-row:last-child { border-bottom: 0; }
.comment-feed .comment-body { margin-top: 0.35rem; opacity: 0.92; }
.comment-feed .comment-body p:first-child { margin-top: 0; }
.comment-feed .comment-body p:last-child { margin-bottom: 0; }

/* ============================================================
 * UX-A2 (M5/B8): post-page + general spacing compaction
 * Reduces vertical airiness on the post page where there's lots
 * of empty space — section headers, article line-height, post
 * cards, form margins. Reading column inside <article> bodies
 * stays comfortable; the page chrome around it gets denser.
 * ============================================================ */
h3.section { margin: 1rem 0 0.4rem; }
.post-page article { margin-top: 0.4rem; }
.post-page > p:first-of-type { margin: 0.3rem 0 0.5rem; }
article { line-height: 1.55; margin-top: 0.6rem; }
article h1, article h2, article h3 { margin-top: 1rem; margin-bottom: 0.3rem; }
article p { margin: 0.5rem 0; }
article hr { margin: 1rem 0; }
form { margin: 0.6rem 0 1rem; gap: 6px; }
.preview { margin-top: 0.25rem; }
.preview p { margin: 0.3rem 0; }
.comment-tree { margin-top: 0.4rem; }
.comment { padding: 0.4rem 0; }
.replies { margin-top: 0.2rem; }

/* ============================================================
 * Mobile responsive (≤640px)
 *
 * Plato's default chrome is sized for ~880px desktops. On narrow
 * viewports two things hurt: the header (brand + status) overlaps
 * because flex-row doesn't wrap, and wide tables (memlog, modlog,
 * subs) overflow horizontally and scroll the whole page sideways.
 *
 * This block targets both with minimal layout surgery: header
 * wraps, body padding tightens, and content-bearing tables drop
 * non-critical columns so the row fits in one line within the
 * viewport. No page-level horizontal scroll under any normal
 * device width.
 * ============================================================ */
@media (max-width: 640px) {
  body { padding: 1rem 0.6rem 3rem; }

  /* Header: stack brand on top, status on its own row tucked right.
   * Wide-screen `space-between` row layout breaks down on mobile when
   * the title carries a page subtitle (e.g. "terribic · check your
   * email") or when status grows (logged-in user icon + handle).
   * Stacking gives both blocks the full viewport width — brand natural
   * left, status hugs right — so they don't fight for space. */
  header.site {
    flex-direction: column;
    align-items: stretch;
    gap: 0.4rem;
  }
  header.site .brand {
    overflow: visible;
    white-space: normal;
    min-width: 0;
  }
  header.site .brand h1 { white-space: normal; }
  /* Status block stacks under the brand on mobile, left-aligned to
   * match. The desktop layout (brand-left + status-right on one row)
   * relies on a second column to anchor the right-tuck against; once
   * we collapse to one column there's no second column, so left-align
   * reads as the natural "one column of stacked items" pattern that
   * every mobile site uses. Removes the orphan-token problem (a
   * single trailing item like `light` floating alone at the right
   * edge of an otherwise-empty wrap line). */
  .status {
    flex-wrap: wrap;
    gap: 0.3rem;
    font-size: 0.78rem;
  }

  /* Post-detail title: clamp h1 size on narrow viewports. Monospace
   * at 1.6rem is ~22px-per-char; a 25-char title at that size still
   * overflows a 320px iPhone SE viewport even with proper grid + flex
   * shrinking applied. Smaller font + the existing overflow-wrap
   * keeps long titles inside the column. */
  .post-title-line h1 { font-size: 1.25rem; }

  /* Mod feed actions wrap below the title at narrow widths. Desktop
   * lays h2/h1 + .post-actions inline (flex row); on a phone the
   * action chips ate enough horizontal space that long titles
   * crushed into 4–5 lines next to a column of buttons. Forcing
   * flex-basis:100% on the title pushes .post-actions to the next
   * row; the verb labels (collapse / remove / ban) stay text rather
   * than collapsing to icons.
   *
   * Specificity note: `.post .body h2 { flex: 1 }` further up
   * resets flex-basis to 0% via shorthand; a lower-specificity
   * `.post-title-line h2` rule can't override it. Match the .post
   * .body prefix here to win on specificity. */
  .post .body .post-title-line h1,
  .post .body .post-title-line h2 { flex-basis: 100%; }

  /* Filter chip rows wrap onto multiple lines when they overflow. */
  .modlog-filters,
  .modlog-modes { flex-wrap: wrap; row-gap: 0.3rem; }

  /* Tighter home-nav gap on narrow viewports for the snake-wrap rows. */
  .home-nav { gap: 0.4rem; row-gap: 0.45rem; }

  /* Memlog table: hide from + where columns at narrow widths so
   * type/when/kind/snippet still fit one row per line. The where
   * column is the only navigational loss; the snippet itself is
   * already a link to the same target. */
  .memlog-page table.modlog th:nth-child(4),
  .memlog-page table.modlog td:nth-child(4),
  .memlog-page table.modlog th:nth-child(5),
  .memlog-page table.modlog td:nth-child(5) { display: none; }
  .memlog-page table.modlog th:nth-child(1),
  .memlog-page table.modlog td:nth-child(1) { width: 3.2rem; }
  .memlog-page table.modlog th:nth-child(2),
  .memlog-page table.modlog td:nth-child(2) { width: 4rem; }
  .memlog-page table.modlog th:nth-child(3),
  .memlog-page table.modlog td:nth-child(3) { width: 5rem; }
  .memlog-page table.modlog th:nth-child(6),
  .memlog-page table.modlog td:nth-child(6) { width: auto; }

  /* Generic narrow-viewport safety: any wide table not specially
   * handled scrolls within its own block instead of spilling the
   * whole page sideways. Containers using table.modlog elsewhere
   * (mod /modlog, subs index) get this fallback. */
  table.modlog { display: block; overflow-x: auto; }
  .memlog-page table.modlog { display: table; overflow: visible; }

  /* /modlog pages: at narrow widths, flow row cells inline with a
   * dot-separator instead of stacking one cell per line. The first
   * attempt stacked (one LABEL: value row per cell), which turned
   * three audit entries into a full phone screen. Inline-flow reads
   * like the breadcrumb chrome elsewhere and packs a record into
   * one or two visual lines. Reason gets its own line because it's
   * the variable-length tail and benefits from breathing room.
   * Empty cells (null reason, single-event rows, non-revokable
   * rows) drop entirely via :empty. */
  .modlog-page table.modlog,
  .modlog-page table.modlog thead,
  .modlog-page table.modlog tbody,
  .modlog-page table.modlog tr { display: block; }
  .modlog-page table.modlog { overflow: visible; }
  .modlog-page table.modlog thead { display: none; }
  .modlog-page table.modlog tr {
    border: none;
    border-bottom: 1px solid var(--border);
    padding: 0.55rem 0;
    line-height: 1.5;
  }
  .modlog-page table.modlog td {
    display: inline;
    border: none;
    padding: 0;
  }
  .modlog-page table.modlog td:empty { display: none; }
  .modlog-page table.modlog td:not(:empty) ~ td:not(:empty)::before {
    content: " · ";
    color: var(--text-dim);
  }
  /* Reason wraps to its own line when present; suppress the dot-separator
   * before and after it so the line-break replaces the bullet visually.
   * :not(:empty) guards the display: block — without it, an empty reason
   * cell beats the global td:empty { display: none } on specificity and
   * leaves a 0-height block with a margin. */
  .modlog-page table.modlog td[data-label="reason"]:not(:empty) {
    display: block;
    margin-top: 0.15rem;
    font-size: 0.9rem;
  }
  .modlog-page table.modlog td[data-label="reason"]::before,
  .modlog-page table.modlog td[data-label="reason"] ~ td:not(:empty)::before { content: ""; }

  /* Subs index: drop description, subscribers (placeholder anyway),
   * owner, and the subscribe-action column. Keeps sub link, post
   * count, and last-active — the navigational essentials. The
   * subscribe action moves to the per-sub page; subscription state
   * surfaces via .subscribed-mark inline after the sub name. */
  table.communities th:nth-child(2),
  table.communities td:nth-child(2),
  table.communities th:nth-child(4),
  table.communities td:nth-child(4),
  table.communities th:nth-child(6),
  table.communities td:nth-child(6),
  table.communities th:nth-child(7),
  table.communities td:nth-child(7) { display: none; }
  table.communities { table-layout: auto; width: 100%; }
  table.communities th, table.communities td {
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  .subscribed-mark { display: inline; margin-left: 0.3rem; }

  /* Forms: inputs stretch to viewport rather than overflowing. */
  input[type="text"], input[type="email"], input[type="number"],
  input[type="url"], textarea, select { max-width: 100%; }

  /* Sub manage page: the flair-editor row (label input + color box + 8
   * preset swatches) and the threshold rows (fixed-basis label span +
   * fixed-basis number input) were sized for desktop and overflowed
   * past a 390px viewport because their flex children couldn't shrink
   * below intrinsic widths. Stack the flair row vertically and let
   * threshold span/input wrap, so every child has the full row to itself. */
  .flair-editor .flair-row {
    flex-direction: column;
    align-items: stretch;
  }
  .flair-editor .flair-row-label { flex-basis: auto; }
  .flair-editor .flair-row-color { width: 100%; }
  .sub-thresholds .threshold-row { flex-wrap: wrap; }
  .sub-thresholds .threshold-row > span { flex: 1 1 100%; }
  .sub-thresholds .threshold-row > input[type="number"] { flex: 0 0 5rem; }

  /* Login popover: anchor to viewport edge so it doesn't push off
   * screen on the narrowest phones. */
  /* Login dropdown: switch to viewport-anchored positioning on narrow
   * viewports. The default position:absolute anchors to .login-trigger
   * (~50px wide), so a 14rem-wide form extended past the viewport's
   * left edge. position:fixed pins to the viewport directly, and
   * left/right both clamped means the form spans width-with-gutter.
   * Input's flex:1 1 0 + width:0 overrides Safari's default input
   * sizing (which uses size=20 even when CSS min-width is 0). */
  .login-form {
    position: fixed;
    top: 3.6rem;
    left: 0.6rem;
    right: 0.6rem;
    min-width: 0;
  }
  .login-form input[type="email"] {
    min-width: 0;
    width: 0;
    flex: 1 1 0;
  }
}
