/* ==========================================================================
   CineScope — themes

   Seven themes, one stylesheet. Nothing below this block names a colour: every
   rule reads a SEMANTIC token (--bg, --surface, --text, --accent, --danger …),
   and those tokens are derived once, here, from twenty-odd PALETTE SLOTS
   (--p-*). A theme is then just those slots filled in.

   That is what keeps a new theme to one short block instead of an audit of two
   thousand lines, and it is why the slots are named --p-* rather than after any
   one palette: "--ctp-teal: #88C0D0" under Nord would be a lie.

   The slots borrow Catppuccin's shape (crust..text for surfaces, then named
   hues) because every palette here has an equivalent for each, and having one
   fixed shape is what lets the derivations below be written once.

   Applied as <html data-theme="…"> by /theme.js, which is a blocking script in
   every page's <head> so the correct theme is on the element before first paint
   — a themed page that flashes the wrong colours first is worse than one theme.

   The one exception is the printed QR placard, which stays black on white
   whatever the theme, because it goes on paper. See "printed placards".
   ========================================================================== */

:root {
  /* ---------------------------------------------------------------- shared */
  --radius-lg: 16px;
  --radius: 12px;
  --radius-sm: 9px;

  --font:
    ui-sans-serif, -apple-system, BlinkMacSystemFont, 'Segoe UI', Inter, Roboto, Helvetica, Arial,
    sans-serif;
  --mono: ui-monospace, SFMono-Regular, 'SF Mono', 'JetBrains Mono', Menlo, Consolas, monospace;

  --ease: cubic-bezier(0.32, 0.72, 0, 1);

  /*
   * The reticle's pupil. The one colour in this stylesheet that is not a theme
   * slot, because it is not decoration — it is the brand, and a mark that
   * changed colour with the theme would not be one. Every other part of the
   * wordmark takes currentColor and recolours freely around it.
   */
  --brand-pupil: #d9922b;
}

/* ----------------------------------------------------------------- semantic
   Written once in terms of the slots, so a theme sets colours and inherits every
   relationship between them. color-mix rather than more slots: the step from a
   surface to its border is a property of the design, not of the palette, and
   should not be rcircuitd six times.

   Also applied to [data-preview], the theme picker's swatches. A custom property
   is substituted where it is DECLARED, so --bg computed on :root keeps :root's
   crust no matter how a descendant redefines --p-crust — the derivations have to
   be re-declared on the card for its slots to reach them. */
:root,
[data-preview] {
  --bg: var(--p-crust);
  --bg-tint: radial-gradient(1200px 600px at 50% -10%, var(--p-tint) 0%, var(--p-crust) 60%);
  --surface: var(--p-mantle);
  --surface-2: var(--p-base);
  --surface-3: color-mix(in oklab, var(--p-base) 80%, var(--p-surface0));
  --border: color-mix(in oklab, var(--p-base) 58%, var(--p-surface0));
  --border-strong: var(--p-surface1);

  --text: var(--p-text);
  --text-dim: var(--p-subtext0);
  --text-faint: var(--p-overlay1);

  --accent: var(--p-accent);
  --accent-hover: color-mix(in oklab, var(--p-accent) 80%, var(--p-text));
  /* Solid fills use a deepened accent: a palette's accent is usually tuned for
     text on the background, and glares when it becomes the background itself. */
  --accent-fill: var(--p-accent-fill);
  --accent-fill-hover: color-mix(in oklab, var(--p-accent-fill) 86%, var(--p-text));
  --accent-ink: var(--p-accent-ink); /* text on top of an accent fill */
  --accent-soft: color-mix(in oklab, var(--p-accent) 13%, var(--p-crust));
  --accent-line: linear-gradient(90deg, var(--p-accent-fill), var(--p-pink));
  --focus: var(--p-lavender);

  --ok: var(--p-green);
  --ok-soft: color-mix(in oklab, var(--p-green) 13%, var(--p-crust));
  --warn: var(--p-yellow);
  --warn-soft: color-mix(in oklab, var(--p-yellow) 13%, var(--p-crust));
  --danger: var(--p-red);
  --danger-soft: color-mix(in oklab, var(--p-red) 13%, var(--p-crust));
  --info: var(--p-blue);
  --info-soft: color-mix(in oklab, var(--p-blue) 13%, var(--p-crust));

  /* Depth comes from a hairline plus a soft drop, not a hard shadow. Both the
     hairline and the drop are slots because a light theme needs a white
     hairline and a far weaker drop than a dark one. */
  --shadow: 0 1px 0 var(--p-hairline) inset, 0 10px 28px -12px var(--p-shadow);
  --shadow-lift: 0 1px 0 var(--p-hairline) inset, 0 18px 40px -16px var(--p-shadow-strong);
}

/* ------------------------------------------------------------ Catppuccin Mocha
   The original theme, and still the default. The page sits on Crust rather than
   Base so the whole UI reads a shade darker than stock Mocha. */
:root,
:root[data-theme='mocha'],
[data-preview='mocha'] {
  color-scheme: dark;

  --p-crust: #11111b;
  --p-mantle: #181825;
  --p-base: #1e1e2e;
  --p-surface0: #313244;
  --p-surface1: #45475a;
  --p-surface2: #585b70;
  --p-overlay0: #6c7086; /* slot kept for palette completeness */
  --p-overlay1: #7f849c;
  --p-subtext0: #a6adc8;
  --p-subtext1: #bac2de;
  --p-text: #cdd6f4;

  --p-pink: #f5c2e7;
  --p-red: #f38ba8;
  --p-peach: #fab387;
  --p-yellow: #f9e2af;
  --p-green: #a6e3a1;
  --p-teal: #94e2d5;
  --p-blue: #89b4fa;
  --p-lavender: #b4befe;

  --p-accent: #cba6f7;
  --p-accent-fill: #a884d8;
  --p-accent-ink: #14101c;

  --p-tint: #1b1b2b;
  --p-hairline: rgba(205, 214, 244, 0.05);
  --p-shadow: rgba(0, 0, 0, 0.7);
  --p-shadow-strong: rgba(0, 0, 0, 0.8);
}

/* ------------------------------------------------------------------ Dark
   A neutral dark grey with a blue accent, for anyone who wants dark without a
   palette's opinions. Deliberately not a variation on Mocha — offering both
   only earns its place if they look meaningfully different. */
:root[data-theme='dark'],
[data-preview='dark'] {
  color-scheme: dark;

  --p-crust: #0e0e11;
  --p-mantle: #16161a;
  --p-base: #1c1c21;
  --p-surface0: #2a2a31;
  --p-surface1: #3a3a44;
  --p-surface2: #4c4c58;
  --p-overlay0: #6a6a78;
  --p-overlay1: #8a8a99;
  --p-subtext0: #b4b4c0;
  --p-subtext1: #d0d0da;
  --p-text: #e8e8ef;

  --p-pink: #e79ec8;
  --p-red: #f2777a;
  --p-peach: #f0a45d;
  --p-yellow: #e8d07a;
  --p-green: #8fd18a;
  --p-teal: #7fd0c4;
  --p-blue: #8ab4f8;
  --p-lavender: #a9b6f0;

  --p-accent: #8ab4f8;
  --p-accent-fill: #5b8dd9;
  --p-accent-ink: #0b1220;

  --p-tint: #17171d;
  --p-hairline: rgba(255, 255, 255, 0.05);
  --p-shadow: rgba(0, 0, 0, 0.7);
  --p-shadow-strong: rgba(0, 0, 0, 0.82);
}

/* ------------------------------------------------------------------ Light
   A proven, contrast-checked light palette rather than one invented here, with
   one change of its own: the accent is the palette's BLUE, not its mauve, so
   Light is Dark in daylight rather than a second purple theme. Picking Light
   should change the time of day, not the accent colour.

   Two things invert. The page sits on Crust, the DARKEST of the three light
   surfaces, so cards on Mantle read as raised rather than sunken. And a solid
   accent fill needs white text on it rather than near-black. */
:root[data-theme='light'],
[data-preview='light'] {
  color-scheme: light;

  --p-crust: #dce0e8;
  --p-mantle: #e6e9ef;
  --p-base: #eff1f5;
  --p-surface0: #ccd0da;
  --p-surface1: #bcc0cc;
  --p-surface2: #acb0be;
  --p-overlay0: #9ca0b0;
  --p-overlay1: #8c8fa1;
  --p-subtext0: #6c6f85;
  --p-subtext1: #5c5f77;
  --p-text: #4c4f69;

  --p-pink: #ea76cb;
  --p-red: #d20f39;
  --p-peach: #fe640b;
  --p-yellow: #df8e1d;
  --p-green: #40a02b;
  --p-teal: #179299;
  --p-blue: #1e66f5;
  --p-lavender: #7287fd;

  /* Deeper than the palette's own blue (#1e66f5, kept above for --info): that one
     only reaches 4.0:1 as text on a card, and --accent IS text here — links, the
     disclosure toggles, the "set to light right now" line. This clears 5:1, and
     white on it as a solid fill clears 6:1. */
  --p-accent: #1657d8;
  --p-accent-fill: #1657d8;
  --p-accent-ink: #ffffff;

  --p-tint: #eceef4;
  --p-hairline: rgba(255, 255, 255, 0.9);
  --p-shadow: rgba(76, 79, 105, 0.16);
  --p-shadow-strong: rgba(76, 79, 105, 0.22);
}

/* ----------------------------------------------------------------- System
   Not a palette: the device's own light/dark setting, resolved to Light or Dark
   by these two media queries.

   No JavaScript and no request — which means it also follows the device LIVE.
   Switching the OS to dark recolours an open page immediately, where a scripted
   version would need a reload or a matchMedia listener on every page.

   The slot values below are copies of the Light and Dark blocks above, because
   plain CSS has no way to say "be that block" and this project has no build step
   to generate it. test/theme.test.js asserts they stay identical, so the drift
   that would otherwise be invisible is a failing test instead.
   ------------------------------------------------------------------------- */
@media (prefers-color-scheme: light) {
  :root[data-theme='system'],
  [data-preview='system'] {
  color-scheme: light;

  --p-crust: #dce0e8;
  --p-mantle: #e6e9ef;
  --p-base: #eff1f5;
  --p-surface0: #ccd0da;
  --p-surface1: #bcc0cc;
  --p-surface2: #acb0be;
  --p-overlay0: #9ca0b0;
  --p-overlay1: #8c8fa1;
  --p-subtext0: #6c6f85;
  --p-subtext1: #5c5f77;
  --p-text: #4c4f69;

  --p-pink: #ea76cb;
  --p-red: #d20f39;
  --p-peach: #fe640b;
  --p-yellow: #df8e1d;
  --p-green: #40a02b;
  --p-teal: #179299;
  --p-blue: #1e66f5;
  --p-lavender: #7287fd;

  --p-accent: #1657d8;
  --p-accent-fill: #1657d8;
  --p-accent-ink: #ffffff;

  --p-tint: #eceef4;
  --p-hairline: rgba(255, 255, 255, 0.9);
  --p-shadow: rgba(76, 79, 105, 0.16);
  --p-shadow-strong: rgba(76, 79, 105, 0.22);
  }
}

