/* Shared theme across every Quorum page. Five built-in presets (plus a "custom" one a
   super_admin can hand-tune or blend from two presets, in admin.html) live here as CSS
   custom properties keyed off `body[data-theme="…"]`, set at runtime by shared.js's
   applyTheme() after it reads GET /api/org-settings. "current" is the original single-file
   app's animated rainbow-gradient look, kept as the default so nothing changes until an
   admin actually picks something else. */

* { box-sizing: border-box; }

:root {
  /* Fallback tokens (the "current" preset's values) -- used before JS has applied a
     theme at all, e.g. very first paint or if applyTheme() never runs for some reason. */
  --surface: linear-gradient(145deg, #2a2a2a, #1a1a1a);
  --surface-border: rgba(255, 255, 255, 0.12);
  --text: #fff;
  --text-dim: rgba(255, 255, 255, 0.75);
  --brand: #22c55e;
  --brand2: #16a34a;
  --accent: #3b82f6;
  --accent2: #1d4ed8;
  --danger: #ef4444;
  --danger2: #b91c1c;
  /* FX2 W3-SHARED (AR2-12-04, owner A-6): button label and gradient stops are their own tokens, so a theme can darken the
     button without changing --brand / --accent (those also paint tab underlines, borders and the custom theme's tiles). Every
     stop of every built-in preset is >= 4.5:1 against --btn-text (test/fx2-w3-shared-contrast.test.ts computes it per stop;
     axe cannot, it files text over a gradient as "incomplete"). The 5.0-6.7:1 values below replace #22c55e / #3b82f6 / #ef4444
     (2.28 / 3.68 / 3.76:1 under white text). */
  --btn-text: #fff;
  --btn-primary-from: #15803d;
  --btn-primary-to: #166534;
  --btn-secondary-from: #2563eb;
  --btn-secondary-to: #1d4ed8;
  --btn-danger-from: #dc2626;
  --btn-danger-to: #b91c1c;
  /* V-19 / EX-14: the text of .archived-badge (red tint, below); light themes override it with a dark red. */
  --archived-badge-text: #fca5a5;
  --radius: 12px;
  --card-shadow: 0 6px 14px rgba(0, 0, 0, 0.4), inset 0 0 6px rgba(255, 255, 255, 0.05);
  /* UI wave BR-4: the platform font from /brand.css (--product-font-*) leads, the stock stack is the fallback; org themes
     below still override --heading-font / --pin-font per body[data-theme]. */
  --heading-font: var(--product-font-display, "Segoe UI", system-ui, sans-serif);
  --pin-font: "Segoe UI", system-ui, sans-serif;
  /* FR1 W3-SHARED (M076): was #93c5fd, 3.0:1 over the gradient's purple stop once that was darkened for white text. */
  --link: #bfdbfe;
  --input-bg: rgba(0, 0, 0, 0.35);
  --input-border: rgba(255, 255, 255, 0.2);
  --row-border: rgba(255, 255, 255, 0.08);
  /* Answer-tile colors -- host.html/play.html reference these instead of hardcoding a
     literal red/blue/amber/green scheme, the single biggest "this looks like a direct
     copy of another quiz game" tell the whole theme-rethink project was about. "current" is the one
     preset that deliberately KEEPS that literal scheme (it's the pre-existing look,
     unchanged unless an admin picks something else). */
  /* UI wave TH-V1 (EX234 2.3 / 2.5 option a): the fallback tiles are palette A "Ledger" (same hexes as --q-tile-1..8 in css/tokens.css),
     flat fills; the label colour per tile is set by the "current" and "papershow" blocks below (--tile-N-text), the other presets
     override all eight tiles themselves. Old (replaced) fallbacks: 145deg gradients #dc2626->#991b1b, #2563eb->#1d4ed8, #b45309->#92400e,
     #15803d->#166534, #9333ea->#6b21a8, #0f766e->#115e59, #be185d->#9d174d, #4f46e5->#3730a3 (red / blue / amber / green, the order the
     theme rethink was about). */
  --tile-1: #0a505b;
  --tile-2: #ffd25a;
  --tile-3: #b4461e;
  --tile-4: #5aa9da;
  --tile-5: #7d2f7d;
  --tile-6: #9bd18a;
  --tile-7: #b9bfc9;
  --tile-8: #f4b9a8;
  --tile-radius: 12px;
  --tile-text: #fff;
  /* FR1 W4-CLEANUP: a theme whose tile colours do not all suit one label colour sets --tile-N-text for the odd ones out (see the
     per-tile rules after the theme presets). Unset = the theme's --tile-text, so nothing changes for any other theme. */
  /* Round 125 (M11): colours that used to be hard-coded for a dark background, as tokens the light themes
     (papershow, custom in light mode) override below. --correct marks a question's correct answer (bank editor,
     platform review); --badge-text is the text on the saturated role/status badges, whose own backgrounds are dark
     in every theme; --muted-opacity / --tab-inactive-opacity fade secondary text, which dark-on-light can afford
     much less of than light-on-dark (a .muted nested in a .muted fell to 3.2:1 on Paper show). */
  --correct: #4ade80;
  --badge-text: #fff;
  --muted-opacity: 0.85; /* FR1 W3-SHARED (M076): was 0.7 (1.76:1 over the default gradient). */
  --tab-inactive-opacity: 0.6;
}

html, body {
  margin: 0;
  min-height: 100vh;
  min-height: 100dvh; /* DC3D: dvh is the visible viewport (the large one, 100vh, is ~56 px too tall while a phone URL bar shows and beat play.html's height: 100dvh); the 100vh line above is only the fallback for browsers without dvh */
  font-family: var(--product-font-sans, "Segoe UI", system-ui, sans-serif);
  color: var(--text);
}

/* -- theme presets --------------------------------------------------------------- */

body[data-theme="current"], body:not([data-theme]) {
  /* UI wave TH-V1 (EX234 2.5 option a, "alias"): "current" stays the stored theme id (organizations.theme_id DEFAULT 'current', no
     migration) but renders the new default look: palette A "Ledger" answer tiles, petrol chrome. The old look was an animated
     rainbow gradient (#1e3a8a, #7e22ce, #166534, #92400e; M076 / A7-08) with red / blue / amber / green tiles; both are gone from every
     theme. The background keeps a slow drift (a compositor-only cross-fade, see D1-GRADIENT below; off under prefers-reduced-motion and
     the in-app Reduce motion switch) but only between petrol tints, each >= 9:1 under white text, so the M076 contrast contract
     (white text, .muted and links >= 4.5:1 over the whole cycle) holds with room. Contrast numbers are computed in test/theme-palette.test.ts. */
  background: linear-gradient(-45deg, #061f24, #0a505b, #08323a, #0b3a42);
  background-size: 400% 400%;
  --surface: linear-gradient(145deg, #0d3f48, #082c33);
  --surface-border: rgba(255, 255, 255, 0.18);
  --brand: #0b6370;
  --brand2: #0a505b;
  --accent: #f7b500; /* marigold: a fill / underline colour only, never a text colour */
  --accent2: #d49a00;
  --btn-primary-from: #0b6370;
  --btn-primary-to: #0a505b;
  --btn-secondary-from: #3f4a5e;
  --btn-secondary-to: #2d3748;
  --link: #9fd3da;
  /* Palette A, one flat fill per tile; the label is white or ink (#161b26), whichever the fill passes (>= 4.5:1, computed in the test). */
  --tile-1: #0a505b;
  --tile-2: #ffd25a;
  --tile-3: #b4461e;
  --tile-4: #5aa9da;
  --tile-5: #7d2f7d;
  --tile-6: #9bd18a;
  --tile-7: #b9bfc9;
  --tile-8: #f4b9a8;
  --tile-text: #fff;
  --tile-2-text: #161b26;
  --tile-4-text: #161b26;
  --tile-6-text: #161b26;
  --tile-7-text: #161b26;
  --tile-8-text: #161b26;
  /* A mixed-lightness set cannot reach 3:1 against one ground, so the tile EDGE carries it (EX234 2.3): 2px ring, 3.3:1 or better
     against the lightest background stop. */
  --tile-border: 2px solid #919ea6;
}
/* D1-GRADIENT (FIX-A): the drift used to animate `background-position` on <body> forever. That repaints the whole canvas every frame,
   on a console page with about 23,000 nodes a full-page paint 60 times a second. The drift is now an opacity cross-fade between two
   fixed viewport-sized layers (body::before at gradient position 0%, body::after at 100%), which the compositor runs without
   repainting the page; it runs 3 cycles of 20 s and then rests on the first layer, so an idle projector page is not animating
   for hours. The body keeps its own static `background` above: it is what shows under prefers-reduced-motion, under the in-app
   Reduce motion switch (<html data-motion="reduced">), in print, and on a page with data-ui="q" (legacy-bridge.css paints that
   body itself). The layers use the SAME four stops as the body rule (test/shared-css-motion.test.ts pins that), and a cross-fade
   of two colours taken from that gradient is never lighter than the lighter of the two, so the contrast contract above holds
   at every opacity. `gradientBG` keeps its name; it now animates opacity only. */
@keyframes gradientBG {
  0%, 100% { opacity: 0; }
  50% { opacity: 1; }
}
@media screen and (prefers-reduced-motion: no-preference) {
  html:not([data-motion="reduced"]) body[data-theme="current"]:not([data-ui="q"])::before,
  html:not([data-motion="reduced"]) body[data-theme="current"]:not([data-ui="q"])::after,
  html:not([data-motion="reduced"]) body:not([data-theme]):not([data-ui="q"])::before,
  html:not([data-motion="reduced"]) body:not([data-theme]):not([data-ui="q"])::after {
    content: "";
    position: fixed;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    z-index: -1;
    pointer-events: none;
    background: linear-gradient(-45deg, #061f24, #0a505b, #08323a, #0b3a42);
    background-size: 400% 400%;
    background-position: 0% 50%;
  }
  html:not([data-motion="reduced"]) body[data-theme="current"]:not([data-ui="q"])::after,
  html:not([data-motion="reduced"]) body:not([data-theme]):not([data-ui="q"])::after {
    background-position: 100% 50%;
    opacity: 0;
    will-change: opacity;
    animation: gradientBG 20s ease-in-out 3 forwards;
  }
}

/* Round 124 (F9): a visitor who asked their OS for reduced motion still got the perpetual 20 s background drift above on
   every page, plus confetti / reaction-flash / hover-lift animation. Under prefers-reduced-motion: reduce every animation
   and transition is switched off (the static gradient stays), and the decorative confetti pieces are not shown at all. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation: none !important;
    transition: none !important;
    scroll-behavior: auto !important;
  }
  .confetti-piece { display: none !important; }
}

body[data-theme="signal"] {
  --surface: linear-gradient(155deg, #1b1b29, #14141f);
  --surface-border: rgba(255, 255, 255, 0.08);
  --text: #f1f0f7;
  --text-dim: rgba(241, 240, 247, 0.62);
  --brand: #6c5ce7;
  --brand2: #4b3fb0;
  --accent: #ff8a5b;
  --accent2: #d96035;
  /* FX2 W3-SHARED (AR2-12-04): the orange secondary stops gave white 2.05:1. */
  --btn-secondary-from: #c2410c;
  --btn-secondary-to: #9a3412;
  --radius: 16px;
  --card-shadow: none;
  --heading-font: -apple-system, "Segoe UI Semibold", system-ui, sans-serif;
  --pin-font: -apple-system, "Segoe UI Semibold", system-ui, sans-serif;
  --link: #b8adf5;
  --input-bg: rgba(0, 0, 0, 0.3);
  --input-border: rgba(255, 255, 255, 0.15);
  --row-border: rgba(255, 255, 255, 0.08);
  --tile-1: linear-gradient(135deg, #6c5ce7, #4b3fb0);
  /* FX2 final V&C (BUG FX2-W4E2E-1): tiles 2-8 were 1.84-3.96:1 under the white label at their light stops (#8b7bf0, #ff8a5b, #ffab85,
     #a855f7, #2dd4bf, #fb7185, #60a5fa and the orange ends); each stop that failed was darkened toward black (same hue and saturation)
     until every stop and mid-point is >= 4.5:1. Old stops: 135deg #8b7bf0 -> #5c4fce / #ff8a5b -> #d96035 / #ffab85 -> #e57a4c /
     #a855f7 -> #7e22ce / #2dd4bf -> #0d9488 / #fb7185 -> #be123c / #60a5fa -> #2563eb. */
  --tile-2: linear-gradient(135deg, #7467c8, #5c4fce);
  --tile-3: linear-gradient(135deg, #af5f3e, #be542e);
  --tile-4: linear-gradient(135deg, #9a6750, #b05e3b);
  --tile-5: linear-gradient(135deg, #994de1, #7e22ce);
  --tile-6: linear-gradient(135deg, #1b8175, #0b8278);
  --tile-7: linear-gradient(135deg, #ba5462, #be123c);
  --tile-8: linear-gradient(135deg, #4576b3, #2563eb);
  --tile-radius: 16px;
  --tile-text: #fff;
  background:
    radial-gradient(circle at 20% 15%, rgba(108, 92, 231, 0.35), transparent 45%),
    radial-gradient(circle at 85% 75%, rgba(255, 138, 91, 0.22), transparent 50%),
    linear-gradient(160deg, #0b0b12, #14101f, #0b0b12);
}

body[data-theme="arena"] {
  --surface: #0d0d14;
  --surface-border: rgba(124, 58, 237, 0.35);
  --text: #f4f4fb;
  --text-dim: rgba(244, 244, 251, 0.6);
  --brand: #7c3aed;
  --brand2: #22d3ee;
  --accent: #22d3ee;
  --accent2: #7c3aed;
  /* FX2 W3-SHARED (AR2-12-04): the cyan stop gave white 1.65:1; both buttons keep the purple-to-teal look with darker ends. */
  --btn-primary-from: #6d28d9;
  --btn-primary-to: #0e7490;
  --btn-secondary-from: #0e7490;
  --btn-secondary-to: #6d28d9;
  --radius: 4px;
  --card-shadow: 0 0 24px rgba(124, 58, 237, 0.15);
  --heading-font: -apple-system, "Segoe UI Semibold", system-ui, sans-serif;
  --pin-font: "Consolas", "SFMono-Regular", "Courier New", monospace;
  --link: #67e8f9;
  --input-bg: rgba(0, 0, 0, 0.4);
  --input-border: rgba(124, 58, 237, 0.4);
  --row-border: rgba(124, 58, 237, 0.2);
  /* FX2 final V&C (BUG FX2-W4E2E-1): the cyan / pink / green / yellow / light-purple stops were 1.53-3.96:1 under the white label; darkened
     toward black (same hue) to >= 4.5:1. Old: #22d3ee, #a855f7, #f472b6, #34d399, #facc15 at the light stops. */
  --tile-1: linear-gradient(135deg, #7c3aed, #158090);
  --tile-2: linear-gradient(135deg, #158090, #7c3aed);
  --tile-3: linear-gradient(135deg, #994de1, #4b3fb0);
  --tile-4: linear-gradient(135deg, #4b3fb0, #994de1);
  --tile-5: linear-gradient(135deg, #158090, #994de1);
  --tile-6: linear-gradient(135deg, #b25385, #7c3aed);
  --tile-7: linear-gradient(135deg, #20835f, #158090);
  --tile-8: linear-gradient(135deg, #8b710c, #7c3aed);
  --tile-radius: 4px;
  --tile-clip: polygon(10px 0, 100% 0, 100% calc(100% - 10px), calc(100% - 10px) 100%, 0 100%, 0 10px);
  --tile-text: #fff;
  background: radial-gradient(ellipse at 50% -10%, rgba(124, 58, 237, 0.28), transparent 55%), #050507;
}

body[data-theme="papershow"] {
  --surface: #fbf8f1;
  --surface-border: #1a1a1a;
  --text: #1a1a1a;
  --text-dim: #55524a;
  --brand: #d94f3d;
  --brand2: #d94f3d;
  --accent: #3d6fd9;
  --accent2: #3d6fd9;
  --radius: 2px;
  --card-shadow: none;
  --heading-font: Georgia, "Times New Roman", serif;
  --pin-font: Georgia, "Times New Roman", serif;
  /* Round 125 (M11): #3d6fd9 was 2.6:1 inside a .muted paragraph. */
  --link: #1f4aa8;
  --correct: #14532d;
  --archived-badge-text: #7f1d1d; /* FX2 W3-SHARED (V-19): #fca5a5 on the cream card was ~1.2:1. */
  /* FX2 W3-SHARED (AR2-12-04): buttons carry a white label on a darker red / blue (the page ink #1a1a1a gave 2.7-4.3:1). */
  --btn-primary-from: #c0392b;
  --btn-primary-to: #b03a2e;
  --btn-secondary-from: #3d6fd9;
  --btn-secondary-to: #2f5bc0;
  --muted-opacity: 0.85;
  --tab-inactive-opacity: 0.75;
  --input-bg: #fff;
  --input-border: #1a1a1a;
  --row-border: rgba(26, 26, 26, 0.15);
  /* UI wave TH-V1 (EX234 2.5): the tiles are re-based on palette A "Ledger" hues (the old set was red / blue / amber / green, the
     order the theme rethink was about). The label is white or the page ink #161b26, whichever the fill passes. Old fills: #c0392b,
     #3d6fd9, #e0a63a, #3f9e63, #7c3aed, #2a9d8f, #d46a9f, #6b7280 (and the AR2-10-10 / W4-CLEANUP per-tile labels that went with them). */
  --tile-1: #0a505b;
  --tile-2: #ffd25a;
  --tile-3: #b4461e;
  --tile-4: #5aa9da;
  --tile-5: #7d2f7d;
  --tile-6: #9bd18a;
  --tile-7: #b9bfc9;
  --tile-8: #f4b9a8;
  --tile-radius: 2px;
  --tile-border: 2px solid #1a1a1a;
  --tile-text: #fff;
  /* TODO(delete-after-project): the previous papershow block set the tile label colour for tiles 3, 4, 6 and 7 (the four lines named tile-3-text, tile-4-text, tile-6-text, tile-7-text) to a near-black ink; palette A replaced them with the lines below (tiles 2, 4, 6, 7, 8 in the dark ink). Old values: git HEAD ff5f8ca and TO_DELETE_AT_PROJECT_END.md (FIX-C). */
  --tile-2-text: #161b26;
  --tile-4-text: #161b26;
  --tile-6-text: #161b26;
  --tile-7-text: #161b26;
  --tile-8-text: #161b26;
  background: #f5f1e8;
}

body[data-theme="terminal"] {
  --surface: transparent;
  --surface-border: #5eead4;
  --text: #d7fff7;
  --text-dim: rgba(215, 255, 247, 0.55);
  --brand: #5eead4;
  --brand2: #a78bfa;
  --accent: #5eead4;
  --accent2: #a78bfa;
  /* FX2 W3-SHARED (AR2-12-04): light teal / violet stops need the dark terminal ink (the light page text gave 1.38:1). */
  --btn-text: #0a0c0d;
  --btn-primary-from: #5eead4;
  --btn-primary-to: #a78bfa;
  --btn-secondary-from: #5eead4;
  --btn-secondary-to: #a78bfa;
  --btn-danger-from: #f87171;
  --btn-danger-to: #ef4444;
  --radius: 0px;
  --card-shadow: inset 0 0 0 1px rgba(94, 234, 212, 0.15);
  --heading-font: "Consolas", "SFMono-Regular", "Courier New", monospace;
  --pin-font: "Consolas", "SFMono-Regular", "Courier New", monospace;
  --link: #a78bfa;
  --input-bg: rgba(0, 0, 0, 0.4);
  --input-border: #5eead4;
  --row-border: rgba(94, 234, 212, 0.2);
  --tile-1: transparent;
  --tile-2: transparent;
  --tile-3: transparent;
  --tile-4: transparent;
  --tile-5: transparent;
  --tile-6: transparent;
  --tile-7: transparent;
  --tile-8: transparent;
  --tile-radius: 0px;
  --tile-border: 1px solid #5eead4;
  --tile-text: #5eead4;
  background-color: #0a0c0d;
  background-image: linear-gradient(#161a1b 1px, transparent 1px), linear-gradient(90deg, #161a1b 1px, transparent 1px);
  background-size: 22px 22px;
}

/* "custom" starts from the signal preset's structure, then shared.js's applyTheme()
   overrides --brand/--brand2/--accent/--accent2/--heading-font/--pin-font and toggles
   dark/light via inline style -- inline styles on body always win over these rules. */
body[data-theme="custom"] {
  --surface: linear-gradient(155deg, #1b1b29, #14141f);
  --surface-border: rgba(255, 255, 255, 0.08);
  --text: #f1f0f7;
  --text-dim: rgba(241, 240, 247, 0.62);
  /* FX2 W3-SHARED (AR2-12-04): the admin's brand / accent colours can be any hue, so the button fills are those colours pulled 38%
     towards black under a white label (both Round 125 palettes are asserted >= 4.5:1 in test/fx2-w3-shared-contrast.test.ts). A very bright pick can still be under 4.5:1; the
     palette preview on the admin page is where that is flagged (AR2-12-03), the CSS cannot choose a colour for it. */
  --btn-primary-from: color-mix(in srgb, var(--brand) 62%, #000);
  --btn-primary-to: color-mix(in srgb, var(--brand2) 62%, #000);
  --btn-secondary-from: color-mix(in srgb, var(--accent) 62%, #000);
  --btn-secondary-to: color-mix(in srgb, var(--accent2) 62%, #000);
  --radius: 14px;
  --card-shadow: none;
  --link: #b8adf5;
  --input-bg: rgba(0, 0, 0, 0.3);
  --input-border: rgba(255, 255, 255, 0.15);
  --row-border: rgba(255, 255, 255, 0.08);
  /* Derived from --brand/--accent (set inline by shared.js's applyTheme(), or a blend
     computed in admin.html) rather than hardcoded -- a hand-picked or blended palette
     automatically produces a matching tile set with no separate customization surface. */
  --tile-1: linear-gradient(135deg, var(--brand), var(--brand2));
  --tile-2: linear-gradient(135deg, var(--accent), var(--accent2));
  --tile-3: linear-gradient(135deg, var(--brand2), var(--accent));
  --tile-4: linear-gradient(135deg, var(--accent2), var(--brand));
  --tile-5: linear-gradient(135deg, var(--brand), var(--accent));
  --tile-6: linear-gradient(135deg, var(--brand2), var(--accent2));
  --tile-7: linear-gradient(135deg, var(--accent), var(--brand2));
  --tile-8: linear-gradient(135deg, var(--brand2), var(--brand));
  --tile-text: #fff;
  background: radial-gradient(circle at 25% 20%, color-mix(in srgb, var(--brand) 30%, transparent), transparent 50%), #0f0f16;
}
body[data-theme="custom"][data-theme-mode="light"] {
  --surface: #fbf8f1;
  --surface-border: rgba(26, 26, 26, 0.2);
  --text: #1a1a1a;
  --text-dim: #55524a;
  /* Round 125 (M11): the dark custom preset's #b8adf5 link (inherited from the rule above) was 1.55:1 here. Same
     light-theme tokens as papershow. */
  --link: #1f4aa8;
  --correct: #14532d;
  --archived-badge-text: #7f1d1d;
  --muted-opacity: 0.85;
  --tab-inactive-opacity: 0.75;
  --input-bg: #fff;
  --input-border: rgba(26, 26, 26, 0.25);
  --row-border: rgba(26, 26, 26, 0.12);
  background: radial-gradient(circle at 25% 20%, color-mix(in srgb, var(--brand) 18%, transparent), transparent 50%), #f5f1e8;
}

/* FR1 W4-CLEANUP: per-tile label colour. host.html/play.html number their tiles .tile-0 ... .tile-7 for --tile-1 ... --tile-8; a theme can give
   tile N its own label colour with --tile-N-text, and every other tile keeps --tile-text exactly as before. */
.tile.tile-0 { color: var(--tile-1-text, var(--tile-text, #fff)); }
.tile.tile-1 { color: var(--tile-2-text, var(--tile-text, #fff)); }
.tile.tile-2 { color: var(--tile-3-text, var(--tile-text, #fff)); }
.tile.tile-3 { color: var(--tile-4-text, var(--tile-text, #fff)); }
.tile.tile-4 { color: var(--tile-5-text, var(--tile-text, #fff)); }
.tile.tile-5 { color: var(--tile-6-text, var(--tile-text, #fff)); }
.tile.tile-6 { color: var(--tile-7-text, var(--tile-text, #fff)); }
.tile.tile-7 { color: var(--tile-8-text, var(--tile-text, #fff)); }

/* -- shared components, all theme-driven from here on --------------------------------- */

a { color: var(--link); }

.hidden { display: none !important; }

/* Visually hidden but still in the accessibility tree -- unlike .hidden (display:none), which
   removes an element from it entirely. Used for aria-live regions (audit round 36, F5). */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

header.app-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 14px 24px;
  flex-wrap: wrap;
}

.brand { display: flex; align-items: center; gap: 12px; }
.brand .logo { max-height: 42px; max-width: 180px; border-radius: 6px; }
.brand .logo-placeholder {
  display: flex; align-items: center; gap: 6px;
  padding: 6px 12px; border: 2px dashed var(--surface-border); border-radius: 8px;
  font-size: 0.8rem; opacity: 0.85;
}
/* The generic Quorum mark shown in the placeholder above before any real organization's
   branding is known -- see shared.js's injectHeader()/play.html's own header comment. */
.brand .logo-placeholder-icon { width: 18px; height: 18px; border-radius: 4px; flex-shrink: 0; }
.brand .org-name { font-family: var(--heading-font); font-size: 1.15rem; font-weight: 700; }

/* Round 2026-09-26 (platform.html viewport-fit pass): header.app-header already wraps
   .brand and .header-actions onto separate lines when needed, but .header-actions's OWN
   children never wrapped -- a platform owner's header can show whoami text plus up to 4 nav
   buttons (Dashboard/Admin/Platform/Log out, shared.js's injectHeader), which genuinely
   overflows a narrow phone width (measured: 320px viewport, 386px-wide .header-actions)
   regardless of the page's own content below it. platform.html is only ever reachable by a
   platform owner, so this is a real, always-on overflow for that page's real narrow-viewport
   audience, not a hypothetical -- fixed here in shared.css (not scoped to one page's <style>
   block) since the bug is in the shared header component every page uses, and affects
   whichever facilitator role happens to see the most buttons at once, not platform.html
   specifically. flex-wrap: wrap is additive -- at any width wide enough for every item to
   already fit on one line, nothing about the rendered layout changes. */
.header-actions { display: flex; align-items: center; gap: 10px; font-size: 0.9rem; flex-wrap: wrap; }
.header-actions .whoami { opacity: 0.85; }

main.app-main {
  max-width: 1000px;
  margin: 0 auto;
  padding: 10px 24px 60px;
}

h1, h2, h3 { margin: 0 0 12px; font-family: var(--heading-font); }
h1 { font-size: 1.7rem; }
h2 { font-size: 1.25rem; }

.card {
  background: var(--surface);
  border: 1px solid var(--surface-border);
  box-shadow: var(--card-shadow);
  border-radius: var(--radius);
  padding: 20px;
  margin-bottom: 20px;
}

.grid { display: grid; gap: 16px; }
.grid-2 { grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); }

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  padding: 9px 16px; border: none; border-radius: var(--radius); cursor: pointer;
  font-size: 0.92rem; font-weight: 600; color: var(--text);
  background: var(--surface);
  border: 1px solid var(--surface-border);
  transition: transform 0.12s ease;
}
.btn:hover { transform: translateY(-1px); }
.btn:disabled { opacity: 0.5; cursor: not-allowed; transform: none; }
/* FX2 W3-SHARED (AR2-12-04): the label colour is the button's own token, not the theme's page ink (see :root). */
.btn-primary { background: linear-gradient(145deg, var(--btn-primary-from), var(--btn-primary-to)); color: var(--btn-text); border-color: transparent; }
.btn-danger { background: linear-gradient(145deg, var(--btn-danger-from), var(--btn-danger-to)); color: var(--btn-text); border-color: transparent; }
.btn-secondary { background: linear-gradient(145deg, var(--btn-secondary-from), var(--btn-secondary-to)); color: var(--btn-text); border-color: transparent; }
.btn-sm { padding: 5px 10px; font-size: 0.8rem; }

.input, textarea.input, select.input {
  width: 100%; padding: 9px 12px; border-radius: var(--radius); border: 1px solid var(--input-border);
  background: var(--input-bg); color: var(--text); font-size: 0.92rem; font-family: inherit;
}
.input::placeholder { color: var(--text-dim); }
label { display: block; margin-bottom: 5px; font-size: 0.85rem; opacity: 0.85; }
.field { margin-bottom: 14px; }

.badge {
  display: inline-block; padding: 2px 9px; border-radius: 999px; font-size: 0.75rem;
  font-weight: 700; text-transform: uppercase; letter-spacing: 0.03em;
}
.badge-private { background: rgba(128, 128, 128, 0.25); }
/* Round 125 (M11): these backgrounds are dark in every theme, so their text is too light to inherit var(--text) --
 * on Paper show that made #1a1a1a on #7f1d1d (1.73:1). */
.badge-pending_review { background: #92400e; color: var(--badge-text); }
.badge-approved { background: #166534; color: var(--badge-text); }
.badge-rejected { background: #7f1d1d; color: var(--badge-text); }
.badge-facilitator { background: rgba(128, 128, 128, 0.25); }
.badge-admin { background: #1e40af; color: var(--badge-text); }
.badge-super_admin { background: #6d28d9; color: var(--badge-text); }

/* FX2 W3-SHARED (V-19 / EX-14): the "Archived" / "platform owner" / "phantom" pill used by platform.js and admin.js. platform.html still
 * carries its own inline copy of the base rule; the `body` prefix makes this one win so the text colour follows the theme (the fixed
 * #fca5a5 was ~1.2:1 on Paper show's cream card). The red tint is the same 25% as before. */
body .archived-badge {
  background: rgba(239, 68, 68, 0.25); color: var(--archived-badge-text, #fca5a5);
  padding: 2px 10px; border-radius: 999px; font-size: 0.8rem; font-weight: 700;
}

/* FX2 W3-SHARED (GA-5-05): the platform owner's maintenance notice (shared.js loadMaintenanceBanner), at the top of every staff and
 * player page. Fixed amber / dark-brown colours (11.5:1), not theme tokens: it must read the same under every theme. Not sticky, so it
 * never covers a live question. The text is set with textContent only. */
.maintenance-banner {
  display: block; padding: 10px 18px; font-size: 0.92rem; font-weight: 600; text-align: center;
  background: #fef3c7; color: #451a03; border-bottom: 2px solid #b45309; overflow-wrap: anywhere;
}

table.list { width: 100%; border-collapse: collapse; }
table.list th, table.list td {
  text-align: left; padding: 10px 8px; border-bottom: 1px solid var(--row-border);
  font-size: 0.9rem;
}
table.list th { opacity: 0.7; font-weight: 600; font-size: 0.78rem; text-transform: uppercase; }

/* W3-F (ENG-7 results UI; also v2/css/dashboard.css, hw-host.css, admin.css): the leaderboard with identifier columns scrolls sideways
   inside its own focusable region (contain: inline-size keeps a wide table from stretching the table cell it sits in, so the page itself
   never scrolls sideways, down to 320 px), plus the Columns menu and the downloads' identifier choice. */
.rid-scroll { overflow-x: auto; max-width: 100%; contain: inline-size; }
/* dashboard.html's sessions list is itself a 560px-minimum, sideways-scrolling table (#tab-sessions table.list): inside it the results region is
   pinned to the visible width, so the leaderboard scrolls on its own instead of widening the list. */
#tab-sessions table.list.rid-table { min-width: 0; }
#tab-sessions .rid-scroll { position: sticky; left: 0; width: min(100%, calc(100vw - 3rem)); }
.rid-table td, .rid-table th { vertical-align: top; overflow-wrap: break-word; }
.rid-cols { margin: 8px 0; }
.rid-cols summary { cursor: pointer; display: inline-block; padding: 6px 0; }
.rid-fieldset { border: 0; margin: 0; padding: 4px 0 0; min-width: 0; }
.rid-legend { font-size: 0.85rem; padding: 0; margin-bottom: 4px; }
.rid-check { display: inline-flex; align-items: center; gap: 6px; margin: 0 16px 6px 0; min-height: 24px; }
.rid-choice { margin: 8px 0; }
.rid-choice label { margin-right: 6px; }
.rid-hint { margin: 4px 0 0; }

/* FR1 W3-SHARED (M100; audit A8-L10): the LTI tables in admin.html (admin.js: LMS platforms, assignments) use class="data-table",
   which had no rule at all -- unstyled browser tables beside every other list. Same look as table.list. */
table.data-table { width: 100%; border-collapse: collapse; }
table.data-table th, table.data-table td {
  text-align: left; padding: 10px 8px; border-bottom: 1px solid var(--row-border);
  font-size: 0.9rem; vertical-align: top;
}
table.data-table th { opacity: 0.7; font-weight: 600; font-size: 0.78rem; text-transform: uppercase; }

.muted { opacity: var(--muted-opacity); font-size: 0.85rem; overflow-wrap: break-word; }

/* Round 98 audit, F(Low): question media and explanation shown inline in a bank-review list
 * (bank-editor.js's renderQuestionList, platform.js's reviewQuestionHtml) -- same media types as
 * host.html's own full-screen `.question-media` (img/audio/video), just capped much smaller
 * since this sits inside a scrolling list of many questions rather than a single projected one. */
.q-review-media { max-width: 320px; margin: 6px 0; }
.q-review-media img, .q-review-media video { max-width: 100%; max-height: 200px; border-radius: var(--radius); display: block; }
.q-review-media audio { width: 100%; display: block; }
.q-explanation { margin: 4px 0; font-size: 0.85rem; }
.q-explanation .label { opacity: 0.7; font-weight: 600; }
.error-box {
  background: rgba(239, 68, 68, 0.15); border: 1px solid rgba(239, 68, 68, 0.4);
  border-radius: 8px; padding: 10px 14px; margin-bottom: 14px; font-size: 0.88rem;
}
.error-box ul { margin: 6px 0 0; padding-left: 20px; }

/* shared.js's renderBlockedActionHtml() -- the structure it returns, meant to sit inside a page's own .error-box
 * (role="alert"). Each of what/why/who/next is its own line; the last one ends in the action link/button when
 * there is one. Deliberately reuses .error-box's red banner rather than a new color, so a blocked action still
 * reads as the same "your action was refused" family of message as every other error-box on the page. */
.blocked-action p { margin: 0 0 6px; }
.blocked-action p:last-child { margin-bottom: 0; }
.blocked-action .blocked-what { font-weight: 600; }
.success-box {
  background: rgba(34, 197, 94, 0.15); border: 1px solid rgba(34, 197, 94, 0.4);
  border-radius: 8px; padding: 10px 14px; margin-bottom: 14px; font-size: 0.88rem;
}

/* Round 125 (L15): a heads-up that isn't an error and isn't a success -- e.g. bank-editor.html's "this bank is
 * approved, edits send it back for review". Amber tint over the theme's own surface; text stays var(--text). */
.notice-box {
  background: rgba(245, 158, 11, 0.14); border: 1px solid rgba(245, 158, 11, 0.55);
  border-radius: 8px; padding: 10px 14px; margin-bottom: 14px; font-size: 0.88rem;
}

.tabs { display: flex; gap: 4px; margin-bottom: 16px; border-bottom: 1px solid var(--row-border); }
.tab {
  padding: 8px 16px; cursor: pointer; opacity: var(--tab-inactive-opacity); border-bottom: 2px solid transparent;
  font-weight: 600; font-size: 0.9rem;
}
.tab.active { opacity: 1; border-bottom-color: var(--brand); }

/* V2-10: the search/filter bar above a list (dashboard.html's banks and sessions, admin.html's facilitators and
 * org-wide sessions, platform.html's cross-org sessions). Shared here rather than page-scoped since it's one component
 * used identically on three pages. Every field gets a real flex-basis and min-width: 0 (the same fix the pages' own
 * `.row > .field` rules apply) so the bar wraps one-per-line on a phone instead of overflowing the page. */
.filter-bar { display: flex; flex-wrap: wrap; gap: 0 10px; align-items: flex-end; margin-bottom: 6px; }
.filter-bar .field { flex: 1 1 130px; min-width: 0; margin-bottom: 10px; }
.filter-bar .field.wide { flex-basis: 180px; }
.filter-bar label { font-size: 0.78rem; }
.filter-bar .input { padding: 7px 10px; font-size: 0.88rem; }
.filter-bar .filter-actions { margin-bottom: 10px; }

.row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.row-between { display: flex; align-items: center; justify-content: space-between; gap: 10px; flex-wrap: wrap; }
.spacer { flex: 1; }

/* V2-07: bank version diff (renderBankDiffHtml in shared.js), used by bank-editor.html and platform.html. */
.bank-diff { list-style: none; padding: 0; margin: 6px 0; }
.bank-diff li { padding: 6px 0; border-bottom: 1px solid rgba(128, 128, 128, 0.2); overflow-wrap: anywhere; }
.bank-diff del { opacity: 0.7; }
.bank-diff ins { text-decoration: none; font-weight: 600; }
.review-panel textarea { width: 100%; min-height: 64px; }

/* MICRO-1 (layout matrix, batch-2 regressions): the Platform settings card (platform-settings.js renders every `.ps-group`).
   A switch row's label is a 24 px target (WCAG 2.5.8 AA), the help paragraphs are 14 px (the matrix floor for body text), and on a
   coarse pointer its fields and buttons are 44 px. Scoped to .ps-group on purpose: the same classes on host / play are untouched. */
.ps-group .row > input[type="checkbox"] + label { display: inline-flex; align-items: center; min-height: 24px; margin-bottom: 0; }
.ps-group p.muted, .ps-group .field .muted { font-size: 0.875rem; }
@media (pointer: coarse) {
  .ps-group .input, .ps-group select, .ps-group .btn-sm { min-height: 44px; }
}