@media (prefers-color-scheme: dark) {
  :root[data-theme='system'],
  [data-preview='system'] {
  color-scheme: dark;

  --p-crust: #0e0e11;
  --p-mantle: #16161a;
  --p-base: #1c1c21;
  --p-surface0: #2a2a31;
  --p-surface1: #3a3a44;
  --p-surface2: #4c4c58;
  --p-overlay0: #6a6a78;
  --p-overlay1: #8a8a99;
  --p-subtext0: #b4b4c0;
  --p-subtext1: #d0d0da;
  --p-text: #e8e8ef;

  --p-pink: #e79ec8;
  --p-red: #f2777a;
  --p-peach: #f0a45d;
  --p-yellow: #e8d07a;
  --p-green: #8fd18a;
  --p-teal: #7fd0c4;
  --p-blue: #8ab4f8;
  --p-lavender: #a9b6f0;

  --p-accent: #8ab4f8;
  --p-accent-fill: #5b8dd9;
  --p-accent-ink: #0b1220;

  --p-tint: #17171d;
  --p-hairline: rgba(255, 255, 255, 0.05);
  --p-shadow: rgba(0, 0, 0, 0.7);
  --p-shadow-strong: rgba(0, 0, 0, 0.82);
  }
}

/* ------------------------------------------------------------------- Nord
   Nord defines four Polar Night greys where this needs eight surface steps, so
   the mid greys are interpolated between nord3 and nord4 rather than invented.
   Frost cyan (nord8) is the accent, as Nord intends. */
:root[data-theme='nord'],
[data-preview='nord'] {
  color-scheme: dark;

  --p-crust: #242933;
  --p-mantle: #2e3440;
  --p-base: #3b4252;
  --p-surface0: #434c5e;
  --p-surface1: #4c566a;
  --p-surface2: #5c667b;
  --p-overlay0: #6d788d;
  --p-overlay1: #8f9aad;
  --p-subtext0: #c2cadb;
  --p-subtext1: #d8dee9;
  --p-text: #eceff4;

  --p-pink: #b48ead;
  --p-red: #bf616a;
  --p-peach: #d08770;
  --p-yellow: #ebcb8b;
  --p-green: #a3be8c;
  --p-teal: #8fbcbb;
  --p-blue: #81a1c1;
  --p-lavender: #88c0d0;

  --p-accent: #88c0d0;
  --p-accent-fill: #5e81ac;
  --p-accent-ink: #eceff4;

  --p-tint: #2b313d;
  --p-hairline: rgba(236, 239, 244, 0.05);
  --p-shadow: rgba(0, 0, 0, 0.6);
  --p-shadow-strong: rgba(0, 0, 0, 0.72);
}

/* ------------------------------------------------------------- Tokyo Night
   The "night" variant. Magenta is the accent; the deep blue-black surfaces are
   what give it its character, so bg_dark is Crust and bg is Mantle. */
:root[data-theme='tokyo-night'],
[data-preview='tokyo-night'] {
  color-scheme: dark;

  --p-crust: #16161e;
  --p-mantle: #1a1b26;
  --p-base: #1f2335;
  --p-surface0: #292e42;
  --p-surface1: #414868;
  --p-surface2: #545c7e;
  --p-overlay0: #565f89;
  --p-overlay1: #737aa2;
  --p-subtext0: #a9b1d6;
  --p-subtext1: #b8c0e0;
  --p-text: #c0caf5;

  --p-pink: #ff75a0;
  --p-red: #f7768e;
  --p-peach: #ff9e64;
  --p-yellow: #e0af68;
  --p-green: #9ece6a;
  --p-teal: #7dcfff;
  --p-blue: #7aa2f7;
  --p-lavender: #7aa2f7;

  --p-accent: #bb9af7;
  --p-accent-fill: #7c5cc4;
  --p-accent-ink: #16161e;

  --p-tint: #1f2030;
  --p-hairline: rgba(192, 202, 245, 0.05);
  --p-shadow: rgba(0, 0, 0, 0.72);
  --p-shadow-strong: rgba(0, 0, 0, 0.84);
}

/* -------------------------------------------------------------- Everforest
   The dark medium variant. Everforest is warm where the others are cool — the
   greens and the tan foreground are the point — and green is its accent. */
:root[data-theme='everforest'],
[data-preview='everforest'] {
  color-scheme: dark;

  --p-crust: #232a2e;
  --p-mantle: #2d353b;
  --p-base: #343f44;
  --p-surface0: #3d484d;
  --p-surface1: #475258;
  --p-surface2: #4f585e;
  --p-overlay0: #7a8478;
  --p-overlay1: #9da9a0;
  --p-subtext0: #b9c0ab;
  --p-subtext1: #c7cbb4;
  --p-text: #d3c6aa;

  --p-pink: #d699b6;
  --p-red: #e67e80;
  --p-peach: #e69875;
  --p-yellow: #dbbc7f;
  --p-green: #a7c080;
  --p-teal: #83c092;
  --p-blue: #7fbbb3;
  --p-lavender: #7fbbb3;

  --p-accent: #a7c080;
  --p-accent-fill: #6f8c58;
  --p-accent-ink: #1e2326;

  --p-tint: #2b3337;
  --p-hairline: rgba(211, 198, 170, 0.05);
  --p-shadow: rgba(0, 0, 0, 0.55);
  --p-shadow-strong: rgba(0, 0, 0, 0.68);
}

/* ------------------------------------------------------------------- base */
*,
*::before,
*::after {
  box-sizing: border-box;
}

/*
 * The browser hides [hidden] with `display: none` at the lowest specificity, so
 * ANY class rule setting display quietly beats it — and 57 rules here do. Every
 * page uses the hidden attribute to show and hide panels, which makes that a trap
 * waiting for whichever component grows a display rule next. One declaration
 * closes it for all of them.
 */
[hidden] {
  display: none !important;
}

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
}

body {
  margin: 0;
  min-height: 100vh;
  font-family: var(--font);
  font-size: 16px;
  line-height: 1.6;
  color: var(--text);
  background-color: var(--bg);
  background-image: var(--bg-tint);
  background-attachment: fixed;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  font-feature-settings: 'cv05', 'ss01';
}

h1,
h2,
h3 {
  line-height: 1.2;
  margin: 0 0 0.4em;
  letter-spacing: -0.02em;
  font-weight: 650;
}

h1 {
  font-size: clamp(1.4rem, 1.2rem + 1vw, 1.85rem);
}
h2 {
  font-size: 1.15rem;
}
h3 {
  font-size: 1rem;
}

p {
  margin: 0 0 1em;
}

a {
  color: var(--p-lavender);
  text-decoration-color: color-mix(in oklab, var(--p-lavender) 40%, transparent);
  text-underline-offset: 2px;
  transition: color 120ms var(--ease);
}

a:hover {
  color: var(--accent);
}

::selection {
  background: color-mix(in oklab, var(--accent) 35%, transparent);
  /* Not a fixed white: on the light theme that is invisible on its own tint. */
  color: var(--text);
}

:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
  border-radius: 4px;
}

/* Slim, unobtrusive scrollbars that match the theme. */
* {
  scrollbar-width: thin;
  scrollbar-color: var(--p-surface1) transparent;
}

*::-webkit-scrollbar {
  width: 10px;
  height: 10px;
}

*::-webkit-scrollbar-thumb {
  background: var(--p-surface1);
  border-radius: 999px;
  border: 2px solid transparent;
  background-clip: content-box;
}

*::-webkit-scrollbar-thumb:hover {
  background: var(--p-surface2);
  background-clip: content-box;
}

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

/* ----------------------------------------------------------------- layout */
.shell {
  max-width: 1180px;
  margin: 0 auto;
  padding: 0 20px 80px;
}

.shell--narrow {
  max-width: 640px;
}

/*
 * Settings: wide enough that ten category pills wrap into two rows rather than
 * five, narrow enough that a line of help text stays readable.
 */
.shell--medium {
  max-width: 880px;
}

.topbar {
  position: sticky;
  top: 0;
  z-index: 20;
  background: color-mix(in oklab, var(--p-mantle) 82%, transparent);
  backdrop-filter: saturate(140%) blur(12px);
  -webkit-backdrop-filter: saturate(140%) blur(12px);
  border-bottom: 1px solid var(--border);
}

.topbar__inner {
  max-width: 1180px;
  margin: 0 auto;
  padding: 12px 20px;
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
}

.brand {
  display: flex;
  align-items: center;
  gap: 10px;
  font-weight: 650;
  font-size: 1rem;
  letter-spacing: -0.02em;
  text-decoration: none;
  color: var(--text);
}

.brand:hover {
  color: var(--text);
}

/* Whatever follows the wordmark — on a General Manager's bar, their cinema. */
.brand__sub {
  color: var(--text-dim);
  font-weight: 550;
  padding-left: 10px;
  border-left: 1px solid var(--border);
}

/* ------------------------------------------------------------- the wordmark
   CINESC⌖PE — the O of SCOPE is a reticle, so the mark IS the name rather than
   a logo sitting beside it. Set in the UI font at two weights: CINE recedes,
   SCOPE holds, and the reticle lands where the eye is already going.

   The reticle is one empty <span>, not seven SVG elements repeated in eight
   pages: with no build step and no templating, markup duplicated per page is
   markup that drifts. The ring and ticks are a masked pseudo-element, so they
   take currentColor and recolour with the theme; the pupil is the other
   pseudo-element, and the one part of the mark that keeps its own colour. */
.wordmark {
  display: inline-flex;
  align-items: baseline;
  /* Sized for the desktop bar, and stepped down under 700px with the rest of the
     top bar — the phone version shares its row with the account button and has no
     room to grow. Everything in the reticle is em-based, so it follows. */
  font-size: 1.14rem;
  letter-spacing: 0.06em;
  line-height: 1.35;
  white-space: nowrap;
  color: var(--text);
}

.wordmark__cine {
  font-weight: 400;
  color: var(--text-dim);
}

.wordmark__scope {
  font-weight: 600;
}

/*
 * Sized in em so it tracks the type it is standing in for. `align-self: center`
 * rather than the baseline the letters sit on: a ring aligned by its bottom edge
 * reads as having fallen off the line.
 *
 * The mask is filled white, which shows through under both mask-mode: alpha and
 * luminance — black would be correct under one and invisible under the other.
 */
.wordmark__reticle {
  --reticle: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Ccircle cx='50' cy='50' r='38' fill='none' stroke='%23fff' stroke-width='12'/%3E%3Cg stroke='%23fff' stroke-width='8.5' stroke-linecap='round'%3E%3Cpath d='M50 6V29'/%3E%3Cpath d='M50 71V94'/%3E%3Cpath d='M6 50H29'/%3E%3Cpath d='M71 50H94'/%3E%3C/g%3E%3C/svg%3E");

  position: relative;
  display: inline-block;
  width: 0.82em;
  height: 0.82em;
  margin: 0 0.05em;
  align-self: center;
  flex: none;
}

.wordmark__reticle::before {
  content: '';
  position: absolute;
  inset: 0;
  background: currentColor;
  -webkit-mask: var(--reticle) center / contain no-repeat;
  mask: var(--reticle) center / contain no-repeat;
}

.wordmark__reticle::after {
  content: '';
  position: absolute;
  inset: 40%;
  border-radius: 50%;
  background: var(--brand-pupil);
}

/* ------------------------------------------------------------ main nav
   Exactly one link is ever highlighted: the page you are on. Home used to carry
   a permanent accent chip so that "how do I get back" needed no guess, but it
   read as "you are here" from every other page — two links looking picked out
   at once, one of them lying. Its icon is enough to make it findable. */
.nav {
  display: flex;
  align-items: center;
  gap: 4px;
  flex-wrap: wrap;
  padding-left: 6px;
  margin-left: 6px;
  border-left: 1px solid var(--border);
}

/* On desktop this is just a row inside a row, so the bar looks the same as if the
   links were direct children. It earns its keep on a phone — see the mobile block. */
.nav__rest {
  display: flex;
  align-items: center;
  gap: 4px;
}

.nav__link {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 7px 12px;
  border-radius: 8px;
  border: 1px solid transparent;
  font-size: 0.87rem;
  font-weight: 550;
  color: var(--text-dim);
  text-decoration: none;
  white-space: nowrap;
  transition:
    background-color 140ms var(--ease),
    color 140ms var(--ease),
    border-color 140ms var(--ease);
}

.nav__link:hover {
  background: var(--surface-3);
  color: var(--text);
}

.nav__icon {
  flex: none;
  display: block;
}

/*
 * The page you are on, and nothing else.
 *
 * The accent rather than a grey chip, now that it is the only mark in the bar:
 * "you are here" is worth being unmistakable, and there is no longer a second
 * accented link for it to be confused with. Not a link offering to take you
 * anywhere either, so hover does not lift.
 */
.nav__link--current {
  color: var(--accent);
  background: color-mix(in oklab, var(--accent) 12%, transparent);
  border-color: color-mix(in oklab, var(--accent) 30%, transparent);
  font-weight: 620;
  cursor: default;
}

.nav__link--current:hover {
  background: color-mix(in oklab, var(--accent) 12%, transparent);
  color: var(--accent);
}

.topbar__spacer {
  flex: 1 1 auto;
}

.topbar__who {
  font-size: 0.82rem;
  color: var(--text-dim);
  line-height: 1.35;
}

.topbar__who strong {
  display: block;
  color: var(--text);
  font-weight: 600;
}

/* --------------------------------------------------------- account menu
   Who you are, and Sign out. A small trigger in the sticky bar instead of a
   button, so scrolling on a phone does not drag a full-width Sign out along
   the top of the screen. */
.account {
  position: relative;
  flex: 0 0 auto;
}

.account__trigger {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 3px 8px 3px 3px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--surface-2);
  color: var(--text);
  cursor: pointer;
  transition:
    background-color 140ms var(--ease),
    border-color 140ms var(--ease);
}

.account__trigger:hover {
  background: var(--surface-3);
  border-color: var(--border-strong);
}

.account__avatar {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: var(--accent-line);
  color: var(--accent-ink);
  display: grid;
  place-items: center;
  font-size: 11px;
  font-weight: 750;
  letter-spacing: -0.02em;
  flex: none;
}

.account__chevron {
  color: var(--text-faint);
  flex: none;
  transition: transform 160ms var(--ease);
}

.account__trigger[aria-expanded='true'] .account__chevron {
  transform: rotate(180deg);
}

.account__menu {
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  min-width: 235px;
  padding: 6px;
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  box-shadow: var(--shadow-lift);
  /* Above the page, but inside the topbar's own stacking context. */
  z-index: 30;
}

.account__menu .topbar__who {
  padding: 8px 10px 10px;
  border-bottom: 1px solid var(--border);
  margin-bottom: 6px;
}

/* A navigation item in the account menu, above Sign out. */
.account__item {
  display: block;
  padding: 9px 10px;
  border-radius: var(--radius-sm);
  color: var(--text);
  font-size: 0.88rem;
  font-weight: 600;
  text-decoration: none;
  transition: background-color 120ms var(--ease);
}

.account__item:hover {
  background: var(--surface-3);
  text-decoration: none;
}

.account__item--current {
  background: var(--surface-3);
  color: var(--accent);
}

/* Divides the destinations above from the one action below. */
.account__item + .account__signout {
  margin-top: 4px;
  padding-top: 12px;
  border-top: 1px solid var(--border);
  border-radius: 0 0 var(--radius-sm) var(--radius-sm);
}

.account__signout {
  display: block;
  width: 100%;
  padding: 9px 10px;
  border: 0;
  border-radius: var(--radius-sm);
  background: none;
  color: var(--danger);
  font: inherit;
  font-size: 0.88rem;
  font-weight: 600;
  text-align: left;
  cursor: pointer;
  transition: background-color 120ms var(--ease);
}

.account__signout:hover {
  background: var(--danger-soft);
}

.page-head {
  padding: 32px 0 20px;
}

.page-head p {
  color: var(--text-dim);
  margin: 0;
  max-width: 64ch;
}

/*
 * "Hide page descriptions", from Settings.
 *
 * One rule for every page, because every page's blurb is the paragraph under the
 * heading inside .page-head — so this cannot go stale as pages are added, and a new
 * page gets the behaviour without knowing about it.
 *
 * The attribute is written by /theme.js before first paint (see src/app.js). Applied
 * on load instead, the paragraph would render and then vanish, which is a page that
 * moves under you as you start reading it.
 *
 * The heading keeps its own bottom margin, so what is left is the title with the
 * page's own spacing rather than a heading jammed against the content.
 */
:root[data-help='off'] .page-head p {
  display: none;
}

/* ------------------------------------------------------------------ cards */
.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow);
  padding: 22px;
}

.card + .card {
  margin-top: 16px;
}

.card > h2:first-child {
  margin-bottom: 16px;
}

/* ------------------------------------------------------------------ forms */
.field {
  margin-bottom: 18px;
}

.field > label,
.field__legend {
  display: block;
  font-weight: 600;
  font-size: 0.9rem;
  margin-bottom: 7px;
  color: var(--p-subtext1);
}

.field__hint {
  font-size: 0.82rem;
  color: var(--text-dim);
  margin: 6px 0 0;
}

/*
 * A yes/no field inside an inline edit row. The label keeps the same position as
 * every text field's so the row's baselines line up, and the box sits under it in
 * place of an input rather than beside the text.
 */
.field--check > label {
  margin-bottom: 7px;
}

/* Matches an input's height so the box centres against the fields beside it. */
.field--check__box {
  display: flex;
  align-items: center;
  min-height: 42px;
}

.field--check input[type='checkbox'] {
  width: 18px;
  height: 18px;
  margin: 0;
  accent-color: var(--accent);
  cursor: pointer;
}

.field__optional {
  font-weight: 400;
  color: var(--text-faint);
}

input[type='text'],
input[type='email'],
input[type='tel'],
input[type='password'],
input[type='search'],
input[type='number'],
select,
textarea {
  width: 100%;
  font: inherit;
  font-size: 1rem;
  color: var(--text);
  background: var(--surface-2);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  padding: 11px 13px;
  min-height: 44px;
  transition:
    border-color 120ms var(--ease),
    box-shadow 120ms var(--ease),
    background-color 120ms var(--ease);
}

input::placeholder,
textarea::placeholder {
  color: var(--text-faint);
}

input:hover:not(:disabled),
select:hover:not(:disabled),
textarea:hover:not(:disabled) {
  border-color: var(--p-surface2);
}

input:focus,
select:focus,
textarea:focus {
  outline: none;
  background: var(--surface-3);
  border-color: var(--accent);
  box-shadow: 0 0 0 3px color-mix(in oklab, var(--accent) 22%, transparent);
}

textarea {
  resize: vertical;
  min-height: 148px;
  line-height: 1.55;
}

select {
  appearance: none;
  background-image: linear-gradient(45deg, transparent 50%, var(--text-dim) 50%),
    linear-gradient(135deg, var(--text-dim) 50%, transparent 50%);
  background-position: right 18px center, right 13px center;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
  padding-right: 36px;
  cursor: pointer;
}

input[aria-invalid='true'],
textarea[aria-invalid='true'],
select[aria-invalid='true'] {
  border-color: var(--danger);
  box-shadow: 0 0 0 3px color-mix(in oklab, var(--danger) 18%, transparent);
}

.counter {
  font-size: 0.78rem;
  color: var(--text-faint);
  text-align: right;
  margin-top: 6px;
  font-variant-numeric: tabular-nums;
}

/* Says what the form covers while only one category is rolled out, standing in
   for the picker rather than leaving an unexplained gap. */
.scope-note {
  margin: 0 0 20px;
  padding: 12px 14px;
  border-radius: var(--radius-sm);
  border: 1px solid color-mix(in oklab, var(--accent) 30%, transparent);
  background: var(--accent-soft);
  color: var(--p-subtext1);
  font-size: 0.9rem;
}

.scope-note[hidden] {
  display: none;
}

/* Radio pill group — big tap targets for a phone held in one hand. */
.pills {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  border: 0;
  padding: 0;
  margin: 0 0 22px;
}

.pill {
  position: relative;
}

.pill input {
  position: absolute;
  inset: 0;
  opacity: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  cursor: pointer;
}

.pill span {
  display: block;
  padding: 10px 15px;
  border: 1px solid var(--border-strong);
  border-radius: 999px;
  font-size: 0.92rem;
  background: var(--surface-2);
  color: var(--p-subtext1);
  user-select: none;
  transition:
    background-color 140ms var(--ease),
    border-color 140ms var(--ease),
    color 140ms var(--ease),
    transform 140ms var(--ease);
}

.pill:hover span {
  border-color: var(--p-surface2);
  color: var(--text);
}

.pill input:checked + span {
  background: var(--accent-fill);
  border-color: var(--accent-fill);
  color: var(--accent-ink);
  font-weight: 650;
}

.pill input:active + span {
  transform: scale(0.97);
}

.pill input:focus-visible + span {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}

/* ---------------------------------------------------------------- buttons
   Three weights, so a screen full of actions does not shout:

     .btn            tonal — an accent-tinted fill with accent text. The default
                     primary. Reads clearly as the main action in a row without
                     a saturated block of colour, which matters on the review
                     queue where every complaint carries one.
     .btn--cta       solid fill. Reserved for the single main action on a page
                     (sign in, send my report), where boldness is the point.
     .btn--secondary neutral. Supporting actions.
     .btn--danger    tonal red. Destructive.

   Tonal and danger share a weight on purpose: in a list, "validate" and
   "reject" are peers, and neither should dominate the row.
   -------------------------------------------------------------------------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  font: inherit;
  font-size: 0.94rem;
  font-weight: 620;
  letter-spacing: -0.01em;
  min-height: 44px;
  padding: 11px 18px;
  border-radius: var(--radius-sm);
  background: color-mix(in oklab, var(--accent) 15%, var(--surface));
  color: var(--accent);
  border: 1px solid color-mix(in oklab, var(--accent) 34%, transparent);
  cursor: pointer;
  text-decoration: none;
  white-space: nowrap;
  transition:
    background-color 140ms var(--ease),
    border-color 140ms var(--ease),
    color 140ms var(--ease),
    transform 100ms var(--ease),
    opacity 140ms var(--ease);
}

.btn:hover:not(:disabled) {
  background: color-mix(in oklab, var(--accent) 26%, var(--surface));
  border-color: color-mix(in oklab, var(--accent) 55%, transparent);
  color: var(--accent-hover);
}

.btn:active:not(:disabled) {
  transform: translateY(1px);
}

.btn:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

/* The one bold action on a page. Filled, but deepened off the raw palette
   value so it is confident rather than glaring. */
.btn--cta {
  background: var(--accent-fill);
  color: var(--accent-ink);
  border-color: transparent;
}

.btn--cta:hover:not(:disabled) {
  background: var(--accent-fill-hover);
  border-color: transparent;
  color: var(--accent-ink);
}

.btn--block {
  width: 100%;
}

.btn--secondary {
  background: var(--surface-2);
  color: var(--p-subtext1);
  border-color: var(--border-strong);
}

.btn--secondary:hover:not(:disabled) {
  background: var(--surface-3);
  border-color: var(--p-surface2);
  color: var(--text);
  box-shadow: none;
}

.btn--danger {
  background: transparent;
  color: var(--danger);
  border-color: color-mix(in oklab, var(--danger) 45%, transparent);
}

.btn--danger:hover:not(:disabled) {
  background: var(--danger-soft);
  border-color: var(--danger);
  box-shadow: none;
}

.btn--sm {
  min-height: 34px;
  padding: 6px 12px;
  font-size: 0.85rem;
  border-radius: 8px;
}

.btn--link {
  background: none;
  border: 0;
  color: var(--p-lavender);
  min-height: 0;
  padding: 0;
  font-weight: 500;
  font-size: 0.87rem;
  text-decoration: underline;
  text-underline-offset: 2px;
}

.btn--link:hover {
  color: var(--accent);
  background: none;
  box-shadow: none;
}

/* ----------------------------------------------------------------- badges */
.badge {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 0.72rem;
  font-weight: 650;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  padding: 3px 9px;
  border-radius: 7px;
  border: 1px solid transparent;
  white-space: nowrap;
}

.badge--pending_review,
.badge--open {
  background: var(--info-soft);
  color: var(--info);
  border-color: color-mix(in oklab, var(--info) 35%, transparent);
}

.badge--validated,
.badge--in_progress {
  background: var(--warn-soft);
  color: var(--warn);
  border-color: color-mix(in oklab, var(--warn) 35%, transparent);
}

.badge--released,
.badge--resolved {
  background: var(--ok-soft);
  color: var(--ok);
  border-color: color-mix(in oklab, var(--ok) 35%, transparent);
}

.badge--rejected {
  background: var(--surface-3);
  color: var(--text-faint);
  border-color: var(--border-strong);
}

.badge--high {
  background: var(--danger-soft);
  color: var(--danger);
  border-color: color-mix(in oklab, var(--danger) 40%, transparent);
}

.badge--medium {
  background: color-mix(in oklab, var(--p-peach) 12%, var(--surface));
  color: var(--p-peach);
  border-color: color-mix(in oklab, var(--p-peach) 32%, transparent);
}

.badge--low {
  background: var(--surface-3);
  color: var(--text-dim);
  border-color: var(--border-strong);
}

/*
 * "Set here" / "From the environment" — sentences, so they keep their own casing.
 * Every other badge is a one-word status that reads well shouted; these do not.
 */
.badge--source {
  background: var(--surface-3);
  color: var(--text-dim);
  border-color: var(--border-strong);
  text-transform: none;
  letter-spacing: 0;
  font-weight: 600;
}

.badge--source-saved {
  background: color-mix(in oklab, var(--p-teal) 10%, var(--surface));
  color: var(--p-teal);
  border-color: color-mix(in oklab, var(--p-teal) 28%, transparent);
}

.badge--cat {
  background: color-mix(in oklab, var(--p-teal) 10%, var(--surface));
  color: var(--p-teal);
  border-color: color-mix(in oklab, var(--p-teal) 28%, transparent);
  text-transform: capitalize;
  letter-spacing: 0;
  font-weight: 600;
}

/*
 * 3D capability, next to the Flat/Scope badge. A distinct colour because it
 * answers a different question — masking versus what kit is in the booth — and
 * two teal badges side by side would read as one fact split in half.
 *
 * Only rendered for a screen that has it, so its absence is the "not 3D" state
 * and there is nothing to hover on a screen that is not.
 */
.badge--3d {
  background: color-mix(in oklab, var(--p-pink) 12%, var(--surface));
  color: var(--p-pink);
  border-color: color-mix(in oklab, var(--p-pink) 32%, transparent);
  letter-spacing: 0.04em;
  font-weight: 700;
  /* It carries a tooltip and nothing else, so the cursor should not promise a click. */
  cursor: help;
}

/* ---------------------------------------------------------------- notices */
.notice {
  border: 1px solid var(--border-strong);
  border-left: 3px solid var(--border-strong);
  border-radius: var(--radius-sm);
  padding: 13px 15px;
  margin-bottom: 18px;
  font-size: 0.92rem;
}

.notice--error {
  background: var(--danger-soft);
  border-color: color-mix(in oklab, var(--danger) 35%, transparent);
  border-left-color: var(--danger);
  color: var(--text);
}

.notice--ok {
  background: var(--ok-soft);
  border-color: color-mix(in oklab, var(--ok) 35%, transparent);
  border-left-color: var(--ok);
}

.notice--info {
  background: var(--info-soft);
  border-color: color-mix(in oklab, var(--info) 35%, transparent);
  border-left-color: var(--info);
}

.notice[hidden] {
  display: none;
}

/* ------------------------------------------------------------ stat strip */
.stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 12px;
  margin-bottom: 24px;
}

.stat {
  position: relative;
  overflow: hidden;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 14px 16px;
  transition: border-color 140ms var(--ease);
}

/* A hairline of accent along the top edge, so the strip reads as one unit. */
.stat::before {
  content: '';
  position: absolute;
  inset: 0 0 auto 0;
  height: 2px;
  background: var(--accent-line);
  opacity: 0.5;
}

.stat:hover {
  border-color: var(--border-strong);
}

/*
 * Proportional figures, not tabular. Equal-width digits are for columns that have
 * to line up vertically; at this size they make a number like 121 read loose,
 * because every digit takes a zero's width.
 */
.stat__value {
  font-size: 1.75rem;
  font-weight: 680;
  line-height: 1.1;
  letter-spacing: -0.03em;
}

.stat__label {
  font-size: 0.72rem;
  color: var(--text-dim);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-weight: 600;
  margin-top: 4px;
}

/* The one line of context a number needs — what it is measured against. */
.stat__note {
  font-size: 0.78rem;
  color: var(--text-dim);
  margin-top: 6px;
}

.stat__note--warn {
  color: var(--warn);
  font-weight: 600;
}

/* ============================================================ home page
   Charts are hand-rolled SVG and HTML rather than a library, so their chrome
   lives here with everything else. Every mark reads a semantic token, which is
   what lets one chart look right in seven themes without any of them knowing.

   The rules the marks follow, in one place so they are not re-decided per chart:
   thin marks, hairline solid grid one step off the surface, a 2px surface GAP
   between touching fills rather than a stroke around them, and text in text
   tokens — never in the data colour. */

/* -------------------------------------------------------------- range bar */
.rangebar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 10px 14px;
  margin-bottom: 20px;
}

.rangebar__options {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
}

.rangebar__option {
  border: 1px solid transparent;
  background: none;
  border-radius: 8px;
  padding: 6px 11px;
  font: inherit;
  font-size: 0.84rem;
  font-weight: 550;
  color: var(--text-dim);
  cursor: pointer;
  white-space: nowrap;
  transition:
    background-color 140ms var(--ease),
    color 140ms var(--ease);
}

.rangebar__option:hover {
  background: var(--surface-3);
  color: var(--text);
}

.rangebar__option--on {
  color: var(--accent);
  background: color-mix(in oklab, var(--accent) 12%, transparent);
  border-color: color-mix(in oklab, var(--accent) 30%, transparent);
  font-weight: 620;
}

/*
 * The buttons sit right, however many of them are showing. Pushed by the options
 * rather than pulled by the buttons: Reset layout appears and disappears, and two
 * elements each claiming `margin-left: auto` split the free space between them
 * instead of both going to the end.
 */
.rangebar__options {
  margin-right: auto;
}

/* ------------------------------------------------------- needs attention
   A .section disclosure like the Settings cards, but built open: this one is the
   page telling you something is wrong, and a warning you have to click to read is
   not a warning. Its rows are full-bleed and tinted, so they sit directly in the
   details rather than in a padded .section__body. */
.attention {
  margin-bottom: 24px;
}

.attention__row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px;
  /* 22px to line up with the .section__body padding used elsewhere. */
  padding: 13px 22px;
  border-left: 3px solid transparent;
}

.attention__row + .attention__row {
  border-top: 1px solid var(--border);
}

.attention__row--danger {
  border-left-color: var(--danger);
  background: var(--danger-soft);
}

.attention__row--warn {
  border-left-color: var(--warn);
  background: var(--warn-soft);
}

.attention__text {
  flex: 1 1 320px;
  font-size: 0.9rem;
}

/* ----------------------------------------------------------- chart layout */
.charts {
  display: grid;
  /* Wide enough that a 12-month trend's labels do not collide; anything narrower
     goes full width rather than being squeezed into a column. */
  /* min() so the track can go narrower than 340px when that is all there is: a
     flat 340px minimum overflowed a 320px phone by 36px and scrolled the page
     sideways. */
  grid-template-columns: repeat(auto-fit, minmax(min(340px, 100%), 1fr));
  gap: 16px;
  margin-bottom: 24px;
  /* Each card is as tall as its own contents. Stretched to match the tallest in the
     row, a three-band impact chart became a card that was two-thirds empty. */
  align-items: start;
}

/* The trend is the page's one time series and reads across the full width. */
.chart--wide {
  grid-column: 1 / -1;
}

.chart {
  position: relative;
  margin: 0;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow);
  padding: 18px 20px 16px;
}

.chart__head {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  margin-bottom: 16px;
}

.chart__heading {
  min-width: 0;
  flex: 1 1 auto;
}

/* ----------------------------------------------------------- the drag grip
   Faint until the card is hovered or the grip itself is focused: five grips at
   full strength turn a page of charts into a page of controls. It stays reachable
   by keyboard the whole time — only its opacity changes, never its display. */
.chart__grip {
  flex: none;
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  margin: -4px -6px 0 0;
  padding: 0;
  border: 1px solid transparent;
  border-radius: 7px;
  background: none;
  color: var(--text-faint);
  cursor: grab;
  opacity: 0.45;
  /* Stops a drag on a touchscreen from scrolling the page instead. */
  touch-action: none;
  transition:
    opacity 140ms var(--ease),
    background-color 140ms var(--ease),
    color 140ms var(--ease);
}

.chart:hover .chart__grip,
.chart__grip:focus-visible {
  opacity: 1;
}

.chart__grip:hover {
  background: var(--surface-3);
  border-color: var(--border);
  color: var(--text);
}

.chart__grip:active {
  cursor: grabbing;
}

/*
 * The card being dragged.
 *
 * It stays in the flow and hops between slots rather than being carried under the
 * cursor — see reorder.js. Pointer-transparent so elementFromPoint finds the card
 * UNDERNEATH it, which is the whole mechanism for working out where to drop.
 */
.chart--dragging {
  pointer-events: none;
  opacity: 0.4;
  border-style: dashed;
  border-color: var(--accent);
  box-shadow: none;
}

/* While a drag is live, nothing else on the page should look clickable or
   selectable — a half-selected chart title mid-drag reads as a bug. */
body.is-dragging {
  user-select: none;
  cursor: grabbing;
}

body.is-dragging .chart__tip {
  display: none;
}

.chart__title {
  margin: 0;
  font-size: 0.98rem;
}

.chart__sub {
  margin: 3px 0 0;
  font-size: 0.82rem;
  color: var(--text-dim);
}

.chart__empty {
  margin: 0;
  padding: 22px 0;
  text-align: center;
  font-size: 0.86rem;
  color: var(--text-faint);
}

/*
 * The plot is sized to include its own x-axis band. A container height that fits
 * only the marks gives the card a tiny nested scrollbar for its axis labels.
 */
/*
 * Height comes from the SVG, which is drawn at the container's real pixel size —
 * see columns() in charts.js. Fixing a height here and scaling into it is what
 * squashed the axis labels on a phone.
 */
.chart__plot--cols .plot {
  display: block;
  max-width: 100%;
}

.plot__grid {
  stroke: var(--border);
  stroke-width: 1;
  /* Solid, never dashed: dashing reads as a threshold or a projection when it is
     only a grid. */
  shape-rendering: crispEdges;
}

.plot__tick,
.plot__label {
  fill: var(--text-faint);
  font-size: 10px;
  font-family: var(--font);
  font-variant-numeric: tabular-nums;
}

.plot__col {
  fill: var(--accent-fill);
  transition: fill 140ms var(--ease);
}

/* The period still in progress. Lighter, so it is visibly not comparable to the
   completed ones beside it — the same hue, so it is still the same series. */
.plot__col--partial {
  fill: color-mix(in oklab, var(--accent-fill) 42%, var(--surface));
}

/* The band is the hit target, not the column: readers aim at a date. */
.plot__hit {
  fill: transparent;
  cursor: default;
}

.plot__hit:focus {
  outline: none;
}

/* The hovered column lifts — the mark responds, rather than every other one dimming. */
.plot__band:hover .plot__col,
.plot__band:focus-within .plot__col {
  fill: var(--accent);
}

/* Keyboard focus still needs a visible ring; the fill change alone is not enough. */
.plot__band:focus-within .plot__hit {
  stroke: var(--focus);
  stroke-width: 2;
}

/* ------------------------------------------------------- horizontal bars */
.bars {
  display: grid;
  gap: 3px;
}

.bars__row {
  display: grid;
  grid-template-columns: minmax(84px, 30%) 1fr auto;
  align-items: center;
  gap: 12px;
  /* Padding, so the hit and focus area is comfortably larger than the 10px bar. */
  padding: 5px 6px;
  margin: 0 -6px;
  border-radius: 7px;
  transition: background-color 120ms var(--ease);
}

.bars__row:hover,
.bars__row:focus-visible {
  background: var(--surface-3);
}

.bars__label {
  font-size: 0.85rem;
  color: var(--text-dim);
  overflow-wrap: anywhere;
}

.bars__track {
  height: 10px;
  border-radius: 5px;
  background: color-mix(in oklab, var(--p-surface0) 60%, transparent);
  overflow: hidden;
}

.bars__fill {
  height: 100%;
  /* Rounded at the data end, square at the baseline, so the bar stays anchored. */
  border-radius: 0 4px 4px 0;
  background: var(--accent-fill);
  transition: width 260ms var(--ease);
}

/*
 * The folded tail, as a line of text rather than a bar. Drawn as one it set the
 * scale — eight cinemas add up to more than the busiest single one — and squashed
 * every real bar against it.
 */
.bars__more {
  display: flex;
  align-items: baseline;
  gap: 12px;
  margin: 8px 0 0;
  padding-top: 9px;
  border-top: 1px solid var(--border);
  font-size: 0.82rem;
  color: var(--text-faint);
}

.bars__more .bars__value {
  margin-left: auto;
  color: var(--text-dim);
}

.bars__value {
  font-size: 0.85rem;
  font-weight: 620;
  font-variant-numeric: tabular-nums;
  min-width: 2.5ch;
  text-align: right;
}

/* --------------------------------------------------------- stacked strip */
.stack {
  display: flex;
  height: 14px;
  border-radius: 7px;
  overflow: hidden;
  /* The gap between segments is the surface showing through, not a stroke around
     each one — a border would add data-weight ink that is not data. */
  gap: 2px;
  background: var(--surface);
}

.stack__part {
  min-width: 3px;
  transition: filter 140ms var(--ease);
}

.stack__part:hover,
.stack__part:focus-visible {
  filter: brightness(1.15);
  outline: none;
}

.stack__part--danger {
  background: var(--danger);
}
.stack__part--warn {
  background: var(--warn);
}
.stack__part--ok {
  background: var(--ok);
}
.stack__part--accent {
  background: var(--accent-fill);
}
.stack__part--accent-soft {
  background: color-mix(in oklab, var(--accent-fill) 42%, var(--surface));
}

/* Always present, so identity never rests on colour alone. */
.stack__legend {
  list-style: none;
  margin: 14px 0 0;
  padding: 0;
  display: grid;
  gap: 7px;
}

.stack__key {
  display: flex;
  align-items: center;
  gap: 9px;
  font-size: 0.85rem;
}

.stack__swatch {
  width: 10px;
  height: 10px;
  border-radius: 3px;
  flex: none;
}

.stack__swatch--danger {
  background: var(--danger);
}
.stack__swatch--warn {
  background: var(--warn);
}
.stack__swatch--ok {
  background: var(--ok);
}
.stack__swatch--accent {
  background: var(--accent-fill);
}
.stack__swatch--accent-soft {
  background: color-mix(in oklab, var(--accent-fill) 42%, var(--surface));
}

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

.stack__key-value {
  margin-left: auto;
  font-weight: 620;
  font-variant-numeric: tabular-nums;
}

/* --------------------------------------------------------------- tooltip */
/*
 * Enhances, never gates: everything it says is also in the table twin below the
 * chart, so a reader who cannot hover loses nothing.
 */
.chart__tip {
  position: absolute;
  z-index: 5;
  pointer-events: none;
  background: var(--surface-3);
  border: 1px solid var(--border-strong);
  border-radius: 8px;
  box-shadow: var(--shadow-lift);
  padding: 6px 10px;
  display: flex;
  flex-direction: column;
  gap: 1px;
  white-space: nowrap;
}

/* The value leads — the reader already knows which mark they are pointing at. */
.chart__tip-value {
  font-size: 0.88rem;
  font-weight: 650;
}

.chart__tip-label {
  font-size: 0.76rem;
  color: var(--text-dim);
}

/* ------------------------------------------------------- the table twin */
.chart__data {
  margin-top: 16px;
  border-top: 1px solid var(--border);
  padding-top: 10px;
}

.chart__data > summary {
  list-style: none;
  cursor: pointer;
  font-size: 0.8rem;
  color: var(--text-dim);
  padding: 2px 0;
}

.chart__data > summary::-webkit-details-marker {
  display: none;
}

.chart__data > summary:hover {
  color: var(--text);
}

.chart__scroll {
  /* Wide content scrolls inside its own box; the page never scrolls sideways. */
  overflow-x: auto;
  margin-top: 8px;
  max-height: 260px;
  overflow-y: auto;
}

.table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.82rem;
}

.table th,
.table td {
  text-align: left;
  padding: 6px 10px 6px 0;
  border-bottom: 1px solid var(--border);
  font-weight: 500;
}

.table thead th {
  position: sticky;
  top: 0;
  background: var(--surface);
  color: var(--text-dim);
  font-size: 0.74rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  font-weight: 650;
}

.table tbody th {
  color: var(--text-dim);
  font-weight: 500;
}

.table td {
  font-variant-numeric: tabular-nums;
  font-weight: 620;
}

/* Refetch holds the previous render rather than flashing a skeleton — no layout
   jump, and the numbers you were reading stay put while the new ones arrive. */
body.is-loading .stats,
body.is-loading .charts,
body.is-loading #attention {
  opacity: 0.55;
  transition: opacity 120ms var(--ease);
}

/* --------------------------------------------------------- filter strip */
.toolbar {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  align-items: flex-end;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 16px;
  margin-bottom: 24px;
}

.toolbar .field {
  margin: 0;
  flex: 1 1 190px;
  min-width: 0;
}

/* Long option labels ("Validated, not yet sent") clip rather than overflow, and
   read as deliberately truncated instead of cut off. */
.toolbar select {
  text-overflow: ellipsis;
}

.toolbar .field > label {
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-faint);
  font-weight: 600;
}

/* ------------------------------------------------------------ account rows
   One row per staff account on Active users. Same card shell as a cinema, so
   the page reads as part of the same app rather than a bolted-on table. */
.person {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 20px;
  border-bottom: 1px solid var(--border);
  flex-wrap: wrap;
}

.person:last-child {
  border-bottom: 0;
}

/* A suspended account is dimmed rather than hidden: it still holds history, and
   an admin looking for "why can this person not sign in" needs to find it. */
.person--suspended {
  background: color-mix(in oklab, var(--danger) 4%, transparent);
}

.person--suspended .person__avatar {
  background: var(--p-surface1);
  color: var(--text-dim);
}

.person--suspended .person__identity {
  opacity: 0.72;
}

.person__avatar {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: var(--accent-line);
  color: var(--accent-ink);
  display: grid;
  place-items: center;
  font-size: 12px;
  font-weight: 750;
  letter-spacing: -0.02em;
  flex: none;
}

.person__identity {
  flex: 1 1 220px;
  min-width: 0;
}

.person__name {
  display: flex;
  align-items: center;
  gap: 8px;
  font-weight: 620;
  font-size: 0.95rem;
}

.person__email {
  font-family: var(--mono);
  font-size: 0.78rem;
  color: var(--text-dim);
  text-decoration: none;
  overflow-wrap: anywhere;
}

.person__email:hover {
  color: var(--p-lavender);
}

/* The Edit button on an account row, and the panel it opens. */
.person__actions {
  flex: 0 0 auto;
}

.person--editing {
  display: block;
  background: var(--surface-2);
}

.person--editing .toolbar--bare {
  align-items: flex-end;
}

.person--editing .field {
  margin: 0;
  flex: 1 1 200px;
  max-width: 320px;
  min-width: 0;
}

.person--editing .field > label {
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-faint);
  font-weight: 600;
}

/* "also at Aviator 10" — one manager, several buildings. */
.manager__also {
  font-size: 0.78rem;
  color: var(--text-faint);
  font-style: italic;
}

.person__scope {
  flex: 0 1 190px;
  font-size: 0.83rem;
  color: var(--text-dim);
}

.person__state {
  display: flex;
  align-items: center;
  gap: 6px;
  flex: 0 0 auto;
}

.person__seen {
  flex: 0 0 130px;
  text-align: right;
  font-size: 0.8rem;
  color: var(--text-faint);
  font-variant-numeric: tabular-nums;
}

/* Distinct from "Active": one is whether the account works, the other is
   whether they are actually holding a session right now. */
.badge--live {
  background: color-mix(in oklab, var(--info) 18%, transparent);
  border-color: color-mix(in oklab, var(--info) 45%, transparent);
  color: var(--info);
}

.person__never {
  color: var(--warn);
}

/* ------------------------------------------------------- collapsible filters
   The review-queue filters, folded into a single bar. The <summary> is the whole
   bar rather than just a label, so it can carry the state of the filters and the
   Refresh button while the controls themselves are put away. */
.filters {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  margin-bottom: 24px;
}

.filters__summary {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 16px 10px 14px;
  cursor: pointer;
  user-select: none;
  list-style: none;
  border-radius: var(--radius);
  transition: background-color 140ms var(--ease);
}

.filters__summary::-webkit-details-marker {
  display: none;
}

.filters__summary:hover {
  background: var(--surface-3);
}

.filters__summary:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: -3px;
}

.filters[open] > .filters__summary {
  border-bottom: 1px solid var(--border);
  border-radius: var(--radius) var(--radius) 0 0;
}

.filters__icon {
  color: var(--text-faint);
  flex: 0 0 auto;
  transition: color 140ms var(--ease), transform 160ms var(--ease);
}

.filters__summary:hover .filters__icon,
.filters[open] > .filters__summary .filters__icon {
  color: var(--accent);
}

.filters__label {
  font-weight: 640;
  font-size: 0.9rem;
  flex: 0 0 auto;
}

/* How many controls are narrowing the queue right now. */
.filters__count {
  flex: 0 0 auto;
  min-width: 18px;
  padding: 1px 6px;
  border-radius: 999px;
  background: color-mix(in oklab, var(--accent) 24%, transparent);
  border: 1px solid color-mix(in oklab, var(--accent) 45%, transparent);
  color: var(--text);
  font-size: 0.72rem;
  font-weight: 700;
  text-align: center;
  font-variant-numeric: tabular-nums;
}

/* Reads out what is filtering the queue while the panel is shut. Truncates
   rather than wrapping, so the bar stays one line however many filters are on. */
.filters__active {
  flex: 1 1 auto;
  min-width: 0;
  font-size: 0.83rem;
  color: var(--text-dim);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/*
 * The summary's buttons sit right, however many of them are showing — the active
 * filter line above already grows to fill, so it does the pushing. Not
 * `margin-left: auto` on the buttons: Clear filters comes and goes, and two elements
 * each claiming it would split the free space between them instead of both going to
 * the end.
 */
.filters__summary .btn {
  flex: 0 0 auto;
}

.filters__body {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  align-items: flex-end;
  padding: 16px;
}

.filters__body .field {
  margin: 0;
  flex: 1 1 190px;
  min-width: 0;
}

.filters__body select {
  text-overflow: ellipsis;
}

.filters__body .field > label {
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-faint);
  font-weight: 600;
}

/* A toolbar with no chrome of its own, for forms nested inside a card. */
.toolbar--bare {
  border: 0;
  padding: 0;
  margin: 0;
  box-shadow: none;
  background: none;
}

/* --------------------------------------- cinema > auditorium > complaint */
.theatre {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow);
  margin-bottom: 20px;
  overflow: hidden;
}

.theatre__head {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 16px 20px;
  background: linear-gradient(180deg, var(--surface-3), var(--surface-2));
  border-bottom: 1px solid var(--border);
  flex-wrap: wrap;
}

.theatre__name {
  margin: 0;
  font-size: 1.1rem;
}

.theatre__meta {
  font-size: 0.83rem;
  color: var(--text-dim);
}

.theatre__count {
  margin-left: auto;
  font-size: 0.83rem;
  color: var(--text-faint);
  font-variant-numeric: tabular-nums;
}

/* Each cinema is a disclosure on the review queue, which spans every building.
   The header keeps the same layout as the static version — it is a <summary>
   rather than a <div>, so the native marker goes and a chevron takes its place. */
.theatre--toggle > .theatre__head {
  border-bottom: 0;
}

.theatre--toggle[open] > .theatre__head {
  border-bottom: 1px solid var(--border);
}

.theatre__head--toggle {
  padding-left: 42px;
  position: relative;
  cursor: pointer;
  user-select: none;
  list-style: none;
  transition: background-color 140ms var(--ease);
}

.theatre__head--toggle::-webkit-details-marker {
  display: none;
}

.theatre__head--toggle::before {
  content: '';
  position: absolute;
  left: 20px;
  top: 50%;
  width: 7px;
  height: 7px;
  margin-top: -5px;
  border-right: 2px solid var(--text-dim);
  border-bottom: 2px solid var(--text-dim);
  transform: rotate(-45deg);
  transition: transform 160ms var(--ease);
}

.theatre--toggle[open] > .theatre__head--toggle::before {
  transform: rotate(45deg);
  margin-top: -6px;
}

.theatre__head--toggle:hover {
  background: var(--surface-3);
}

.theatre__head--toggle:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: -3px;
}

/* Address and phone. Takes the full width below the cinema name so a long
   street address never squeezes the counts out of the header. */
.contact {
  flex: 1 0 100%;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 6px 18px;
  margin-top: -2px;
  font-size: 0.83rem;
}

.contact__link {
  color: var(--text-dim);
  text-decoration: none;
  border-bottom: 1px solid transparent;
  transition: color 120ms var(--ease), border-color 120ms var(--ease);
}

.contact__link:hover {
  color: var(--text);
  border-bottom-color: var(--border-strong);
}

.contact__link:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
  border-radius: 3px;
}

.contact__link--phone {
  font-variant-numeric: tabular-nums;
}

.auditorium {
  border-bottom: 1px solid var(--border);
}

.auditorium:last-child {
  border-bottom: 0;
}

.auditorium__head {
  display: flex;
  align-items: baseline;
  gap: 10px;
  padding: 11px 20px;
  background: color-mix(in oklab, var(--p-base) 45%, transparent);
  border-bottom: 1px solid var(--border);
}

.auditorium__number {
  font-weight: 680;
  font-size: 0.95rem;
  letter-spacing: -0.01em;
  font-variant-numeric: tabular-nums;
}

.auditorium__meta {
  font-size: 0.8rem;
  color: var(--text-faint);
}

.auditorium__count {
  margin-left: auto;
  font-size: 0.8rem;
  color: var(--text-dim);
  font-variant-numeric: tabular-nums;
}

.complaint {
  padding: 16px 20px;
  border-bottom: 1px solid var(--border);
  transition: background-color 140ms var(--ease);
}

.complaint:last-child {
  border-bottom: 0;
}

/* ------------------------------------------------------------- deep links
   Arriving from a link that named one report — Needs attention on the home page
   does — the queue scrolls to it and marks it. Inset, so the ring reads as being
   around this row rather than as a border between two of them.

   It fades after a few seconds (removed by review.js): the mark says where to
   look on arrival, and a highlight that never leaves becomes furniture. */
.complaint--spotlight {
  background: var(--accent-soft);
  box-shadow: inset 0 0 0 2px var(--accent);
  border-radius: var(--radius);
}

/*
 * Why the queue is narrowed, when the reader did not narrow it themselves. The
 * collapsed filter bar already names the active filters, but somebody who got here
 * by clicking "Review now" did not choose them — and a short queue with no
 * explanation reads as an empty one.
 */
.deeplink {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px;
  margin-bottom: 16px;
  padding: 12px 18px;
  border: 1px solid color-mix(in oklab, var(--accent) 35%, transparent);
  border-left: 3px solid var(--accent);
  border-radius: var(--radius);
  background: var(--accent-soft);
}

.deeplink__text {
  flex: 1 1 300px;
  font-size: 0.88rem;
}

.complaint__top {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  margin-bottom: 10px;
}

.complaint__ref {
  font-family: var(--mono);
  font-size: 0.78rem;
  color: var(--text-faint);
  letter-spacing: 0.02em;
}

.complaint__when {
  margin-left: auto;
  font-size: 0.78rem;
  color: var(--text-faint);
}

.complaint__body {
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  margin: 0 0 12px;
  color: var(--text);
}

.complaint__facts {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 18px;
  font-size: 0.84rem;
  color: var(--text-dim);
  margin-bottom: 12px;
}

.complaint__note {
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-left: 3px solid var(--p-surface2);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  padding: 10px 14px;
  font-size: 0.88rem;
  margin: 0 0 12px;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.complaint__note strong {
  display: block;
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-faint);
  font-weight: 650;
  margin-bottom: 2px;
}

.complaint__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: center;
}

.complaint--busy {
  opacity: 0.45;
  pointer-events: none;
}

/* --------------------------------------------------- archived / editing */

/* An archived cinema or screen is dimmed but still fully readable — its
   complaint history is intact and admins still need to work with it. */
.theatre--archived .theatre__head {
  background: repeating-linear-gradient(
    135deg,
    var(--surface-2),
    var(--surface-2) 10px,
    var(--surface) 10px,
    var(--surface) 20px
  );
}

.theatre--archived .theatre__name {
  color: var(--text-dim);
}

/* Inline edit rows: the form takes the place of the row it replaces. */
.theatre__head--editing,
.complaint--editing {
  background: var(--accent-soft);
  border-bottom: 1px solid var(--border);
  display: block;
}

.theatre__head--editing {
  display: flex;
  align-items: flex-end;
  gap: 12px;
  flex-wrap: wrap;
}

.theatre__head--editing .field {
  margin: 0;
  flex: 1 1 150px;
  /* Without a cap, whichever field lands alone on a wrapped row stretches to
     the full width and stops looking like a sibling of the others. */
  max-width: 280px;
  min-width: 0;
}

/* A street address needs more room than a ZIP or a region. */
.theatre__head--editing .field--wide {
  flex: 2 1 220px;
  max-width: 380px;
}

.theatre__head--editing .field > label,
.toolbar--bare .field > label {
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-faint);
  font-weight: 600;
}

.theatre__head--editing input,
.toolbar--bare input {
  min-height: 38px;
  padding: 7px 11px;
  font-size: 0.95rem;
}

/* ------------------------------------------------------- page head + modal */

/* Heading on the left, the page's one create action on the right. */
.page-head--split {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 20px;
  flex-wrap: wrap;
}

.page-head--split > div {
  flex: 1 1 420px;
  min-width: 0;
}

.page-head--split > div > h1 {
  margin-bottom: 0.35em;
}

.page-head--split .btn {
  flex: 0 0 auto;
  margin-top: 4px;
}

/* The "Add new…" dialog. Native <dialog>, so focus trapping, Escape and
   inertness of the page behind come from the browser. */
.modal {
  width: min(660px, calc(100vw - 32px));
  max-height: min(86vh, 760px);
  padding: 0;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-lg);
  background: var(--surface);
  color: var(--text);
  box-shadow: var(--shadow-lift);
  overflow: hidden;
}

.modal::backdrop {
  background: rgba(10, 10, 16, 0.66);
  backdrop-filter: blur(3px);
  -webkit-backdrop-filter: blur(3px);
}

.modal__head {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 18px 20px;
  border-bottom: 1px solid var(--border);
  background: linear-gradient(180deg, var(--surface-3), var(--surface-2));
}

.modal__title {
  margin: 0;
  font-size: 1.05rem;
  flex: 1 1 auto;
}

.modal__x {
  flex: none;
  width: 32px;
  height: 32px;
  display: grid;
  place-items: center;
  font-size: 22px;
  line-height: 1;
  border-radius: 8px;
  border: 1px solid transparent;
  background: none;
  color: var(--text-dim);
  cursor: pointer;
  transition:
    background-color 140ms var(--ease),
    color 140ms var(--ease);
}

.modal__x:hover {
  background: var(--surface-3);
  color: var(--text);
}

.modal__notice {
  margin: 16px 20px 0;
}

.modal__body {
  padding: 20px;
  max-height: calc(86vh - 150px);
  overflow-y: auto;
}

.modal__body[hidden] {
  display: none;
}

.modal__grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 0 18px;
}

.modal__grid .field:last-child {
  grid-column: 1 / -1;
}

/* Sticky, so the primary action stays reachable when a form scrolls — which it
   does on a phone. */
.modal__foot {
  position: sticky;
  bottom: -20px;
  display: flex;
  justify-content: flex-end;
  gap: 10px;
  margin-top: 8px;
  padding: 16px 0;
  border-top: 1px solid var(--border);
  background: var(--surface);
}

/* Step 1 — the two things you can add. */
.choice {
  display: block;
  width: 100%;
  text-align: left;
  font: inherit;
  padding: 16px 18px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  background: var(--surface-2);
  color: var(--text);
  cursor: pointer;
  transition:
    background-color 140ms var(--ease),
    border-color 140ms var(--ease),
    transform 100ms var(--ease);
}

.choice + .choice {
  margin-top: 12px;
}

.choice:hover {
  background: var(--accent-soft);
  border-color: color-mix(in oklab, var(--accent) 50%, transparent);
}

.choice:active {
  transform: translateY(1px);
}

.choice__title {
  display: block;
  font-weight: 650;
  font-size: 1rem;
  letter-spacing: -0.01em;
  margin-bottom: 3px;
}

.choice__sub {
  display: block;
  font-size: 0.87rem;
  color: var(--text-dim);
  line-height: 1.45;
}

@media (max-width: 560px) {
  .page-head--split .btn {
    width: 100%;
  }
}

/* --------------------------------------------- collapsible screens section
   A 16-screen cinema would push everything below it off the page, so the
   auditorium list is a disclosure that starts closed. */
.screens {
  border-bottom: 1px solid var(--border);
}

.screens:last-child {
  border-bottom: 0;
}

.screens__summary {
  display: flex;
  align-items: baseline;
  gap: 10px;
  flex-wrap: wrap;
  padding: 12px 20px 12px 42px;
  position: relative;
  cursor: pointer;
  background: color-mix(in oklab, var(--p-base) 45%, transparent);
  user-select: none;
  transition: background-color 140ms var(--ease);
}

/* Replace the native triangle with a chevron we can animate. */
.screens__summary::-webkit-details-marker {
  display: none;
}

.screens__summary {
  list-style: none;
}

.screens__summary::before {
  content: '';
  position: absolute;
  left: 20px;
  top: 50%;
  width: 7px;
  height: 7px;
  margin-top: -5px;
  border-right: 2px solid var(--text-dim);
  border-bottom: 2px solid var(--text-dim);
  transform: rotate(-45deg);
  transition: transform 160ms var(--ease);
}

.screens[open] > .screens__summary::before {
  transform: rotate(45deg);
  margin-top: -6px;
}

.screens__summary:hover {
  background: var(--surface-3);
}

.screens[open] > .screens__summary {
  border-bottom: 1px solid var(--border);
}

.screens__title {
  font-weight: 680;
  font-size: 0.95rem;
  letter-spacing: -0.01em;
}

.screens__meta {
  font-size: 0.83rem;
  color: var(--text-dim);
  font-variant-numeric: tabular-nums;
}

/* Unsaved deletions are the one thing that must be obvious while closed. */
.screens__meta--warn {
  color: var(--danger);
  font-weight: 600;
}

/* Guidance only matters once the list is open. */
.screens__hint {
  margin-left: auto;
  display: none;
}

.screens[open] > .screens__summary .screens__hint {
  display: inline;
}

/* ------------------------------------------------- revealed edit actions
   Rows show a single Edit button. The panel it opens holds the fields plus
   everything else you can do to that row, separated by a rule so the
   destructive options are not mistaken for part of the form. */
.edit-more {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  width: 100%;
  margin-top: 14px;
  padding-top: 14px;
  border-top: 1px dashed var(--border-strong);
}

.edit-more__label {
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-weight: 600;
  color: var(--text-faint);
  margin-right: 2px;
}

/* -------------------------------------------------- managers on a cinema */
.managers {
  border-bottom: 1px solid var(--border);
}

/* A cinema with nobody assigned is a real problem — validated reports would
   have nowhere to go — so it is called out rather than stated flatly. */
.managers__warn {
  color: var(--warn);
  font-weight: 600;
}

.managers__head {
  display: flex;
  align-items: baseline;
  gap: 10px;
  flex-wrap: wrap;
  padding: 11px 20px;
  background: color-mix(in oklab, var(--p-base) 45%, transparent);
  border-bottom: 1px solid var(--border);
}

/* A select used as an action inside a row of buttons, rather than a form field:
   sized to its content and matched to .btn--sm. */
.inline-select {
  width: auto;
  flex: 0 0 auto;
  min-height: 34px;
  padding: 5px 30px 5px 11px;
  font-size: 0.85rem;
  border-radius: 8px;
  background-position: right 13px center, right 8px center;
}

.inline-select:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

/* ------------------------------------------- staged (unsaved) deletions */

/* A row marked for deletion but not yet saved. Tinted and struck through so it
   is obviously different from a row that is simply archived. */
.complaint--staged {
  background: var(--danger-soft);
}

.auditorium__number--struck {
  text-decoration: line-through;
  text-decoration-thickness: 2px;
  text-decoration-color: color-mix(in oklab, var(--danger) 70%, transparent);
  color: var(--text-dim);
}

.badge--pending-delete {
  background: transparent;
  color: var(--danger);
  border-color: var(--danger);
  border-style: dashed;
}

/* The commit point for staged deletions. */
.savebar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 14px;
  padding: 16px 20px;
  background: linear-gradient(180deg, var(--danger-soft), var(--surface-2));
  border-top: 1px solid color-mix(in oklab, var(--danger) 45%, transparent);
}

.savebar__text {
  flex: 1 1 260px;
  min-width: 0;
}

/*
 * The Settings page's save bar follows you down the page. Its neutral tint is not
 * the dashed-danger one the locations page uses — nothing here is destructive,
 * and borrowing the red would cry wolf.
 */
.savebar--sticky {
  position: sticky;
  /* Clear of the very bottom so it reads as floating over the page, not welded on. */
  bottom: 16px;
  z-index: 20;
  margin-top: 16px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-lg);
  background: linear-gradient(180deg, var(--surface-3), var(--surface));
  box-shadow: var(--shadow-lift);
}

/*
 * Room to scroll the last row clear of the pinned bar — but only while there IS a
 * bar. It appears on the first change and nowhere else, so holding 180px of empty
 * page open for it the rest of the time would be worse than the bar was.
 *
 * A sticky element still occupies its place in the flow, so the page has to be
 * scrollable far enough to reach it, or the bar stays stuck over the content above
 * it. Sized for its tallest form, the phone layout where the text and buttons wrap
 * onto separate rows.
 */
.shell--medium:has(#savebar:not([hidden])) {
  padding-bottom: 180px;
}

/* ------------------------------------------------------------- settings page */
/*
 * Every section of the page is a disclosure, closed by default.
 *
 * Open all at once, the page is something you scroll to read rather than something
 * you use: the theme swatches alone are two rows tall, and what you came for is
 * usually one row in one section. Closed, the page is a short list of what is here,
 * and each summary carries a count so a shut section still says how much is inside.
 *
 * Native <details>, the same as the auditorium list on the locations page — so
 * keyboard support, find-in-page opening the right section, and the browser's own
 * expand-all come for free rather than being reimplemented.
 */
.section {
  /* The summary tints the full width, so the card's padding moves inwards to it. */
  padding: 0;
  /* Clips that tint to the rounded corners. */
  overflow: hidden;
}

.section__summary {
  list-style: none;
  display: flex;
  align-items: baseline;
  gap: 10px;
  flex-wrap: wrap;
  padding: 16px 22px 16px 44px;
  position: relative;
  cursor: pointer;
  user-select: none;
  transition: background-color 140ms var(--ease);
}

/* Replace the native triangle with a chevron we can animate. */
.section__summary::-webkit-details-marker {
  display: none;
}

.section__summary::before {
  content: '';
  position: absolute;
  left: 22px;
  top: 50%;
  width: 7px;
  height: 7px;
  margin-top: -5px;
  border-right: 2px solid var(--text-dim);
  border-bottom: 2px solid var(--text-dim);
  transform: rotate(-45deg);
  transition: transform 160ms var(--ease);
}

.section[open] > .section__summary::before {
  transform: rotate(45deg);
  margin-top: -6px;
}

.section__summary:hover {
  background: var(--surface-3);
}

/* Inset, because the card clips its overflow and a +2px offset ring would be cut
   off on three sides. */
.section__summary:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: -3px;
}

.section[open] > .section__summary {
  border-bottom: 1px solid var(--border);
}

/*
 * Still a real <h2> inside the summary — which HTML allows — so a screen reader
 * user can jump between sections by heading instead of tabbing through five
 * disclosures to find the one they want.
 */
.section__title {
  margin: 0;
  font-weight: 680;
  font-size: 1.05rem;
  letter-spacing: -0.01em;
}

.section__meta {
  font-size: 0.83rem;
  color: var(--text-dim);
  font-variant-numeric: tabular-nums;
}

/*
 * An edit hidden inside a closed section is the one thing collapsing must not do
 * quietly, so it is marked on the summary as well as counted in the save bar.
 * Accent rather than danger: nothing here is destructive, and the locations page's
 * red belongs to staged deletions.
 */
.section__meta--dirty {
  color: var(--accent);
  font-weight: 600;
}

/*
 * How many things are waiting, on the home page's Needs attention panel — the one
 * part of a closed disclosure that has to carry weight, since closing it is how you
 * put the warnings away. Declared here beside the other modifier rather than with
 * the panel: both are one class deep, so the plain .section__meta below would win
 * on source order and quietly grey it out.
 */
.section__meta--alert {
  color: var(--warn);
  font-weight: 620;
}

.section__body {
  padding: 20px 22px 22px;
}

#settings-form {
  margin: 0;
}

/*
 * `.card + .card` cannot reach across the form the schema's sections live in, so the
 * gap around it is stated. Only while it holds sections: a General Manager's form is
 * empty, and an empty form should take up no room at all.
 */
#settings-form:has(.section) {
  margin: 16px 0;
}

.setting + .setting,
/* The theme card's circuit-wide rows sit in a wrapper of their own, so the divider
   between "Your theme" and what follows has to cross it. */
#theme-circuit > .setting:first-child {
  margin-top: 22px;
  padding-top: 22px;
  border-top: 1px solid var(--border);
}

.setting__head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  margin-bottom: 4px;
}

.setting__label {
  font-weight: 650;
  font-size: 0.95rem;
  color: var(--text);
}

/* Pushed to the right so the labels down the page stay a readable column. */
.setting__source {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  white-space: nowrap;
}

.setting__reset {
  border: 0;
  background: none;
  padding: 2px 4px;
  color: var(--text-dim);
  font: inherit;
  font-size: 0.8rem;
  text-decoration: underline;
  text-decoration-color: var(--border-strong);
  cursor: pointer;
}

.setting__reset:hover {
  color: var(--accent);
}

.setting__control {
  margin-top: 10px;
}

.setting__control input[type='text'],
.setting__control input[type='email'] {
  width: 100%;
}

.setting__control--inline {
  display: flex;
  align-items: center;
  gap: 9px;
}

.setting__control--inline input[type='checkbox'] {
  width: 18px;
  height: 18px;
  margin: 0;
  accent-color: var(--accent);
  cursor: pointer;
}

.setting__inline-label {
  font-size: 0.9rem;
  color: var(--text-dim);
  cursor: pointer;
}

.setting__pills {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

/*
 * The last remaining issue type is disabled: a form with nothing to submit is a
 * broken form, and finding that out from a guest holding a phone is too late.
 */
.pill input:disabled + span {
  cursor: not-allowed;
}

.pill input:disabled:checked + span {
  opacity: 0.75;
}

/* ------------------------------------------------------------ theme picker
   Swatches, not a dropdown: the thing being chosen is how it looks, and a list of
   names tells you nothing about which one you want.

   Each card carries data-preview="<id>", and the theme blocks at the top of this
   file are re-applied to it below — so a preview is drawn with the real theme's
   own tokens and cannot drift from what selecting it does. */
.themes {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(210px, 1fr));
  gap: 12px;
  margin-top: 16px;
}

.theme {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 14px;
  text-align: left;
  font: inherit;
  cursor: pointer;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  /* The card paints itself in the theme it offers. */
  background: var(--bg);
  color: var(--text);
  transition:
    border-color 140ms var(--ease),
    transform 140ms var(--ease);
}

.theme:hover {
  border-color: var(--p-accent);
  transform: translateY(-1px);
}

.theme--chosen {
  border-color: var(--p-accent);
  box-shadow: 0 0 0 1px var(--p-accent) inset;
}

.theme__swatch {
  display: flex;
  gap: 6px;
}

.theme__chip {
  width: 26px;
  height: 26px;
  border-radius: 7px;
  border: 1px solid var(--border-strong);
}

.theme__chip--surface {
  background: var(--surface-2);
}

.theme__chip--accent {
  background: var(--p-accent-fill);
  border-color: var(--p-accent-fill);
}

.theme__chip--text {
  background: var(--p-text);
  border-color: var(--p-text);
}

.theme__name {
  display: flex;
  align-items: center;
  gap: 8px;
  font-weight: 650;
  font-size: 0.92rem;
}

.theme__default {
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  padding: 2px 6px;
  border-radius: 5px;
  color: var(--text-dim);
  background: var(--surface-3);
  border: 1px solid var(--border-strong);
}

.theme__note {
  font-size: 0.8rem;
  color: var(--text-dim);
  line-height: 1.45;
}

/* Which half of System the swatch happens to be showing. */
.theme__note--live {
  color: var(--p-accent);
  font-weight: 600;
}

/* Inside a settings row rather than a card of its own: smaller, and no note. */
.themes--compact {
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 8px;
}

.themes--compact .theme {
  padding: 10px;
  gap: 6px;
}

.themes--compact .theme__chip {
  width: 20px;
  height: 20px;
}

.themes--compact .theme__name {
  font-size: 0.84rem;
}

/* Not a preview of anything, so it keeps the page's own colours. */
.theme--reset {
  align-items: flex-start;
  justify-content: center;
  background: var(--surface-2);
  color: var(--text-dim);
  border-style: dashed;
  font-size: 0.86rem;
  font-weight: 600;
}

.theme--reset:hover {
  color: var(--text);
}

/* --------------------------------------------------------- read-only facts */
.facts {
  margin: 16px 0 0;
}

.facts__row {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 16px;
  padding: 9px 0;
  border-top: 1px solid var(--border);
}

.facts dt {
  flex: 0 0 200px;
  color: var(--text-dim);
  font-size: 0.88rem;
}

.facts dd {
  flex: 1 1 200px;
  margin: 0;
  font-size: 0.88rem;
  font-weight: 600;
  overflow-wrap: anywhere;
}

.facts__warn {
  color: var(--warn);
}

.savebar__text strong {
  display: block;
  font-size: 0.95rem;
}

.savebar .field__hint {
  margin: 3px 0 0;
}

/* ---------------------------------------------------------------- states */
.empty {
  text-align: center;
  padding: 56px 24px;
  color: var(--text-dim);
  background: var(--surface);
  border: 1px dashed var(--border-strong);
  border-radius: var(--radius-lg);
}

.empty h2 {
  color: var(--text);
}

.empty p {
  margin: 0 auto;
  max-width: 46ch;
}

.spinner {
  text-align: center;
  padding: 48px;
  color: var(--text-faint);
  font-size: 0.92rem;
}

/* A quiet pulse, so a slow request looks alive rather than stuck. */
.spinner::after {
  content: '';
  display: block;
  width: 26px;
  height: 26px;
  margin: 16px auto 0;
  border-radius: 50%;
  border: 2px solid var(--border-strong);
  border-top-color: var(--accent);
  animation: spin 700ms linear infinite;
}

@keyframes spin {
  to {
    transform: rotate(1turn);
  }
}

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

.timeline {
  list-style: none;
  margin: 0;
  padding: 0;
  font-size: 0.85rem;
}

.timeline li {
  padding: 7px 0 7px 14px;
  border-left: 2px solid var(--border-strong);
  color: var(--text-dim);
}

.timeline li strong {
  color: var(--text);
}

details.reveal {
  margin-bottom: 12px;
  border-top: 1px solid var(--border);
  padding-top: 12px;
}

details.reveal > summary {
  cursor: pointer;
  font-size: 0.88rem;
  color: var(--p-lavender);
  font-weight: 550;
  padding: 2px 0;
}

details.reveal > summary:hover {
  color: var(--accent);
}

details.reveal > div {
  padding-top: 14px;
}

/* ------------------------------------------------------- guest form page */
.guest-hero {
  position: relative;
  padding: 28px 20px 26px;
  background:
    radial-gradient(900px 320px at 12% -60%, color-mix(in oklab, var(--accent) 26%, transparent), transparent),
    linear-gradient(180deg, var(--surface-2), var(--surface));
  border-bottom: 1px solid var(--border);
}

/* Gradient rule along the bottom edge, in place of a heavy colour band. */
.guest-hero::after {
  content: '';
  position: absolute;
  inset: auto 0 0 0;
  height: 2px;
  background: var(--accent-line);
}

.guest-hero__inner {
  max-width: 640px;
  margin: 0 auto;
}

.guest-hero h1 {
  margin: 0 0 6px;
  font-size: clamp(1.3rem, 1.15rem + 1vw, 1.6rem);
}

.guest-hero__where {
  font-size: 0.95rem;
  color: var(--p-subtext1);
  margin: 0;
}

.thanks {
  text-align: center;
  padding: 26px 0;
}

.thanks__mark {
  width: 60px;
  height: 60px;
  border-radius: 50%;
  background: var(--ok-soft);
  color: var(--ok);
  border: 1px solid color-mix(in oklab, var(--ok) 40%, transparent);
  display: grid;
  place-items: center;
  font-size: 28px;
  margin: 0 auto 16px;
  box-shadow: 0 0 0 8px color-mix(in oklab, var(--ok) 7%, transparent);
}

.thanks__ref {
  font-family: var(--mono);
  font-size: 1.3rem;
  font-weight: 650;
  letter-spacing: 0.08em;
  color: var(--accent);
  background: var(--accent-soft);
  border: 1px dashed color-mix(in oklab, var(--accent) 45%, transparent);
  border-radius: var(--radius);
  padding: 12px 20px;
  display: inline-block;
  margin: 8px 0 18px;
}

/* --------------------------------------------------------------- mobile */
@media (max-width: 700px) {
  .wordmark {
    font-size: 0.95rem;
  }

  /*
   * Home joins the wordmark on the first row; the rest goes on one line below it.
   *
   * The nav used to drop to its own line whole, which put Home on a second row and
   * the remaining links on a third — three rows of sticky top bar covering the
   * reports somebody came to read. Home is the one link that belongs beside the
   * logo anyway: it is where the logo goes.
   *
   * `display: contents` is how one child of the nav can be lifted onto the row
   * above it — the nav's box disappears and its links become flex items of the top
   * bar, so each can be ordered on its own. The explicit role="navigation" in the
   * markup is what keeps the landmark: an element with no box has, in some
   * browsers, lost the role its tag name implied.
   *
   * Losing the nav's box also loses its left divider, which is the point: on this
   * row Home sits WITH the wordmark, not across a rule from it.
   */
  .nav {
    display: contents;
  }

  #nav-home {
    order: 1;
  }

  /* Right of the first row, opposite the brand. The auto margin also eats the free
     space, which is what pushes the strip below onto its own row. */
  .account {
    order: 2;
    margin-left: auto;
  }

  /* A full-width item of no height: it wraps to a row of its own, so its top border
     becomes the rule under the first row, and everything after it starts below. */
  .topbar__spacer {
    order: 3;
    flex: 0 0 100%;
    height: 0;
    border-top: 1px solid var(--border);
  }

  /*
   * The rest of the nav, on one line of its own.
   *
   * Wrapping was the alternative and it defeated the point: at 390px the three of
   * them need more width than there is, so they took two rows and the bar was as
   * tall as it had been before Home moved. One row that scrolls when it has to is a
   * shorter bar at every width — and the thin scrollbar the stylesheet already gives
   * everything is the affordance that says there is more.
   */
  .nav__rest {
    order: 4;
    flex: 0 0 100%;
    gap: 2px;
    overflow-x: auto;
    /* Vertical is never wanted here, and `auto` on one axis implies it on the other
       unless it is said. */
    overflow-y: hidden;
    /* Room for the scrollbar, so it does not sit on top of the link text. */
    padding-bottom: 2px;
  }

  /*
   * The links are the whole width of this strip, so they must not be squeezed — and
   * they are tighter here than on desktop, because the three of them plus 12px of
   * padding each came to 23px more than a 390px phone has. Trimmed to 8px they fit
   * that width exactly, which is the one most people are holding; narrower phones
   * scroll the last one into view. The 7px vertical stays, so the tap target does
   * not get shorter.
   */
  .nav__rest .nav__link {
    flex: 0 0 auto;
    padding-left: 8px;
    padding-right: 8px;
  }

  /* Tighter between rows than the 14px that separates things along one. */
  .topbar__inner {
    row-gap: 9px;
    /* Home reads as part of the brand at this width rather than as the first of a
       list it is no longer next to. */
    column-gap: 10px;
  }
}

@media (max-width: 560px) {
  .shell {
    padding-left: 16px;
    padding-right: 16px;
  }

  .theatre__head,
  .auditorium__head,
  .complaint,
  .savebar {
    padding-left: 16px;
    padding-right: 16px;
  }

  .person {
    padding-left: 16px;
    padding-right: 16px;
    row-gap: 8px;
  }

  .person__scope,
  .person__seen {
    flex: 1 1 100%;
    text-align: left;
  }

  .filters__summary {
    flex-wrap: wrap;
    padding-left: 12px;
    padding-right: 12px;
  }

  /* The buttons keep the first row; what is filtering the queue gets its own line
     rather than being truncated to an ellipsis on a narrow screen.
     Buttons stretch at this width, which would leave the bar itself — whose job
     is to open the filters — a sliver to tap. These stay their own size.
     Two classes deep on purpose: the generic `.btn { flex: 1 1 auto }` below
     shares this rule's specificity and would win on source order. */
  .filters__summary .btn {
    order: 1;
    flex: 0 0 auto;
  }

  .filters__active {
    order: 2;
    flex: 1 1 100%;
    white-space: normal;
  }

  .filters__body {
    padding-left: 12px;
    padding-right: 12px;
  }

  /* Still has to clear the chevron. The header wraps to three lines at this
     width, so the chevron sits against the cinema name rather than floating in
     the middle of the block. */
  .theatre__head--toggle {
    padding-left: 38px;
  }

  .theatre__head--toggle::before {
    left: 16px;
    top: 28px;
  }

  .complaint__when {
    margin-left: 0;
    width: 100%;
  }

  .theatre__count {
    margin-left: 0;
    width: 100%;
  }

  .btn {
    flex: 1 1 auto;
  }

  .page-head {
    padding-top: 24px;
  }
}

/* =========================================================================
   Printed placards

   These go on paper beside an auditorium door, so they are deliberately black
   on white regardless of the theme — a dark placard would waste toner and scan
   no better.
   ========================================================================= */
.placards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: 16px;
}

/*
 * One printable card plus its admin controls. The controls are a sibling rather
 * than a child so the card stays a clean white object that can be trimmed out,
 * and so `.no-print` can drop them without leaving a gap inside the border.
 */
.placard-slot {
  display: flex;
  flex-direction: column;
  gap: 10px;
  break-inside: avoid;
}

.placard__admin {
  display: flex;
  justify-content: center;
}

.placard {
  background: #ffffff;
  color: #11111b;
  border: 2px solid #11111b;
  border-radius: 12px;
  padding: 22px 16px;
  text-align: center;
  break-inside: avoid;
}

.placard__theatre {
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-weight: 600;
  color: #4c4f69;
}

.placard__aud {
  font-size: 2.1rem;
  font-weight: 750;
  letter-spacing: -0.03em;
  margin: 2px 0 12px;
  line-height: 1.05;
}

.placard img {
  width: 100%;
  max-width: 210px;
  height: auto;
  display: block;
  margin: 0 auto 12px;
}

.placard__cta {
  font-size: 1rem;
  font-weight: 650;
  margin: 0 0 4px;
}

.placard__sub {
  font-size: 0.8rem;
  color: #5c5f77;
  margin: 0;
}

.placard__url {
  font-family: var(--mono);
  font-size: 0.66rem;
  color: #7c7f93;
  margin-top: 10px;
  overflow-wrap: anywhere;
}

@media print {
  .topbar,
  .toolbar,
  .filters,
  .page-head p,
  .notice,
  .no-print {
    display: none !important;
  }

  body {
    background: #fff;
    color: #11111b;
  }

  .page-head h1 {
    color: #11111b;
  }

  .shell {
    max-width: none;
    padding: 0;
  }

  .placards {
    grid-template-columns: repeat(2, 1fr);
    gap: 12px;
  }

  /* The slot collapses to just the card once its controls are hidden. */
  .placard-slot,
  .placard {
    page-break-inside: avoid;
    gap: 0;
  }
}
