/* ==========================================================================
   San Andreas First Response — site design system
   Committed dark theme: this is a night-patrol subject and the community's
   existing brand (deep navy + cyan) is already dark. Every colour is painted
   explicitly so nothing inherits from a host page.
   ========================================================================== */

:root {
  /* Ground and surfaces — near-black neutrals carrying the faintest blue bias,
     so they read cool and deliberate rather than muddy grey. */
  --ground:      #0A0B0D;
  --ground-2:    #0F1113;
  --surface:     #141619;
  --surface-2:   #1B1E22;
  --line:        #2A2E34;
  --line-soft:   #1F2328;

  /* The two policing colours. Used as a pair wherever a light bar is implied,
     and individually for interactive (blue) and urgent (red). */
  --blue:        #4C8DF6;
  --blue-soft:   #86B3FF;
  --red:         #E5484D;
  --red-soft:    #FF7075;

  /* Interactive accent is the blue; the primary button is white (below). */
  --accent:      #4C8DF6;
  --accent-soft: #86B3FF;
  --accent-dim:  #2A4A7A;

  /* Text */
  --text:        #DEE1E6;
  --text-strong: #FFFFFF;
  --muted:       #8A9099;
  --muted-dim:   #6B727B40;

  /* Departments — official colours */
  --lspd: #3498DB;
  --lssd: #F1C40F;
  --sahp: #9B976D;

  /* Semantic — deliberately distinct from the two policing accents */
  --good: #3FB27A;
  --warn: #D9A441;
  --bad:  #E5484D;

  /* Type */
  --display: "Barlow Condensed", "Arial Narrow", system-ui, sans-serif;
  --body: "IBM Plex Sans", "Segoe UI", system-ui, -apple-system, sans-serif;
  --mono: "IBM Plex Mono", Consolas, "Cascadia Mono", monospace;

  /* Rhythm */
  --shell: 68rem;
  --radius: 10px;
  --radius-sm: 6px;
}

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

html { scroll-behavior: smooth; }
/*
 * MOTION IS ALWAYS ON. This is deliberate, and it is a decision, not an
 * oversight — so please don't "fix" it by adding prefers-reduced-motion back.
 *
 * Windows reports reduced-motion for reasons that often have nothing to do with
 * the person: Remote Desktop turns client-area animation off for bandwidth, and
 * Chrome maps that straight onto the media query. Every admin on an RDP session
 * was therefore getting a site with no animation at all, which is how this was
 * found. The site owner asked for animations unconditionally, with no opt-out.
 *
 * The trade-off is real and was raised: visitors who genuinely need reduced
 * motion will still get the full set. If that is ever revisited, the honest fix
 * is to honour the media query again rather than to add a hidden toggle.
 */

/* ------------------------------------------------------ page transitions */

/*
 * Cross-document view transitions. This is a real multi-page site — every page
 * is a full server render — so moving between them used to be a white flash and
 * a jump. `navigation: auto` lets the browser hold the old page on screen and
 * cross-fade to the new one, with no client-side router involved.
 *
 * Chromium-based browsers honour this today; everywhere else simply navigates
 * as it always did, so the fallback below covers them.
 */
@view-transition { navigation: auto; }

/*
 * Naming the header and footer lifts them OUT of the root snapshot, so they
 * stay anchored while only the page content moves. Without this the whole
 * viewport cross-fades — and since every page shares the same header, footer
 * and near-black background, that read as nothing happening at all.
 */
.site-header { view-transition-name: site-header; }
.site-footer { view-transition-name: site-footer; }

/* The chrome only needs to catch up with its own small changes, like which
   nav item is the current page. */
::view-transition-old(site-header),
::view-transition-old(site-footer) { animation: vt-leave 0.12s ease both; }
::view-transition-new(site-header),
::view-transition-new(site-footer) { animation: vt-appear 0.12s ease both; }

/* The content itself gets a real move, so the switch is legible. */
::view-transition-old(root) {
  animation: vt-out 0.18s cubic-bezier(0.4, 0, 1, 1) both;
}
::view-transition-new(root) {
  animation: vt-in 0.34s cubic-bezier(0.16, 0.84, 0.44, 1) both;
}

@keyframes vt-leave { to { opacity: 0; } }
@keyframes vt-appear { from { opacity: 0; } }
@keyframes vt-out { to { opacity: 0; transform: translateY(-14px); } }
@keyframes vt-in {
  from { opacity: 0; transform: translateY(24px); }
  to   { opacity: 1; transform: none; }
}

/* ------------------------------------------------------- scroll reveals */

/*
 * Sections rise into place as they scroll into view.
 *
 * A TIME-based transition triggered once on entry, not a scroll-scrubbed
 * animation. `animation-timeline: view()` ties progress to scroll offset, so
 * scrolling at any normal speed finishes the animation instantly and it reads
 * as nothing happening at all. Running a fixed 0.7s transition when the element
 * first enters is what actually looks like an animation. site.js does the
 * triggering.
 *
 * The hidden state hangs off `.js`, which an inline script in <head> sets
 * before first paint. If scripting is off or that script never runs, none of
 * this applies and every page renders normally — content is never hidden by
 * something that cannot also un-hide it.
 *
 * `:where()` keeps the hidden selectors at zero specificity, so the plain
 * `.js .is-in` below outranks them without needing !important.
 */
.js
  :where(
    .band .section-head,
    .band > .shell > .card,
    .band .grid > *,
    .band .rule-stack > *,
    .band .notice,
    .band .statline .stat,
    .staff-group
  ) {
  opacity: 0;
  /* Enough travel and time to actually register as movement, without content
     still drifting into place while you are reading it. */
  transform: translateY(26px);
  transition:
    opacity 0.55s cubic-bezier(0.22, 0.9, 0.3, 1),
    transform 0.55s cubic-bezier(0.22, 0.9, 0.3, 1);
  will-change: opacity, transform;
}

.js .is-in {
  opacity: 1;
  transform: none;
  will-change: auto;
}


/* Browsers without view transitions still get an entrance rather than a flash. */
@supports not (view-transition-name: none) {
  body { animation: page-in 0.24s cubic-bezier(0.16, 0.84, 0.44, 1) both; }
  @keyframes page-in {
    from { opacity: 0; transform: translateY(6px); }
    to   { opacity: 1; transform: none; }
  }
}

/*
 * Reduced motion keeps the FADES and drops the MOVEMENT.
 *
 * What the preference is actually asking for is no large or vestibular motion —
 * slides, parallax, zooms. A brief cross-fade involves no movement at all, and
 * killing it outright leaves navigation as a hard cut, which is both harsher to
 * look at and less clear about what just happened.
 *
 * These need `!important` to beat the blanket rule near the top of this file,
 * and they sit after it so the later declaration wins.
 */

body {
  margin: 0;
  background: var(--ground);
  color: var(--text);
  font-family: var(--body);
  font-size: 16px;
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}

img { max-width: 100%; display: block; }
a { color: var(--accent); text-decoration: none; }
a:hover { color: var(--accent-soft); text-decoration: underline; }

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

/* --------------------------------------------------------------- headings */

h1, h2, h3, h4 {
  font-family: var(--display);
  font-weight: 600;
  letter-spacing: 0.01em;
  color: var(--text-strong);
  text-wrap: balance;
  margin: 0;
  line-height: 1.12;
}
h1 { font-size: clamp(2.4rem, 5.5vw, 3.6rem); }
h2 { font-size: clamp(1.7rem, 3.4vw, 2.3rem); }
h3 { font-size: 1.3rem; }
h4 { font-size: 1.05rem; }

p { margin: 0 0 1rem; max-width: 65ch; }
p:last-child { margin-bottom: 0; }

/* Radio-code eyebrow: encodes real dispatch vernacular, not decoration */
.eyebrow {
  font-family: var(--mono);
  font-size: 0.72rem;
  font-weight: 500;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--accent);
  display: block;
  margin-bottom: 0.6rem;
}
.eyebrow--muted { color: var(--muted); }

/*
 * Pill label — a bolder alternative to .eyebrow, for a section that wants to
 * announce itself. Tone comes from --chip so the same component works in any
 * of the site's accent colours.
 *
 * .section-head is a flex column, so the chip needs align-self or it stretches
 * the full width of the block instead of hugging its own text.
 */
.chip {
  --chip: var(--accent);
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  padding: 0.4rem 0.95rem 0.4rem 0.8rem;
  border-radius: 100px;
  border: 1px solid color-mix(in srgb, var(--chip) 45%, transparent);
  background: color-mix(in srgb, var(--chip) 12%, transparent);
  box-shadow: 0 0 22px -8px color-mix(in srgb, var(--chip) 70%, transparent);
  color: var(--chip);
  font-size: 0.82rem;
  font-weight: 600;
  line-height: 1.2;
  white-space: nowrap;
}
.chip svg { width: 14px; height: 14px; flex: none; }

.chip--good   { --chip: var(--good); }
.chip--warn   { --chip: var(--warn); }
.chip--red    { --chip: var(--red); }
.chip--violet { --chip: #A855F7; }

.section-head .chip { align-self: flex-start; }
.figures-head .chip { align-self: center; }

.lede {
  font-size: 1.12rem;
  color: var(--text);
  max-width: 60ch;
}
.muted { color: var(--muted); }
.small { font-size: 0.875rem; }

/* ----------------------------------------------------------------- layout */

.shell {
  width: 100%;
  max-width: var(--shell);
  margin: 0 auto;
  padding: 0 1.5rem;
}

main { flex: 1 0 auto; }

/* ------------------------------------------------------- page backdrop --- */

/*
 * One fixed photograph behind the entire page, so scrolling past the hero
 * reveals it instead of flat --ground.
 *
 * A real fixed ELEMENT rather than `background-attachment: fixed`, which iOS
 * Safari has never supported properly and which repaints the whole layer on
 * every scroll frame on the browsers that do.
 *
 * z-index -1 puts it above the body's ground colour and below every piece of
 * content: body is unpositioned and sets no transform or opacity, so it creates
 * no stacking context and in-flow content still paints on top. Give body a
 * transform or a filter one day and this will vanish behind it.
 *
 * Darkened hard and desaturated on purpose. The source is a bright daylight
 * screenshot and this site is near-white type on near-black; any lighter and
 * body copy starts sitting on sky and water rather than on a background.
 */
.page-backdrop {
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background:
    linear-gradient(180deg,
      rgba(10, 11, 13, 0.93) 0%,
      rgba(10, 11, 13, 0.82) 42%,
      rgba(10, 11, 13, 0.90) 100%),
    var(--backdrop-img) center / cover no-repeat;
  /* brightness on top of the scrim: the source is a midday beach with a huge
     pale sky, which is far brighter than anything else on this site. */
  filter: saturate(0.38) brightness(0.88);
}

section.band { padding: 4.5rem 0; }
section.band + section.band { border-top: 1px solid var(--line-soft); }
.band--tint { background: var(--ground-2); }

/* Anything that paints its own opaque ground would punch a flat hole in the
   photograph. On a backdrop page those grounds become translucent so the image
   runs the whole way down, which is the entire point of having it. */
body[data-backdrop] .band--tint { background: rgba(15, 17, 19, 0.55); }
body[data-backdrop] .site-footer { background: rgba(15, 17, 19, 0.66); }

.stack { display: flex; flex-direction: column; gap: 1rem; }
.stack--lg { gap: 2rem; }
.stack--sm { gap: 0.5rem; }

.section-head {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  margin-bottom: 2.5rem;
}

/* ----------------------------------------------------------------- header */

/* Transparent while sitting over the hero photo; the glass panel fades in on
   scroll so the nav never fights the image at the top of the page. */
.site-header {
  position: sticky;
  top: 0;
  z-index: 50;
  background: transparent;
  border-bottom: 1px solid transparent;
  transition: background-color 0.25s ease, border-color 0.25s ease, backdrop-filter 0.25s ease;
}
.site-header[data-scrolled="true"] {
  background: #0A0B0DD9;
  backdrop-filter: blur(14px) saturate(1.1);
  border-bottom-color: var(--line);
}
/* Pages without a photo hero get the solid treatment immediately. */
body[data-hero="none"] .site-header {
  background: #0A0B0DD9;
  backdrop-filter: blur(14px) saturate(1.1);
  border-bottom-color: var(--line);
}
/* The header runs wider than the 68rem content column on purpose: it pushes the
   wordmark out to the left edge and the actions to the right, leaving the
   navigation genuinely centred instead of bunched in the middle third. */
.site-header .shell {
  display: flex;
  align-items: center;
  gap: 1.5rem;
  height: 68px;
  max-width: 96rem;
  padding: 0 2rem;
}
@media (max-width: 1100px) {
  .site-header .shell { padding: 0 1.25rem; gap: 1rem; }
}

/* Brand and actions size to their content; the nav takes everything left over
   and distributes its links across it. Giving the outer zones equal flex looked
   right in theory, but the actions are far wider than the wordmark, so they ate
   into the middle — leaving a large hole after the logo and the last nav item
   pressed up against Discord. */
.brand {
  display: flex;
  align-items: center;
  color: var(--text-strong);
  text-decoration: none;
  flex: 0 0 auto;
}
.brand:hover { text-decoration: none; }

/* The wordmark is white artwork on transparency, so it needs no container —
   just a height. Width follows the natural 1.36:1 aspect. */
.brand-logo {
  width: auto;
  display: block;
  transition: opacity 0.15s ease;
}
.brand-logo--header { height: 46px; }
.brand-logo--footer { height: 60px; }
.brand:hover .brand-logo { opacity: 0.82; }

@media (max-width: 480px) {
  .brand-logo--header { height: 34px; }
}

/* Pill nav, optically centred in the bar. */
/* Fills the gap between the wordmark and the actions, spreading the links
   evenly across it rather than clustering them in the middle. */
.nav {
  display: flex;
  align-items: center;
  justify-content: space-evenly;
  flex: 1 1 auto;
  gap: 0.2rem;
  margin: 0 1.25rem;
}
.nav a {
  font-size: 0.9rem;
  font-weight: 500;
  color: var(--text);
  padding: 0.45rem 1.15rem;
  border-radius: 100px;
  text-decoration: none;
  white-space: nowrap;
  transition: background-color 0.15s ease, color 0.15s ease;
}
@media (max-width: 1440px) {
  /* NINE items is a lot; tighten well before anything is forced to wrap.
     Raised from 1340px when Dispatch joined the nav — at 1366 the full-padding
     nav plus the actions was 11px wider than the viewport. */
  .nav { gap: 0.1rem; }
  .nav a { padding: 0.45rem 0.7rem; font-size: 0.85rem; }
  .nav-sep { margin: 0 0.05rem; }
}
.nav a:hover { background: #FFFFFF14; color: var(--text-strong); text-decoration: none; }
.nav a[aria-current="page"] { background: #FFFFFF1A; color: var(--text-strong); }

/* ------------------------------------------------------- header dropdown */

.hdr-menu { position: relative; display: flex; align-items: center; flex: none; }
/*
 * Given the accent and a glow so it reads as a control rather than an artefact
 * of the logo beside it. The ring is a box-shadow, not a border: a border would
 * add to the 32px box and shift the whole header by a pixel.
 */
.hdr-menu-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  margin-left: 0.55rem;
  padding: 0;
  border: 0;
  border-radius: 100px;
  background: color-mix(in srgb, var(--accent) 16%, transparent);
  color: var(--accent);
  cursor: pointer;
  box-shadow:
    0 0 0 1px color-mix(in srgb, var(--accent) 45%, transparent),
    0 0 14px -3px color-mix(in srgb, var(--accent) 75%, transparent);
  transition: color 0.18s ease, background-color 0.18s ease, box-shadow 0.18s ease;
}
.hdr-menu-btn svg { width: 18px; height: 18px; }
.hdr-menu-btn:hover,
.hdr-menu-btn[aria-expanded="true"] {
  color: var(--text-strong);
  background: color-mix(in srgb, var(--accent) 34%, transparent);
  box-shadow:
    0 0 0 1px color-mix(in srgb, var(--accent) 75%, transparent),
    0 0 20px -2px color-mix(in srgb, var(--accent) 90%, transparent);
}
.hdr-menu-btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.hdr-menu-panel {
  position: absolute;
  top: calc(100% + 0.5rem);
  left: 0;
  z-index: 60;
  min-width: 15rem;
  padding: 0.4rem;
  background: #0F1113F5;
  backdrop-filter: blur(12px);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: 0 18px 40px #00000080;
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
  animation: hdr-pop 0.16s cubic-bezier(0.2, 0.9, 0.3, 1);
}
.hdr-menu-panel[hidden] { display: none; }
@keyframes hdr-pop {
  from { opacity: 0; transform: translateY(-4px) scale(0.98); }
  to   { opacity: 1; transform: none; }
}
.hdr-menu-panel a {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
  padding: 0.55rem 0.7rem;
  border-radius: var(--radius-sm);
  text-decoration: none;
  color: var(--text);
}
.hdr-menu-panel a:hover { background: #FFFFFF14; text-decoration: none; }
.hdr-menu-panel a b { font-size: 0.9rem; font-weight: 600; color: var(--text-strong); }
.hdr-menu-panel a span { font-size: 0.78rem; color: var(--muted); }

/*
 * Management's way into /analytics: an icon beside the wordmark rather than a
 * word in the nav.
 *
 * Deliberately quiet — it is a staff tool, not somewhere visitors are being
 * invited, and the nav is already full. It is present in the DOM only for
 * management, so the empty slot costs nothing for everyone else.
 */
#mgmt-slot { display: flex; align-items: center; flex: none; }
.mgmt-link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  margin-left: 0.55rem;
  border-radius: 100px;
  color: var(--muted);
  opacity: 0.65;
  transition: opacity 0.15s ease, color 0.15s ease, background-color 0.15s ease;
}
.mgmt-link svg { width: 17px; height: 17px; }
.mgmt-link:hover {
  opacity: 1;
  color: var(--text-strong);
  background: #FFFFFF14;
  text-decoration: none;
}
.mgmt-link[aria-current="page"] {
  opacity: 1;
  color: var(--accent);
  background: color-mix(in srgb, var(--accent) 16%, transparent);
}

/* Divider between nav groups. It is a flex item like the links are, so
   space-evenly lands it exactly midway between the two groups it separates
   rather than needing a hand-tuned margin. */
.nav-sep {
  width: 1px;
  height: 16px;
  background: var(--line);
  flex: none;
}
@media (max-width: 1340px) { .nav-sep { height: 14px; } }

/* Right-hand actions: Discord, session chip, and the connect CTA. Mirrors
   `.brand` so both zones weigh the same; contents are pushed to the far edge. */
.nav-actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 0.7rem;
  flex: 0 0 auto;
}
.nav-actions .link {
  font-size: 0.9rem;
  font-weight: 500;
  color: var(--text);
  text-decoration: none;
  white-space: nowrap;
}
/* "Sign in" was breaking across two lines once the nav filled up. */
.nav-actions .btn,
.nav-actions .user-chip { white-space: nowrap; }
.nav-actions .link:hover { color: var(--text-strong); text-decoration: none; }

.nav-toggle {
  display: none;
  background: #FFFFFF14;
  border: 1px solid var(--line);
  color: var(--text);
  width: 40px; height: 36px;
  border-radius: 100px;
  cursor: pointer;
  font-size: 1.1rem;
  line-height: 1;
}

@media (max-width: 1040px) {
  .nav-actions .link { display: none; }
}

/*
 * Collapse to the menu button at 1240px, not 900px.
 *
 * (Raised again from 1040px when Dispatch became the ninth nav item: even with
 * the tightened padding above, nine links plus the header actions overflowed
 * the viewport at 1200px.)
 *
 * Between those two widths the eight links have already been squeezed to their
 * minimum and the header ran off the side of the screen — measured at 1037px of
 * content in a 1000px window. 1040px is also where the Discord link drops out,
 * so the two thresholds now agree instead of leaving a band where the layout is
 * broken. (This predates the grouped nav; the dividers add about 3px.)
 */
@media (max-width: 1240px) {
  .nav-toggle { display: block; order: 3; }
  /* The nav leaves the flow here, so the three-zone split no longer applies —
     drop back to "brand left, everything else pinned right". */
  .brand { flex: none; }
  .nav-actions { order: 2; margin-left: auto; flex: none; }
  .nav {
    position: absolute;
    top: 68px; left: 0; right: 0;
    background: #0F1113F5;
    backdrop-filter: blur(14px);
    border-bottom: 1px solid var(--line);
    flex-direction: column;
    align-items: stretch;
    gap: 0.15rem;
    padding: 0.6rem;
    margin: 0;
    display: none;
    order: 4;
  }
  .nav[data-open="true"] { display: flex; }
  .nav a { padding: 0.75rem 1rem; border-radius: var(--radius-sm); }
  /* Stacked menu: the group divider becomes a rule across the dropdown rather
     than a 1px sliver lying on its side. */
  .nav-sep {
    width: auto;
    height: 1px;
    align-self: stretch;
    margin: 0.4rem 0.6rem;
  }
}

/* ---------------------------------------------------------------- buttons */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  font-family: var(--body);
  font-size: 0.94rem;
  font-weight: 600;
  padding: 0.7rem 1.4rem;
  border-radius: var(--radius-sm);
  border: 1px solid transparent;
  cursor: pointer;
  text-decoration: none;
  transition: background-color 0.15s ease, border-color 0.15s ease, transform 0.1s ease;
  line-height: 1.3;
}
.btn:hover { text-decoration: none; }
.btn:active { transform: translateY(1px); }

/* White primary: on a near-black ground it outranks any coloured button, and it
   keeps red and blue reserved for meaning rather than spending them on chrome. */
.btn--primary {
  background: #FFFFFF;
  color: #0A0B0D;
}
.btn--primary:hover { background: #E4E7EB; color: #0A0B0D; }

.btn--ghost {
  background: transparent;
  border-color: var(--line);
  color: var(--text);
}
.btn--ghost:hover { background: var(--surface-2); border-color: var(--accent-dim); color: var(--text-strong); }

.btn--discord { background: #5865F2; color: #fff; }
.btn--discord:hover { background: #6B77F5; color: #fff; }

.btn[disabled], .btn[aria-disabled="true"] {
  opacity: 0.45;
  pointer-events: none;
}

.btn-row { display: flex; flex-wrap: wrap; gap: 0.75rem; }

/* Inline marks (Discord). Sized in em so they track whatever text they sit in. */
.icon {
  width: 1.05em;
  height: 1.05em;
  flex: none;
  display: inline-block;
  vertical-align: -0.15em;
}
.nav-actions .link,
.footer-links a { display: inline-flex; align-items: center; gap: 0.45rem; }

/* Department badge artwork, straight from the in-game menu. */
.dept-badge {
  width: 44px;
  height: 44px;
  object-fit: contain;
  flex: none;
}
.dept-head {
  display: flex;
  align-items: center;
  gap: 0.9rem;
}
.dept-head .dept-badge--lg { width: 64px; height: 64px; }
.dept-head > div { display: flex; flex-direction: column; gap: 0.15rem; }

/* ------------------------------------------------------------------ cards */

.card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 1.5rem;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}
.card h3 { color: var(--text-strong); }

.grid { display: grid; gap: 1.25rem; }
.grid--3 { grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr)); }
.grid--2 { grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr)); }
/* Narrower minimum so a five-item set lands on one row rather than orphaning
   the last card onto a line of its own. */
.grid--5 { grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr)); }
/* Caps at three across, so a five-card set lands 3 + 2 rather than 4 + orphan. */
.grid--feature { grid-template-columns: repeat(auto-fit, minmax(18.5rem, 1fr)); }

/* A wide chart beside a narrow list. */
.grid--2-1 { grid-template-columns: 2fr 1fr; align-items: start; }
@media (max-width: 900px) {
  .grid--2-1 { grid-template-columns: 1fr; }
}

/* Department cards carry a real dept colour on the top edge */
.dept {
  border-top: 3px solid var(--dept, var(--accent));
  padding-top: calc(1.5rem - 2px);
}
.dept--lspd { --dept: var(--lspd); }
.dept--lssd { --dept: var(--lssd); }
.dept--sahp { --dept: var(--sahp); }
.dept .dept-tag {
  font-family: var(--mono);
  font-size: 0.7rem;
  letter-spacing: 0.16em;
  color: var(--dept);
  text-transform: uppercase;
}

/* -------------------------------------------------------------------- hero */

.hero {
  position: relative;
  padding: 5.5rem 0 4.5rem;
  background:
    radial-gradient(ellipse 70% 100% at 50% 0%, #1A1D2199 0%, transparent 70%),
    linear-gradient(180deg, var(--ground-2) 0%, var(--ground) 100%);
  border-bottom: 1px solid var(--line-soft);
  overflow: hidden;
}
.hero .shell { position: relative; z-index: 1; }
.hero h1 { margin-bottom: 1rem; }
.hero .lede { margin-bottom: 2rem; }

/* Faint scanning light bar across the hero top — the one flourish on the page */
.hero::after {
  content: "";
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 2px;
  /* A light bar: blue running into white running into red. */
  background: linear-gradient(90deg,
    transparent 0%, var(--blue) 22%, var(--blue-soft) 38%,
    #FFFFFF 50%,
    var(--red-soft) 62%, var(--red) 78%, transparent 100%);
  opacity: 0.7;
}

/* ------------------------------------------------------- cinematic hero */

/* Full-bleed photograph behind centred content. The nav sits over this, so the
   hero starts at the very top of the document and pulls up under the header. */
.hero-cine {
  position: relative;
  min-height: 92svh;
  margin-top: -68px;
  padding: 68px 0 4rem;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  isolation: isolate;
  border-bottom: 1px solid var(--line-soft);
}
@supports not (min-height: 92svh) {
  .hero-cine { min-height: 92vh; }
}

.hero-media {
  position: absolute;
  inset: 0;
  z-index: -2;
}
/* The hero is a looping aerial of the skyline at night. The `video` element
   renders its own poster frame, so this styles both the still and the moving
   picture at once and they cannot drift apart. */
.hero-media video {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* MUCH lighter than the daylight photo this replaced. That one needed
     brightness(0.78) and a 2px blur to stop a bright, busy screenshot fighting
     the headline — applied to night footage the same numbers crush it to a
     black rectangle once the scrim lands on top. The footage is already dark
     and already soft, so all it needs is a pull toward the site's grey palette
     and a touch of contrast to keep the skyline from going muddy. */
  filter: saturate(0.62) contrast(1.06) brightness(1.22);
}

/* Layered navy scrim: keeps the middle of the picture readable, sinks the
   edges, and hands off to the page background at the bottom with no visible
   seam.

   RETUNED for the night video. These three layers stack, so their opacities
   compound in the centre — at the values the daylight photo needed (0.68 /
   0.10 / 0.30) the skyline underneath went practically black and the hero read
   as a plain dark rectangle with type on it. Night footage needs a fraction of
   that: enough under the headline to hold contrast, and the vignette kept
   strong at the edges where it does the real work of sinking the frame into
   the page. If this is ever pointed back at a bright image, put the old
   numbers back rather than leaving these. */
.hero-scrim {
  position: absolute;
  inset: 0;
  z-index: -1;
  background:
    radial-gradient(ellipse 58% 44% at 50% 44%, rgba(10, 11, 13, 0.40) 0%, rgba(10, 11, 13, 0.26) 55%, rgba(10, 11, 13, 0.04) 100%),
    radial-gradient(ellipse 95% 75% at 50% 48%, rgba(10, 11, 13, 0.00) 0%, rgba(10, 11, 13, 0.34) 64%, rgba(10, 11, 13, 0.82) 100%),
    linear-gradient(180deg, rgba(10, 11, 13, 0.78) 0%, rgba(10, 11, 13, 0.10) 34%, rgba(10, 11, 13, 0.72) 84%, var(--ground) 100%);
}
/*
 * Blend the hero into the page backdrop.
 *
 * Without this the hero paints down to solid --ground and stops dead on a 1px
 * border, and the photograph behind the page starts abruptly on the next
 * pixel — a hard horizontal seam right where the eye lands after the fold.
 *
 * Masking BOTH the video and its scrim fades everything the hero paints over
 * its last third, so what emerges underneath is the fixed backdrop: the video
 * genuinely dissolves into the photograph rather than cutting to it. Because
 * the backdrop is fixed and the hero scrolls, the dissolve travels up the
 * frame as you scroll, which is what sells it.
 *
 * Scoped to backdrop pages: with no photograph behind it, fading the hero out
 * would just reveal flat --ground and lose the scrim that keeps the headline
 * readable.
 */
body[data-backdrop] .hero-media,
body[data-backdrop] .hero-scrim {
  -webkit-mask-image: linear-gradient(180deg,
    #000 0%, #000 58%, rgba(0, 0, 0, 0.5) 82%, transparent 100%);
  mask-image: linear-gradient(180deg,
    #000 0%, #000 58%, rgba(0, 0, 0, 0.5) 82%, transparent 100%);
}
/* The border is the seam itself — there is nothing to divide once the two
   pictures are blended into each other. */
body[data-backdrop] .hero-cine { border-bottom: none; }

/* The same dept-coloured light bar, now across the top of the photo. */
.hero-cine::before {
  content: "";
  position: absolute;
  top: 68px; left: 0; right: 0;
  height: 2px;
  z-index: 1;
  background: linear-gradient(90deg,
    transparent 0%, var(--blue) 22%, var(--blue-soft) 38%,
    #FFFFFF 50%,
    var(--red-soft) 62%, var(--red) 78%, transparent 100%);
  opacity: 0.6;
}

.hero-inner {
  position: relative;
  z-index: 2;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 1.4rem;
}

/* Live player count, straight from the game server. */
.live-pill {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.45rem 1.1rem;
  border-radius: 100px;
  background: rgba(255, 255, 255, 0.07);
  border: 1px solid rgba(255, 255, 255, 0.14);
  backdrop-filter: blur(10px);
  font-family: var(--mono);
  font-size: 0.8rem;
  letter-spacing: 0.06em;
  color: var(--text);
}
.live-pill .dot {
  width: 8px; height: 8px; border-radius: 50%;
  background: var(--muted);
  flex: none;
}
.live-pill[data-state="online"] .dot {
  background: var(--good);
  box-shadow: 0 0 0 4px rgba(63, 191, 127, 0.18);
}
.live-pill[data-state="offline"] .dot { background: var(--bad); }
.live-pill b { color: var(--text-strong); font-weight: 600; font-variant-numeric: tabular-nums; }

.hero-cine h1 {
  font-size: clamp(2.9rem, 8vw, 5.75rem);
  line-height: 0.98;
  letter-spacing: -0.01em;
  margin: 0;
  max-width: 16ch;
}
/* Second line recedes — the name reads as one object, not two lines of shouting. */
.hero-cine h1 .dim {
  display: block;
  color: var(--text);
  opacity: 0.42;
}

.hero-sub {
  font-size: clamp(1.05rem, 1.6vw, 1.25rem);
  color: var(--text);
  opacity: 0.86;
  max-width: 46ch;
  margin: 0;
  text-wrap: balance;
}

.hero-cta { display: flex; flex-wrap: wrap; gap: 0.75rem; justify-content: center; align-items: center; }

/* Glass secondary — reads as a real control against a photograph, where a flat
   outline button would disappear. */
.btn--glass {
  background: rgba(255, 255, 255, 0.09);
  border-color: rgba(255, 255, 255, 0.18);
  color: var(--text-strong);
  backdrop-filter: blur(10px);
}
.btn--glass:hover {
  background: rgba(255, 255, 255, 0.15);
  border-color: rgba(255, 255, 255, 0.28);
  color: var(--text-strong);
}

.btn--lg { padding: 0.85rem 1.7rem; font-size: 1rem; }

.hero-tertiary {
  font-size: 0.9rem;
  color: var(--muted);
  text-decoration: none;
  padding: 0.5rem 0.25rem;
}
.hero-tertiary:hover { color: var(--accent-soft); text-decoration: none; }

/* Scroll hint at the base of the hero. */
.hero-scroll {
  position: absolute;
  bottom: 1.75rem;
  left: 50%;
  transform: translateX(-50%);
  z-index: 2;
  color: var(--muted);
  font-family: var(--mono);
  font-size: 0.65rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.4rem;
  animation: hero-bob 2.6s ease-in-out infinite;
}
.hero-scroll span { width: 1px; height: 24px; background: linear-gradient(180deg, var(--muted), transparent); }
@keyframes hero-bob {
  0%, 100% { transform: translateX(-50%) translateY(0); opacity: 0.7; }
  50%      { transform: translateX(-50%) translateY(5px); opacity: 1; }
}
@media (max-height: 700px) {
  .hero-scroll { display: none; }
}

/* --------------------------------------------------------------- statlines */

.statline {
  display: flex;
  flex-wrap: wrap;
  gap: 0 2.5rem;
  padding: 1rem 0 0;
  border-top: 1px solid var(--line-soft);
  margin-top: 2.5rem;
}
.stat { display: flex; flex-direction: column; gap: 0.1rem; }
.stat .n {
  font-family: var(--display);
  font-size: 1.7rem;
  font-weight: 700;
  color: var(--text-strong);
  font-variant-numeric: tabular-nums;
  line-height: 1.1;
}
.stat .l {
  font-family: var(--mono);
  font-size: 0.68rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
}

/* The six badges tucked under the departments count. Muted by default so they
   read as a footnote to the number rather than competing with it. */
.stat-badges {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  margin-top: 0.55rem;
  text-decoration: none;
}
.stat-badges img {
  width: 24px;
  height: 24px;
  object-fit: contain;
  opacity: 0.55;
  transition: opacity 0.15s ease, transform 0.15s ease;
}
.stat-badges .badge-sep {
  width: 1px;
  height: 15px;
  background: var(--line);
  margin: 0 0.2rem;
  flex: none;
}
.stat-badges:hover img { opacity: 0.95; }
.stat-badges img:hover { transform: translateY(-2px); opacity: 1; }
.stat-badges:hover { text-decoration: none; }

@media (max-width: 520px) {
  .stat-badges img { width: 20px; height: 20px; }
}

/* Gold treatment for the rank milestone — it's the one number on the strip that
   represents something earned, so it gets the brass. Solid colour first, so a
   browser without background-clip:text still shows the number rather than
   nothing at all. */
.stat--gold .n { color: #E8C252; }
.stat--gold .l { color: #A88A3F; }

@supports ((-webkit-background-clip: text) or (background-clip: text)) {
  .stat--gold .n {
    background: linear-gradient(172deg,
      #FDF3CB 0%,
      #F1C40F 34%,
      #D9A521 58%,
      #A8791C 100%);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    -webkit-text-fill-color: transparent;
  }
}

/* Live server status pill */
.status-pill {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  font-family: var(--mono);
  font-size: 0.75rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: 100px;
  padding: 0.35rem 0.9rem;
  color: var(--muted);
}
.status-dot {
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--muted);
  flex: none;
}
.status-pill[data-state="online"] { color: var(--good); border-color: #3FBF7F40; }
.status-pill[data-state="online"] .status-dot { background: var(--good); box-shadow: 0 0 0 3px #3FBF7F25; }
.status-pill[data-state="offline"] { color: var(--bad); border-color: #E06C6040; }
.status-pill[data-state="offline"] .status-dot { background: var(--bad); }

/* ------------------------------------------------------------------ store */

.product {
  background: linear-gradient(150deg, var(--surface) 0%, var(--surface-2) 100%);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
  display: grid;
  grid-template-columns: 1.15fr 1fr;
}
@media (max-width: 820px) { .product { grid-template-columns: 1fr; } }

.product-body { padding: 2rem; display: flex; flex-direction: column; gap: 1rem; }
.product-buy {
  padding: 2rem;
  background: #0C0E10;
  border-left: 1px solid var(--line);
  display: flex;
  flex-direction: column;
  gap: 1rem;
}
@media (max-width: 820px) {
  .product-buy { border-left: none; border-top: 1px solid var(--line); }
}

.price {
  display: flex;
  align-items: baseline;
  gap: 0.4rem;
}
.price .amt {
  font-family: var(--display);
  font-size: 3rem;
  font-weight: 700;
  color: var(--text-strong);
  line-height: 1;
  font-variant-numeric: tabular-nums;
}
.price .per { color: var(--muted); font-size: 0.95rem; }

.feature-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.85rem; }
.feature-list li { display: flex; gap: 0.75rem; align-items: flex-start; }
.feature-list .fi { flex: none; width: 1.4rem; text-align: center; font-size: 1rem; line-height: 1.5; }
.feature-list b { color: var(--text-strong); font-weight: 600; display: block; }
.feature-list span { color: var(--muted); font-size: 0.9rem; line-height: 1.5; }

.pack-card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
  display: flex;
  flex-direction: column;
}
.pack-card img { aspect-ratio: 16 / 9; object-fit: cover; width: 100%; }
.pack-card .pc-body { padding: 1.15rem; display: flex; flex-direction: column; gap: 0.5rem; flex: 1; }
.pack-card .pc-price {
  font-family: var(--display);
  font-size: 1.5rem;
  font-weight: 700;
  color: var(--text-strong);
  font-variant-numeric: tabular-nums;
}
.pack-card .pc-foot { margin-top: auto; padding-top: 0.75rem; }

/* ------------------------------------------------------------------ staff */

.staff-group { margin-bottom: 3rem; }
.staff-group:last-child { margin-bottom: 0; }

/* Coloured rule + pill, so each tier announces itself before you read a name. */
.staff-head {
  border-left: 3px solid var(--tier, var(--accent));
  padding: 0.25rem 0 0.25rem 1.25rem;
  margin-bottom: 1.5rem;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}
/* --tier is set inline from the live Discord role colour; this is the fallback
   for a role with no colour set. */
.staff-group { --tier: var(--blue); }

.tier-pill {
  align-self: flex-start;
  font-family: var(--mono);
  font-size: 0.66rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--tier, var(--accent));
  border: 1px solid var(--tier, var(--accent));
  border-radius: 100px;
  padding: 0.25rem 0.8rem;
  opacity: 0.9;
}
.staff-head h2 { margin: 0; }
.staff-head p { margin: 0; color: var(--muted); font-size: 0.925rem; }

.staff-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(15.5rem, 1fr));
  gap: 1rem;
}

/*
 * Hovering a member lights their card in THEIR tier's colour.
 *
 * --tier is the live Discord role colour, set inline per group, so a Founder
 * glows in exactly the shade the role is set to in Discord — recolour the role
 * there and this follows, with nothing to update here.
 */
.staff-card {
  position: relative;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 0.9rem;
  display: flex;
  align-items: center;
  gap: 0.9rem;
  transition:
    border-color 0.25s ease,
    background-color 0.25s ease,
    box-shadow 0.3s ease,
    transform 0.3s cubic-bezier(0.16, 0.84, 0.44, 1);
}

/*
 * A spotlight in the tier's colour that follows the cursor across the card.
 *
 * --mx/--my are written by site.js on pointermove, once per animation frame.
 * They default to the centre so the card still looks right before the pointer
 * has moved, and on touch where there may be no pointer at all.
 *
 * Only `opacity` transitions — the position is updated every frame, which IS
 * the movement. Transitioning the gradient itself would smear it behind the
 * cursor instead of tracking it.
 */
.staff-card::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 0;
  border-radius: inherit;
  background: radial-gradient(
    circle 190px at var(--mx, 50%) var(--my, 50%),
    color-mix(in srgb, var(--tier, var(--accent)) 30%, transparent),
    color-mix(in srgb, var(--tier, var(--accent)) 8%, transparent) 45%,
    transparent 72%
  );
  opacity: 0;
  transition: opacity 0.25s ease;
  pointer-events: none;
}
.staff-card > * { position: relative; z-index: 1; }

.staff-card:hover {
  border-color: var(--tier, var(--accent));
  background: var(--surface-2);
  transform: translateY(-3px);
  /* Three layers: a crisp rim, a lifted drop shadow, and the bloom itself. */
  box-shadow:
    0 0 0 1px color-mix(in srgb, var(--tier, var(--accent)) 45%, transparent),
    0 10px 26px -8px color-mix(in srgb, var(--tier, var(--accent)) 45%, transparent),
    0 0 26px -4px color-mix(in srgb, var(--tier, var(--accent)) 32%, transparent);
}
.staff-card:hover::before { opacity: 1; }

.staff-card img {
  width: 52px;
  height: 52px;
  border-radius: 9px;
  flex: none;
  object-fit: cover;
  background: var(--surface-2);
  box-shadow: 0 0 0 1px var(--line);
  transition: box-shadow 0.3s ease, transform 0.3s cubic-bezier(0.16, 0.84, 0.44, 1);
}
.staff-card:hover img {
  transform: scale(1.05);
  box-shadow:
    0 0 0 2px color-mix(in srgb, var(--tier, var(--accent)) 75%, transparent),
    0 0 18px -3px color-mix(in srgb, var(--tier, var(--accent)) 60%, transparent);
}
.staff-card .who { display: flex; flex-direction: column; gap: 0.1rem; min-width: 0; }
.staff-card .nm {
  font-weight: 600;
  color: var(--text-strong);
  font-size: 0.98rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.staff-card .rl {
  font-size: 0.82rem;
  color: var(--tier, var(--accent));
  font-weight: 500;
}
.staff-card .hd {
  font-family: var(--mono);
  font-size: 0.72rem;
  color: var(--muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The name takes the tier's colour on hover, and both it and the role label
   pick up a matching halo. */
.staff-card .nm { transition: color 0.25s ease, text-shadow 0.3s ease; }
.staff-card:hover .nm {
  color: var(--tier, var(--text-strong));
  text-shadow: 0 0 16px color-mix(in srgb, var(--tier, var(--accent)) 55%, transparent);
}
.staff-card .rl { transition: text-shadow 0.3s ease; }
.staff-card:hover .rl {
  text-shadow: 0 0 14px color-mix(in srgb, var(--tier, var(--accent)) 50%, transparent);
}
.staff-card .hd { transition: color 0.25s ease; }
.staff-card:hover .hd { color: var(--text); }

/* ----------------------------------------------------------- leaderboards */

.lb-card { gap: 0.6rem; }

.lb-tabs { display: flex; gap: 0.25rem; margin: 0.25rem 0 0.5rem; flex-wrap: wrap; }
.lb-tab {
  font-family: var(--mono);
  font-size: 0.66rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
  background: transparent;
  border: 1px solid var(--line);
  border-radius: 100px;
  padding: 0.28rem 0.7rem;
  cursor: pointer;
  transition: color 0.15s ease, border-color 0.15s ease, background-color 0.15s ease;
}
.lb-tab:hover { color: var(--text); border-color: var(--accent-dim); }
.lb-tab.is-on {
  color: var(--text-strong);
  border-color: var(--accent);
  background: #4C8DF61F;
}

.lb { list-style: none; margin: 0; padding: 0; counter-reset: lbpos; display: flex; flex-direction: column; }
.lb li {
  display: grid;
  grid-template-columns: 1.6rem 1fr auto;
  align-items: baseline;
  gap: 0.65rem;
  padding: 0.42rem 0;
  border-bottom: 1px solid var(--line-soft);
  font-size: 0.9rem;
}
/* --- board search + scrolling ------------------------------------------- */

/*
 * The leaderboard carries ~12,000 DOM nodes across 22 cards. Skipping layout
 * and paint for cards that are off screen keeps scrolling and the page
 * transition smooth; the intrinsic size stops the scrollbar jumping as cards
 * come into view. Content stays in the DOM, so search still reaches it.
 */
.lb-card {
  content-visibility: auto;
  contain-intrinsic-size: auto 30rem;
}

.lb-search { margin: 0 0 0.85rem; }
.lb-search-input {
  width: 100%;
  box-sizing: border-box;
  background: #FFFFFF0A;
  border: 1px solid var(--line);
  border-radius: 100px;
  color: var(--text-strong);
  font-family: var(--body);
  font-size: 0.84rem;
  padding: 0.45rem 0.95rem;
  transition: border-color 0.15s ease, background-color 0.15s ease;
}
.lb-search-input::placeholder { color: var(--muted); }
.lb-search-input:focus {
  outline: none;
  background: #FFFFFF12;
  border-color: #FFFFFF33;
}
/* Edge and Chrome draw their own clear button; ours is the Escape key. */
.lb-search-input::-webkit-search-cancel-button { cursor: pointer; }

/* The whole board is rendered, so the list itself scrolls. */
.lb-panel {
  max-height: 26rem;
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: thin;
  scrollbar-color: #FFFFFF26 transparent;
}
.lb-panel::-webkit-scrollbar { width: 8px; }
.lb-panel::-webkit-scrollbar-track { background: transparent; }
.lb-panel::-webkit-scrollbar-thumb {
  background: #FFFFFF1F;
  border-radius: 100px;
  border: 2px solid transparent;
  background-clip: content-box;
}
.lb-panel::-webkit-scrollbar-thumb:hover { background: #FFFFFF33; background-clip: content-box; }

.lb-none { margin: 0.75rem 0 0; }

/* Switching a card's tab fades the new list in rather than snapping it.
   Opacity only — a transform here would make the whole grid twitch on load,
   when every card's default panel animates at once. */
.lb-panel:not(.hide) { animation: panel-in 0.2s ease both; }
@keyframes panel-in { from { opacity: 0; } }

/* Rows carrying a department split need a second line under the name. */
.lb li.has-sub { padding-bottom: 0.55rem; }
.lb-sub {
  grid-column: 2 / -1;
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem;
  margin-top: 0.25rem;
}
.lb-dept {
  font-family: var(--mono);
  font-size: 0.66rem;
  letter-spacing: 0.04em;
  color: var(--muted);
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: 100px;
  padding: 0.1rem 0.45rem;
  white-space: nowrap;
}
/* The department a player has actually spent the most time in leads the list,
   so colouring the badges makes their home agency readable at a glance. */
.lb-dept--lspd { color: var(--lspd); border-color: #3498DB44; }
.lb-dept--lssd { color: var(--lssd); border-color: #F1C40F44; }
.lb-dept--sahp { color: var(--sahp); border-color: #9B976D55; }
.lb-dept--fib,
.lb-dept--usms,
.lb-dept--usss { color: var(--text); border-color: var(--line); }

/* --------------------------------------------------- live duty scoreboard */

/* One row per officer: level, who they are, duty state, ping. The column
   template is shared by the header and every row so they stay aligned. */
.sb {
  --sb-cols: 3.25rem 34px minmax(0, 1fr) 7.5rem 4.5rem;
  display: flex;
  flex-direction: column;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
  background: #0F111380;
}

.sb-head {
  display: grid;
  grid-template-columns: var(--sb-cols);
  align-items: center;
  gap: 0.9rem;
  padding: 0.6rem 1rem;
  background: #FFFFFF08;
  border-bottom: 1px solid var(--line);
  font-family: var(--mono);
  font-size: 0.66rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted);
}
.sb-h-lvl { text-align: center; }
.sb-h-duty,
.sb-h-ping { text-align: right; }
/* Spacer so the header labels line up with the rows' avatar column. */
.sb-h-pfp { display: block; }

.sb-row {
  /* The department colour lands here; unaffiliated players fall back to a
     neutral edge rather than an arbitrary hue. */
  --row: #4A5058;
  display: grid;
  grid-template-columns: var(--sb-cols);
  align-items: center;
  gap: 0.9rem;
  width: 100%;
  padding: 0.6rem 1rem;
  border: none;
  border-bottom: 1px solid var(--line-soft);
  border-left: 3px solid var(--row);
  background: transparent;
  color: inherit;
  font: inherit;
  text-align: left;
}
.sb-row:last-child { border-bottom: none; }

/*
 * Hover.
 *
 * Everything animates on TRANSFORM and colour only — never on width, padding or
 * border-width — so a row lifting cannot nudge the rows around it. The row is
 * promoted above its neighbours while raised, or the one below would clip the
 * shadow.
 *
 * The lift is reserved for rows that are actually clickable (staff get a
 * <button>, everyone else a <div>). Making an inert row leap under the cursor
 * promises a click that will not happen; it still lights up, just without the
 * physical cue.
 */
.sb-row {
  position: relative;
  transform-origin: left center;
  transition:
    transform 0.24s cubic-bezier(0.22, 0.9, 0.3, 1),
    background-color 0.24s ease,
    box-shadow 0.24s ease;
}
.sb-row .sb-name,
.sb-row .sb-lvl,
.sb-row .sb-pfp {
  transition:
    transform 0.24s cubic-bezier(0.22, 0.9, 0.3, 1),
    color 0.2s ease,
    background-color 0.2s ease,
    border-color 0.2s ease,
    box-shadow 0.24s ease;
}

/* The department stripe, brightened from behind rather than widened. */
.sb-row::before {
  content: '';
  position: absolute;
  inset: 0 auto 0 -3px;
  width: 3px;
  background: var(--row);
  opacity: 0;
  transition: opacity 0.24s ease, box-shadow 0.24s ease;
  pointer-events: none;
}
.sb-row:hover::before {
  opacity: 1;
  box-shadow: 0 0 14px 1px var(--row);
}

/* Non-clickable: light up, stay put. */
.sb-row:hover { background: #FFFFFF0A; }
.sb-row:hover .sb-name { color: var(--text-strong); }

/* Clickable: the full lift. */
.sb-row.is-clickable { cursor: pointer; }
.sb-row.is-clickable:hover {
  z-index: 2;
  background: #FFFFFF14;
  /* Small numbers, but on an 88px row they read clearly. Anything larger and
     running the cursor down the list makes the whole page seem to wobble. */
  transform: translateX(7px) scale(1.013);
  box-shadow:
    0 16px 34px -12px #000000B3,
    0 0 0 1px color-mix(in srgb, var(--row) 45%, transparent);
}
.sb-row.is-clickable:hover .sb-pfp {
  transform: scale(1.12);
  border-color: color-mix(in srgb, var(--row) 70%, transparent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--row) 22%, transparent);
}
.sb-row.is-clickable:hover .sb-lvl {
  transform: scale(1.06);
  background: #FFFFFF1F;
}
/* Settle back down on press, so a click feels like one. */
.sb-row.is-clickable:active { transform: translateX(7px) scale(0.998); }
.sb-row.is-clickable:focus-visible {
  z-index: 2;
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

.sb-lvl {
  justify-self: center;
  min-width: 2.4rem;
  padding: 0.2rem 0.4rem;
  border-radius: var(--radius-sm);
  background: #FFFFFF12;
  font-family: var(--mono);
  font-size: 0.82rem;
  font-variant-numeric: tabular-nums;
  text-align: center;
  color: var(--text-strong);
}
.sb-lvl--none { color: var(--muted); background: #FFFFFF08; }

/* Discord picture. Proxied through our own origin, so a failed load just
   leaves the fallback initial rather than a broken-image icon. */
.sb-pfp {
  width: 34px;
  height: 34px;
  border-radius: 50%;
  object-fit: cover;
  background: #FFFFFF12;
  border: 1px solid var(--line);
  flex: none;
}
.sb-pfp--none {
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--mono);
  font-size: 0.82rem;
  color: var(--muted);
}

.sb-who { display: flex; flex-direction: column; gap: 0.1rem; min-width: 0; }
.sb-name {
  display: flex;
  align-items: baseline;
  gap: 0.45rem;
  font-size: 0.92rem;
  font-weight: 500;
  color: var(--text-strong);
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Server id: what staff type into in-game commands. */
.sb-slot {
  font-family: var(--mono);
  font-size: 0.66rem;
  font-variant-numeric: tabular-nums;
  color: var(--muted);
  background: #FFFFFF0D;
  border-radius: 100px;
  padding: 0.05rem 0.4rem;
  flex: none;
}

/* The in-game name, when it differs from the Discord one — which is most of
   the time, and is exactly why both are shown. */
.sb-alias {
  font-size: 0.76rem;
  color: var(--muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.sb-tags { display: flex; flex-wrap: wrap; align-items: center; gap: 0.35rem; }

.sb-dept,
.sb-rank,
.sb-staff {
  font-family: var(--mono);
  font-size: 0.62rem;
  letter-spacing: 0.08em;
  padding: 0.1rem 0.4rem;
  border-radius: 100px;
  white-space: nowrap;
}
.sb-dept { color: var(--row); border: 1px solid var(--row); }
.sb-rank { color: var(--muted); border: 1px solid var(--line); }
.sb-staff {
  --staff: var(--accent);
  color: var(--staff);
  border: 1px solid var(--staff);
}

.sb-duty {
  display: inline-flex;
  align-items: center;
  justify-content: flex-end;
  gap: 0.4rem;
  font-size: 0.76rem;
  color: var(--muted);
  white-space: nowrap;
}
.sb-duty i {
  width: 7px; height: 7px;
  border-radius: 50%;
  background: #565C66;
  flex: none;
}
.sb-duty.is-on { color: var(--good); }
.sb-duty.is-on i {
  background: var(--good);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--good) 22%, transparent);
}

.sb-ping {
  justify-self: end;
  font-family: var(--mono);
  font-size: 0.78rem;
  font-variant-numeric: tabular-nums;
  color: var(--muted);
  white-space: nowrap;
}
.sb-ping em { font-style: normal; font-size: 0.62rem; opacity: 0.6; margin-left: 0.1rem; }
.sb-ping.is-good { color: var(--good); }
.sb-ping.is-ok { color: var(--warn); }
.sb-ping.is-bad { color: var(--bad, #D9534F); }

/* XP grants are separated from the punishments by a rule, so "Max level" is
   never the button next to "Ban" that a slipped cursor finds. */
.mod-row--grant {
  margin-top: 0.6rem;
  padding-top: 0.7rem;
  border-top: 1px solid var(--line);
}
.mod-xp {
  width: 7rem;
  padding: 0.4rem 0.6rem;
  background: #FFFFFF0A;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  color: var(--text-strong);
  font-family: var(--mono);
  font-size: 0.85rem;
}
.mod-xp:focus {
  outline: none;
  border-color: color-mix(in srgb, var(--accent) 60%, transparent);
}
.mod-btn--grant {
  background: color-mix(in srgb, var(--good) 18%, transparent);
  border: 1px solid color-mix(in srgb, var(--good) 55%, transparent);
  color: var(--good);
}
.mod-btn--grant:hover {
  background: color-mix(in srgb, var(--good) 28%, transparent);
  color: var(--text-strong);
}

.sb-foot { margin: 0.85rem 0 0; }

/* Narrow screens drop the duty column's label width and the ping header. */
@media (max-width: 640px) {
  .sb { --sb-cols: 2.5rem 30px minmax(0, 1fr) 5.5rem; }
  .sb-h-ping, .sb-ping { display: none; }
  .sb-pfp { width: 30px; height: 30px; }
  .sb-row, .sb-head { gap: 0.55rem; padding-left: 0.7rem; padding-right: 0.7rem; }
}

/* --- department standings: server-wide totals, not a player ranking ------ */

.ds { list-style: none; margin: 0; padding: 0; }
.ds-row {
  --dept: var(--text);
  display: grid;
  grid-template-columns: 1.25rem 32px 1fr auto;
  align-items: center;
  gap: 0.15rem 0.7rem;
  padding: 0.75rem 0;
  border-bottom: 1px solid var(--line);
}
.ds-row:first-child { padding-top: 0.25rem; }
.ds-row:last-child { border-bottom: none; padding-bottom: 0.25rem; }
.ds-row--lspd { --dept: var(--lspd); }
.ds-row--lssd { --dept: var(--lssd); }
.ds-row--sahp { --dept: var(--sahp); }

.ds-pos { font-family: var(--mono); font-size: 0.72rem; color: var(--muted); }
.ds-row .ds-badge { width: 32px; height: 32px; }

/* Same chart for things that aren't departments: no badge, so the name column
   starts one track earlier and the bar follows it. */
.ds-row--plain {
  grid-template-columns: 1.25rem 1fr auto;
  --dept: var(--accent);
}
.ds-row--plain .ds-bar { grid-column: 2 / -1; }
.ds-row--plain .ds-who b {
  font-family: var(--body);
  font-size: 0.88rem;
  letter-spacing: 0;
  color: var(--text-strong);
}

.ds-who { display: flex; flex-direction: column; gap: 0.1rem; min-width: 0; }
.ds-who b {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-family: var(--mono);
  font-size: 0.8rem;
  letter-spacing: 0.1em;
  color: var(--dept);
}
.ds-who em {
  font-style: normal;
  font-size: 0.78rem;
  color: var(--muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ds-lead {
  font-size: 0.58rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text);
  background: #FFFFFF14;
  border-radius: 100px;
  padding: 0.12rem 0.5rem;
  white-space: nowrap;
}

.ds-val { display: flex; flex-direction: column; align-items: flex-end; gap: 0.1rem; }
.ds-val b { font-size: 0.95rem; color: var(--text-strong); }
.ds-val em {
  font-style: normal;
  font-family: var(--mono);
  font-size: 0.66rem;
  color: var(--muted);
  white-space: nowrap;
}

/* Second row of the same grid, under the name and figure. */
.ds-bar {
  grid-column: 3 / -1;
  height: 4px;
  border-radius: 100px;
  background: #FFFFFF12;
  overflow: hidden;
}
.ds-bar i {
  display: block;
  height: 100%;
  border-radius: 100px;
  background: var(--dept);
  opacity: 0.85;
}
.lb li:last-child { border-bottom: none; }
.lb-pos {
  font-family: var(--mono);
  font-size: 0.72rem;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}
/* The podium gets the brass, same language as the rank stat on the homepage. */
.lb li:nth-child(1) .lb-pos { color: #E8C252; }
.lb li:nth-child(2) .lb-pos { color: #C9CDD4; }
.lb li:nth-child(3) .lb-pos { color: #C08457; }
.lb-name {
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.lb li:nth-child(1) .lb-name { color: var(--text-strong); font-weight: 600; }
.lb-val {
  font-family: var(--mono);
  font-size: 0.8rem;
  color: var(--accent);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* -------------------------------------------------------- live player list */

.live-grid { display: flex; flex-wrap: wrap; gap: 0.5rem; }
/*
 * Cfx.re platform status, sitting under the live player pill.
 *
 * Deliberately quieter than the pill above it: the player count is about US and
 * is the reason someone is on the page, while this is about the platform
 * underneath and only matters when it is bad. Green states should be almost
 * unnoticeable; a degraded one earns its colour.
 */
.cfx-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  margin-top: 0.5rem;
  padding: 0.24rem 0.7rem;
  border-radius: 100px;
  border: 1px solid var(--line);
  background: #FFFFFF08;
  font-size: 0.72rem;
  color: var(--muted);
  text-decoration: none;
  max-width: 100%;
  transition: color 0.15s ease, border-color 0.15s ease, background-color 0.15s ease;
}
.cfx-chip:hover {
  color: var(--text);
  background: #FFFFFF14;
  text-decoration: none;
}
.cfx-chip .dot {
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: currentColor;
  flex: none;
}
.cfx-chip[data-state='good'] .dot { background: var(--good); }
.cfx-chip[data-state='warn'] {
  color: var(--warn);
  border-color: color-mix(in srgb, var(--warn) 45%, transparent);
}
.cfx-chip[data-state='warn'] .dot { background: var(--warn); }
.cfx-chip[data-state='bad'] {
  color: var(--bad);
  border-color: color-mix(in srgb, var(--bad) 55%, transparent);
  background: color-mix(in srgb, var(--bad) 12%, transparent);
}
.cfx-chip[data-state='bad'] .dot { background: var(--bad); }

.live-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: 100px;
  padding: 0.32rem 0.85rem;
  font-size: 0.85rem;
  color: var(--text);
  max-width: 100%;
}
.live-chip .dot {
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--good);
  box-shadow: 0 0 0 3px #3FB27A22;
  flex: none;
}
.live-chip .nm { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.live-chip .pg {
  font-family: var(--mono);
  font-size: 0.68rem;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

/* ------------------------------------------------- staff player lookup */

.live-chip.is-clickable {
  cursor: pointer;
  font: inherit;
  font-size: 0.85rem;
  transition: border-color 0.15s ease, background-color 0.15s ease;
}
.live-chip.is-clickable:hover {
  border-color: var(--accent);
  background: #4C8DF61A;
}

/* Player details open as a centred overlay. As an inline panel under the
   roster it could open well below the fold on a busy server, so a staff member
   clicked a name and appeared to get nothing. */
.pd-overlay {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.5rem;
  background: #05070AC7;
  backdrop-filter: blur(6px);
  animation: pd-fade 0.16s ease;
}
.pd-dialog {
  width: min(34rem, 100%);
  /* The dialog scrolls, not the page behind it. */
  max-height: min(82vh, 46rem);
  overflow-y: auto;
  overscroll-behavior: contain;
  background: var(--ground-2);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 1.15rem 1.25rem;
  box-shadow: 0 26px 70px #00000090;
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  animation: pd-pop 0.38s both;
  transform-origin: center center;
}
.pd-dialog:focus { outline: none; }

@keyframes pd-fade { from { opacity: 0; } }

/*
 * It grows. Starts at HALF size and scales up to full, overshooting slightly
 * before it settles.
 *
 * The previous version started at 0.82 and also slid up 14px — only a fifth
 * smaller, with vertical movement competing for attention, so it read as
 * "drifted into place" rather than "popped". There is no translate here at all:
 * scale alone is what makes something look like it grew.
 */
@keyframes pd-pop {
  0% {
    opacity: 0;
    transform: scale(0.5);
    animation-timing-function: cubic-bezier(0.2, 0.9, 0.3, 1);
  }
  55% {
    opacity: 1;
    transform: scale(1.045);
    animation-timing-function: cubic-bezier(0.4, 0, 0.45, 1);
  }
  78% {
    transform: scale(0.983);
    animation-timing-function: ease-out;
  }
  100% { opacity: 1; transform: scale(1); }
}

/*
 * The contents fade in just behind the dialog, a few milliseconds apart, so the
 * panel doesn't read as one flat slab arriving.
 *
 * Kept short and tight to the parent: while the panel is growing from half
 * size, a long cascade underneath it only looks busy. Opacity only for the same
 * reason — the parent's scale is the movement, and anything else competes.
 */
.pd-dialog > * { animation: pd-item 0.24s cubic-bezier(0.16, 0.84, 0.44, 1) both; }
.pd-dialog > *:nth-child(1) { animation-delay: 0.06s; }
.pd-dialog > *:nth-child(2) { animation-delay: 0.1s; }
.pd-dialog > *:nth-child(3) { animation-delay: 0.14s; }
.pd-dialog > *:nth-child(4) { animation-delay: 0.17s; }
.pd-dialog > *:nth-child(n + 5) { animation-delay: 0.2s; }

@keyframes pd-item {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* Closing is driven by a class rather than by removing the element, so the
   dialog can animate out instead of vanishing. The JS hides it once this has
   finished — see closeDetail() in stats.js. */
.pd-overlay.is-closing { animation: pd-fade-out 0.14s ease both; }
.pd-overlay.is-closing .pd-dialog { animation: pd-sink 0.14s ease both; }

@keyframes pd-fade-out { to { opacity: 0; } }
@keyframes pd-sink { to { opacity: 0; transform: translateY(6px) scale(0.97); } }

/* Reduced motion: the dialog still fades in, it just doesn't travel or scale. */

/* Stops the roster scrolling underneath while the dialog is up. */
body.modal-open { overflow: hidden; }

@media (max-width: 620px) {
  .pd-overlay { padding: 0.75rem; align-items: flex-end; }
  .pd-dialog { max-height: 88vh; }
}
.pd-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1rem;
  margin-bottom: 0.6rem;
}
.pd-name { display: block; font-weight: 600; color: var(--text-strong); font-size: 1.05rem; }
.pd-sub { display: block; font-size: 0.8rem; color: var(--muted); }

.pd-head-right { display: flex; align-items: center; gap: 0.6rem; flex: none; }

/* The server id: what staff type into in-game commands, so it reads at a
   glance rather than sitting behind a copy button like the long identifiers. */
.pd-id {
  font-family: var(--mono);
  font-size: 0.78rem;
  color: var(--accent);
  background: #FFFFFF0F;
  border: 1px solid var(--line);
  border-radius: 100px;
  padding: 0.15rem 0.55rem;
  font-variant-numeric: tabular-nums;
}

.pd-close {
  width: 30px; height: 30px;
  display: flex; align-items: center; justify-content: center;
  background: #FFFFFF0F;
  border: 1px solid var(--line);
  border-radius: 50%;
  color: var(--text);
  font-size: 1.2rem;
  line-height: 1;
  cursor: pointer;
  transition: background-color 0.15s ease, color 0.15s ease;
}
.pd-close:hover { background: #FFFFFF1F; color: var(--text-strong); }

.copy-row {
  display: grid;
  grid-template-columns: 8.5rem 1fr auto;
  align-items: center;
  gap: 0.75rem;
  width: 100%;
  text-align: left;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: 0.55rem 0.8rem;
  cursor: pointer;
  font: inherit;
  transition: border-color 0.15s ease;
}
.copy-row:hover { border-color: var(--accent-dim); }
.copy-row .ck {
  font-family: var(--mono);
  font-size: 0.68rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
}
.copy-row .cv {
  font-family: var(--mono);
  font-size: 0.82rem;
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.copy-row .cc {
  font-family: var(--mono);
  font-size: 0.68rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--accent);
}
@media (max-width: 620px) {
  .copy-row { grid-template-columns: 1fr auto; }
  .copy-row .ck { grid-column: 1 / -1; }
}

/* ------------------------------------------------------------------ AOP */

#aop-card h3 {
  font-size: clamp(1.6rem, 3vw, 2.2rem);
  margin: 0.1rem 0 0;
}

.aop-meta {
  display: flex;
  align-items: baseline;
  gap: 1rem;
  flex-wrap: wrap;
  margin-top: 0.1rem;
}
.aop-countdown {
  font-family: var(--mono);
  font-size: 0.82rem;
  color: var(--accent);
  font-variant-numeric: tabular-nums;
}

.aop-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  margin-top: 0.9rem;
}
.aop-group { display: flex; flex-wrap: wrap; gap: 0.5rem; }

/* Divides the region groups. Hidden when the row wraps onto its own line,
   where a stray vertical bar would read as a mistake. */
.aop-sep {
  width: 1px;
  align-self: stretch;
  min-height: 1.4rem;
  background: var(--line);
  margin: 0 0.35rem;
}
@media (max-width: 560px) {
  .aop-sep { display: none; }
  .aop-group { width: 100%; }
}
.aop-btn {
  font: inherit;
  font-size: 0.85rem;
  color: var(--text);
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: 100px;
  padding: 0.42rem 1rem;
  cursor: pointer;
  transition: border-color 0.15s ease, background-color 0.15s ease, color 0.15s ease;
}
.aop-btn:hover:not(:disabled) {
  border-color: var(--accent);
  background: #4C8DF61A;
  color: var(--text-strong);
}
/* The active AOP is shown as a state, not a target — it is disabled so you
   cannot queue a change to where you already are. */
.aop-btn.is-on {
  background: #3FB27A1F;
  border-color: var(--good);
  color: var(--good);
  cursor: default;
}
.aop-btn:disabled:not(.is-on) { opacity: 0.45; cursor: default; }

/* ------------------------------------------------------------ moderation */

.mod-block {
  margin-top: 0.9rem;
  padding-top: 0.9rem;
  border-top: 1px solid var(--line);
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}
.mod-title {
  font-family: var(--mono);
  font-size: 0.68rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--red);
}
.mod-tier {
  font-size: 0.78rem;
  color: var(--muted);
  margin-top: -0.45rem;
}

.mod-reason {
  width: 100%;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: 0.55rem 0.75rem;
  color: var(--text);
  font: inherit;
  font-size: 0.9rem;
}
.mod-reason:focus { outline: 2px solid var(--accent-soft); outline-offset: 1px; }
.mod-reason::placeholder { color: var(--muted); }

.mod-row { display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; }
.mod-lab {
  font-family: var(--mono);
  font-size: 0.66rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
  min-width: 4.2rem;
}
.mod-btn { padding: 0.4rem 0.95rem; font-size: 0.85rem; }
/* Destructive actions are the one place red is used as a button colour. */
.mod-btn--danger { background: var(--red); color: #fff; border-color: var(--red); }
.mod-btn--danger:hover { background: var(--red-soft); color: #1a0505; }

.mod-select {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  color: var(--text);
  font: inherit;
  font-size: 0.85rem;
  padding: 0.4rem 0.6rem;
}

.mod-result {
  font-size: 0.85rem;
  padding: 0.5rem 0.75rem;
  border-radius: var(--radius-sm);
  border-left: 3px solid var(--line);
}
.mod-result--ok   { color: var(--good); border-left-color: var(--good); background: #3FB27A14; }
.mod-result--err  { color: var(--red);  border-left-color: var(--red);  background: #E5484D14; }
.mod-result--warn { color: var(--warn); border-left-color: var(--warn); background: #D9A44114; }
.mod-result--info { color: var(--muted); }

/* ---------------------------------------------------------------- callouts */

.notice {
  background: var(--surface);
  border: 1px solid var(--line);
  border-left: 3px solid var(--accent);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  padding: 1.1rem 1.35rem;
}
.notice--warn { border-left-color: var(--warn); }
.notice--good { border-left-color: var(--good); }
.notice--bad  { border-left-color: var(--bad); }
.notice .nt {
  font-family: var(--mono);
  font-size: 0.7rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--accent);
  display: block;
  margin-bottom: 0.35rem;
}
.notice--warn .nt { color: var(--warn); }
.notice--good .nt { color: var(--good); }
.notice--bad  .nt { color: var(--bad); }

/* ------------------------------------------------------------------ steps */

.steps { list-style: none; margin: 0; padding: 0; counter-reset: step; display: flex; flex-direction: column; gap: 1rem; }
.steps li {
  counter-increment: step;
  display: grid;
  grid-template-columns: 2rem 1fr;
  gap: 1rem;
  align-items: start;
}
.steps li::before {
  content: counter(step);
  font-family: var(--mono);
  font-size: 0.85rem;
  color: var(--accent);
  border: 1px solid var(--accent-dim);
  border-radius: 50%;
  width: 2rem; height: 2rem;
  display: flex; align-items: center; justify-content: center;
  flex: none;
}
/* Label above description — both are inline elements, so without this they
   run together on one line. */
.steps li b {
  display: block;
  color: var(--text-strong);
  font-weight: 600;
  margin-bottom: 0.15rem;
}
.steps li span { display: block; }

/* ------------------------------------------------------------------ rules */

/* ---------------------------------------------------------------- figures */

.figures-head { text-align: center; }
.figures-head .lede { margin-left: auto; margin-right: auto; }

.figures {
  grid-template-columns: repeat(auto-fit, minmax(11.5rem, 1fr));
  margin-top: 2rem;
}

.figure {
  --fig: var(--accent);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.6rem;
  padding: 1.6rem 1rem 1.4rem;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  text-align: center;
  transition:
    border-color 0.25s ease,
    box-shadow 0.3s ease,
    transform 0.3s cubic-bezier(0.16, 0.84, 0.44, 1);
}
.figure:hover {
  transform: translateY(-3px);
  border-color: color-mix(in srgb, var(--fig) 55%, transparent);
  box-shadow: 0 10px 26px -10px color-mix(in srgb, var(--fig) 45%, transparent);
}

.figure-label {
  font-family: var(--mono);
  font-size: 0.66rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
}

.figure-n {
  font-family: var(--display);
  font-weight: 700;
  font-size: clamp(2.3rem, 4.2vw, 3.1rem);
  line-height: 1;
  color: var(--fig);
  /* Tabular figures stop the number jittering sideways as it counts up. */
  font-variant-numeric: tabular-nums;
}

.figure-rule {
  width: 44px;
  height: 3px;
  border-radius: 2px;
  background: var(--fig);
  opacity: 0.9;
}

.figure--good   { --fig: var(--good); }
.figure--blue   { --fig: var(--accent); }
.figure--warn   { --fig: var(--warn); }
.figure--violet { --fig: #A855F7; }

/* -------------------------------------------------------------- analytics */

/*
 * One hue for every data mark. These are all single-series magnitude charts,
 * so there is no identity to encode with colour and no legend to reconcile —
 * the card heading names the series. Figures use text ink, never a mark colour.
 * The four-colour tile set from the home page was measured against this surface
 * and failed the lightness band, so it is deliberately not reused here.
 */
.dash-range { display: flex; gap: 0.5rem; margin-top: 1.5rem; }
.dash-btn {
  background: transparent;
  border: 1px solid var(--line);
  border-radius: 100px;
  padding: 0.4rem 0.95rem;
  color: var(--muted);
  font: inherit;
  font-size: 0.85rem;
  cursor: pointer;
  transition: color 0.15s ease, border-color 0.15s ease, background-color 0.15s ease;
}
.dash-btn:hover { color: var(--text); background: #FFFFFF0A; }
.dash-btn.is-on {
  color: var(--text-strong);
  border-color: color-mix(in srgb, var(--accent) 60%, transparent);
  background: color-mix(in srgb, var(--accent) 14%, transparent);
}

.dash-figures {
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
  margin-bottom: 1.25rem;
}
.dash-fig {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  padding: 1.25rem 1.15rem;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}
.dash-fig-l {
  font-family: var(--mono);
  font-size: 0.64rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
}
.dash-fig-n {
  font-family: var(--display);
  font-weight: 700;
  font-size: 2rem;
  line-height: 1;
  /* Text ink, not a series colour — these are numbers, not marks. */
  color: var(--text-strong);
  font-variant-numeric: tabular-nums;
  /* A currency total can be long ("CA$1,234.56"); shrink rather than overflow. */
  overflow-wrap: anywhere;
}
/* Optional third line on a tile — the order count under a revenue figure. */
.dash-fig .small { margin: 0; }

.dash-wide { margin-bottom: 1.25rem; }
/* 30rem, not 20rem: at the shell's ~1040px this holds the four cards to a 2x2
   instead of letting a third track fit and orphaning the fourth on its own row. */
.dash-grid { grid-template-columns: repeat(auto-fit, minmax(30rem, 1fr)); }
.dash-card h3 { margin: 0 0 0.2rem; font-size: 1.05rem; }
.dash-note { margin: 0 0 0.9rem; }

/* --- ranked horizontal bars ------------------------------------------- */

.dash-bars { list-style: none; margin: 0.9rem 0 0; padding: 0; }
.dash-bars li {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 0.3rem 0.75rem;
  padding: 0.5rem 0;
  border-bottom: 1px solid var(--line-soft);
}
.dash-bars li:last-child { border-bottom: none; }
.dash-bar-label {
  font-size: 0.85rem;
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.dash-bar-val {
  font-family: var(--mono);
  font-size: 0.78rem;
  color: var(--text-strong);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.dash-bar-val em { font-style: normal; color: var(--muted); font-size: 0.72rem; }
.dash-bar {
  grid-column: 1 / -1;
  height: 4px;
  background: #FFFFFF0F;
  border-radius: 2px;
  overflow: hidden;
}
.dash-bar i {
  display: block;
  height: 100%;
  background: var(--accent);
  /* Rounded data-end only; the baseline end stays square against the axis. */
  border-radius: 0 4px 4px 0;
}

/* --- ordered columns (days, hours) ------------------------------------ */

.dash-cols {
  display: flex;
  /* Columns must STRETCH to the row height, not sit at flex-end: each bar is
     sized as a percentage of its column, and a column shrink-wrapped to its
     content is zero tall, so every bar would resolve to 0. The bars are pushed
     to the baseline by align-items on .dash-col instead. */
  align-items: stretch;
  /* A 2px surface gap between adjacent marks, per the mark spec. */
  gap: 2px;
  height: 130px;
  margin-top: 0.9rem;
  padding-bottom: 2px;
  /* Recessive baseline rather than a full grid. */
  border-bottom: 1px solid var(--line);
}
.dash-col {
  flex: 1 1 0;
  height: 100%;
  display: flex;
  align-items: flex-end;
  min-width: 2px;
}
.dash-col i {
  display: block;
  width: 100%;
  background: var(--accent);
  border-radius: 4px 4px 0 0;
  transition: opacity 0.15s ease;
}
.dash-col:hover i { opacity: 0.75; }

.dash-cols-axis {
  display: flex;
  justify-content: space-between;
  margin-top: 0.45rem;
  font-family: var(--mono);
  font-size: 0.66rem;
  color: var(--muted);
}

/* --- section headings & single figures --------------------------------- */

.dash-h {
  margin: 2.25rem 0 1rem;
  font-size: 1.4rem;
  padding-bottom: 0.5rem;
  border-bottom: 1px solid var(--line);
}
.dash-solo {
  font-family: var(--display);
  font-weight: 700;
  font-size: 2.4rem;
  line-height: 1;
  margin: 0.4rem 0 0.35rem;
  color: var(--text-strong);
  font-variant-numeric: tabular-nums;
}
.dash-foot { text-align: center; max-width: none; margin-top: 1.5rem; }

/* --- pack table -------------------------------------------------------- */

/* Identity plus three measures is a table, not a chart. */
.dash-scroll { overflow-x: auto; }
.dash-table {
  width: 100%;
  border-collapse: collapse;
  margin-top: 0.9rem;
  font-size: 0.85rem;
}
.dash-table th {
  text-align: left;
  font-family: var(--mono);
  font-size: 0.62rem;
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
  padding: 0 0.6rem 0.5rem 0;
  border-bottom: 1px solid var(--line);
  white-space: nowrap;
}
.dash-table td {
  padding: 0.6rem 0.6rem 0.6rem 0;
  border-bottom: 1px solid var(--line-soft);
  vertical-align: middle;
}
.dash-table tr:last-child td { border-bottom: none; }
.dash-t-name {
  color: var(--text);
  max-width: 15rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* Alignment follows the CELL CLASS, not the column index: the pack funnel table
   and the six-column payments table do not share a shape, and nth-child rules
   written for one silently mis-align the other. */
.dash-table th.h-num { text-align: right; }
.dash-table--pack th:nth-child(2), .dash-table--pack th:nth-child(3) { text-align: right; }
.dash-table--money th:nth-child(2), .dash-table--money th:nth-child(3) { text-align: right; }
/* A currency heading inside the money table. */
.dash-t-group td {
  font-family: var(--mono);
  font-size: 0.64rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
  padding-top: 1rem;
  border-bottom: 1px solid var(--line);
}
.dash-t-num {
  text-align: right;
  font-family: var(--mono);
  color: var(--text-strong);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
/* The click-through column needs air: without it the bar starts immediately
   after the Buy-clicked figure and the two columns read as one. */
.dash-table th:nth-child(4) { padding-left: 1.5rem; }
.dash-t-ctr {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  min-width: 8rem;
  padding-left: 1.5rem;
}
.dash-mini {
  flex: 1 1 auto;
  height: 4px;
  min-width: 2.5rem;
  background: #FFFFFF0F;
  border-radius: 2px;
  overflow: hidden;
}
.dash-mini i {
  display: block;
  height: 100%;
  background: var(--accent);
  border-radius: 0 4px 4px 0;
}
/* A row that is real money but not attributable to a pack. */
.dash-t-faint td { color: var(--muted); }
.dash-t-pct {
  font-family: var(--mono);
  font-size: 0.78rem;
  color: var(--text-strong);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* --- federal agency cards --------------------------------------------- */

/* The three federal cards carry more copy than the local ones, so they get a
   little structure: what the agency actually does, then the rank ladder pinned
   to the bottom so the three line up. */
.card.fed { display: flex; flex-direction: column; }
.card.fed .muted { flex: 1 1 auto; }
.fed-ranks {
  margin: 0.9rem 0 0;
  padding-top: 0.75rem;
  border-top: 1px solid var(--line);
  font-size: 0.76rem;
  color: var(--muted);
}

/* --------------------------------------------------------------- dispatch */

.disp-figs {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
  margin-top: 1.5rem;
}
.disp-fig {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  padding: 0.5rem 0.9rem;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 100px;
}
.disp-fig-l {
  font-family: var(--mono);
  font-size: 0.62rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
}
.disp-fig-n {
  font-family: var(--display);
  font-weight: 700;
  font-size: 1.15rem;
  color: var(--text-strong);
  font-variant-numeric: tabular-nums;
}

/*
 * The left column is now a STACK (map, units, activity), so it can grow to meet
 * the sidebar instead of one short card stretching to the row height and
 * leaving a thousand pixels of nothing under the map — which is exactly what it
 * did when the map was the column's only child.
 *
 * align-items: start stops any remaining height difference being absorbed by
 * stretching a card; the shorter column simply ends.
 */
.disp-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.9fr) minmax(19rem, 1fr);
  gap: 1.25rem;
  align-items: start;
}
.disp-main { display: flex; flex-direction: column; gap: 1.25rem; min-width: 0; }

/* --- legend --- */
.disp-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 0.45rem 1rem;
  margin-top: 0.85rem;
  padding-top: 0.8rem;
  border-top: 1px solid var(--line);
}
.disp-leg {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.72rem;
  color: var(--muted);
}
.disp-leg-pin {
  width: 11px;
  height: 11px;
  background: var(--c, #8A9099);
  border: 1.5px solid #0A0B0D;
  border-radius: 50% 50% 50% 0;
  box-shadow: 0 0 0 1px var(--c, #8A9099);
  flex: none;
  position: relative;
}
.disp-leg-pin.is-duty { box-shadow: 0 0 0 2px #4ADE8066; }
.disp-leg-pin b { position: absolute; inset: 3px; background: #0A0B0D; border-radius: 50%; }
.disp-leg-call {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: #dc2626;
  border: 2px solid #0A0B0D;
  box-shadow: 0 0 0 2px #dc262666;
  flex: none;
}

/* --- unit board --- */
.disp-unit-row {
  display: grid;
  grid-template-columns: 4.5rem minmax(0, 1fr) minmax(0, 1fr);
  gap: 0.75rem;
  align-items: center;
  padding: 0.55rem 0.6rem;
  margin: 0 -0.6rem;
  border-bottom: 1px solid var(--line-soft);
  border-left: 2px solid transparent;
  border-radius: var(--radius-sm);
  transition: background-color 0.15s ease, border-left-color 0.15s ease;
}
.disp-unit-row:last-child { border-bottom: none; }
.disp-unit-row.is-locatable { cursor: pointer; }
.disp-unit-row.is-locatable:hover {
  background: #FFFFFF0A;
  border-left-color: var(--c);
}
.disp-u-cs {
  font-family: var(--mono);
  font-size: 0.82rem;
  color: var(--text-strong);
  border-left: 2px solid var(--c);
  padding-left: 0.5rem;
}
.disp-u-who { display: flex; flex-direction: column; gap: 0.05rem; min-width: 0; }
.disp-u-who b {
  font-size: 0.85rem;
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.disp-u-job { min-width: 0; text-align: right; }
.disp-u-call {
  display: inline-block;
  max-width: 100%;
  font-size: 0.75rem;
  padding: 0.15rem 0.5rem;
  border-radius: 100px;
  background: color-mix(in srgb, var(--warn) 18%, transparent);
  color: var(--warn);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.disp-u-call.is-scene {
  background: color-mix(in srgb, var(--good) 18%, transparent);
  color: var(--good);
}
@media (max-width: 620px) {
  .disp-unit-row { grid-template-columns: 4.5rem minmax(0, 1fr); }
  .disp-u-job { grid-column: 2; text-align: left; }
}

/* --- activity feed --- */
.disp-feed-row {
  display: grid;
  grid-template-columns: 1.4rem minmax(0, 1fr) auto;
  gap: 0.6rem;
  align-items: baseline;
  padding: 0.4rem 0;
  border-bottom: 1px solid var(--line-soft);
  font-size: 0.82rem;
}
.disp-feed-row:last-child { border-bottom: none; }
.disp-f-ico {
  font-family: var(--mono);
  font-size: 0.72rem;
  text-align: center;
  color: var(--muted);
}
.disp-f-text { color: var(--text); min-width: 0; }
.disp-f-when {
  font-family: var(--mono);
  font-size: 0.66rem;
  color: var(--muted);
  white-space: nowrap;
}
.disp-f--call-new .disp-f-ico { color: var(--bad); }
.disp-f--call-closed .disp-f-ico,
.disp-f--on-scene .disp-f-ico,
.disp-f--duty-on .disp-f-ico { color: var(--good); }
.disp-f--attach .disp-f-ico { color: var(--warn); }
@media (max-width: 1100px) { .disp-grid { grid-template-columns: minmax(0, 1fr); } }
.disp-side { display: flex; flex-direction: column; gap: 1.25rem; min-width: 0; }

.disp-maphead { display: flex; align-items: flex-end; justify-content: space-between; gap: 1rem; margin-bottom: 0.75rem; }
.disp-stamp { font-family: var(--mono); font-size: 0.7rem; color: var(--muted); text-align: right; }

/* SQUARE ON PURPOSE. The atlas artwork is square, so a square panel is the one
   shape where "show the whole map" and "fill the frame" are the same thing --
   any other ratio forces a choice between dead space and a crop. The JS floors
   the zoom at whatever actually covers the panel, so the clamps below (a very
   tall narrow card, a short viewport) degrade to a small crop rather than to
   dead space. */
.disp-map {
  position: relative;   /* the wheel-zoom hint anchors to this */
  /*
   * CONTAIN LEAFLET'S STACKING.
   *
   * Leaflet gives its panes z-index 200-800 and its zoom control 1000. The
   * sticky header is z-index 50. Without a stacking context here those numbers
   * are compared at the document root, so the map's markers and controls paint
   * straight over the header as the page scrolls under it.
   *
   * `isolation: isolate` makes this element a stacking context, so every one of
   * Leaflet's z-indexes is resolved INSIDE the panel and the panel as a whole
   * takes its natural place below the header. Preferred over `z-index: 0`
   * because it says what it is for and needs no invented number.
   */
  isolation: isolate;
  aspect-ratio: 1 / 1;
  height: auto;
  min-height: 360px;
  max-height: 78vh;
  border-radius: var(--radius);
  overflow: hidden;
  background: #0A0B0D;
  border: 1px solid var(--line);
}

/* The wheel-zoom hint, shown only while the wheel is still inert. */
.disp-maphint {
  position: absolute;
  left: 50%;
  bottom: 0.9rem;
  transform: translateX(-50%);
  z-index: 500;
  padding: 0.3rem 0.8rem;
  font-size: 0.75rem;
  color: var(--text);
  background: #0A0B0DE6;
  border: 1px solid var(--line);
  border-radius: 100px;
  pointer-events: none;
  transition: opacity 0.25s ease;
}
.disp-map.is-live .disp-maphint { opacity: 0; }
@media (max-width: 700px) { .disp-map { max-height: 62vh; } }

/* Leaflet paints its own light chrome; bring it into the dark theme.
   SCOPED, and that matters: leaflet.css is injected per-page AFTER site.css
   (layout.js loads site.css, pages.js appends the page's own <head>), so a bare
   `.leaflet-container` here loses the tie on source order and Leaflet's default
   #ddd wins -- which is the grey that showed through around the map. Two
   classes outrank one whatever the load order. */
/* NO SPACE. Leaflet puts `leaflet-container` ON the element it is given, so it
   is #disp-map itself — a descendant selector (`.disp-map .leaflet-container`)
   matches nothing at all, which is why the grey survived the first attempt. */
.disp-map.leaflet-container { background: #0A0B0D; }
.leaflet-container { background: #0A0B0D; font: inherit; }
.leaflet-control-zoom a {
  background: #14161AF2;
  border: 1px solid var(--line);
  color: var(--text);
}
.leaflet-control-zoom a:hover { background: #1D2026; color: var(--text-strong); }
/*
 * Unit popup.
 *
 * SCOPED UNDER .disp-map, and that is the whole fix. leaflet.css is loaded from
 * the page's head AFTER site.css, so a bare `.leaflet-popup-content-wrapper`
 * ties Leaflet's own rule on specificity and loses on source order — the popup
 * kept Leaflet's white background while our rules still coloured the VALUES
 * white, so every value was white-on-white and looked missing. Leaflet renders
 * popups inside the map container, so the descendant selector both outranks it
 * and stays confined to this page.
 */
.disp-map .leaflet-popup-content-wrapper {
  background: #14161AFA;
  color: var(--text);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: 0 20px 44px #000000A6;
  backdrop-filter: blur(10px);
}
.disp-map .leaflet-popup-tip {
  background: #14161AFA;
  border: 1px solid var(--line);
  box-shadow: none;
}
.disp-map .leaflet-popup-content { margin: 0; font-size: 0.85rem; line-height: 1.45; }
.disp-map .leaflet-popup-close-button {
  color: var(--muted);
  top: 6px;
  right: 6px;
}
.disp-map .leaflet-popup-close-button:hover { color: var(--text-strong); }

.disp-pop { min-width: 12rem; padding: 0.85rem 0.95rem; }
.disp-pop-name {
  display: block;
  font-family: var(--display);
  font-weight: 700;
  font-size: 1.15rem;
  line-height: 1.15;
  color: var(--text-strong);
  padding-right: 1rem;   /* clear of the close button */
}
.disp-pop-cs {
  display: inline-block;
  margin-top: 0.4rem;
  font-family: var(--mono);
  font-size: 0.8rem;
  color: var(--text-strong);
  background: color-mix(in srgb, var(--c, #8A9099) 22%, transparent);
  border: 1px solid color-mix(in srgb, var(--c, #8A9099) 55%, transparent);
  border-radius: 100px;
  padding: 0.1rem 0.55rem;
}
/* One fact per row: a label in muted ink, the value in text ink. Values are
   never given a colour that could match the surface behind them. */
.disp-pop-list { margin: 0.6rem 0 0; display: grid; gap: 0.28rem; }
.disp-pop-row {
  display: grid;
  grid-template-columns: 4.6rem 1fr;
  gap: 0.5rem;
  align-items: baseline;
  font-size: 0.8rem;
}
.disp-pop-k {
  font-family: var(--mono);
  font-size: 0.62rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
}
.disp-pop-v { color: var(--text-strong); min-width: 0; overflow-wrap: anywhere; }
.disp-pop-v.is-muted { color: var(--muted); }
.disp-pop-duty {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  margin-top: 0.65rem;
  padding-top: 0.55rem;
  border-top: 1px solid var(--line);
  font-size: 0.78rem;
}
.disp-pop-duty i {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: currentColor;
  flex: none;
}
.disp-pop-duty.is-on { color: var(--good); }
.disp-pop-duty.is-off { color: var(--muted); }

/* A unit: a heading wedge, with the callsign riding alongside. */
.disp-marker { background: none; border: 0; }
.disp-pin {
  display: block;
  width: 14px;
  height: 14px;
  background: var(--c);
  border: 1.5px solid #0A0B0D;
  border-radius: 50% 50% 50% 0;
  box-shadow: 0 0 0 1px var(--c), 0 2px 6px #000000A6;
  transform-origin: center;
}
.disp-marker.is-duty .disp-pin { box-shadow: 0 0 0 2px #4ADE8066, 0 2px 8px #000000A6; }
.disp-veh {
  position: absolute;
  inset: 4px;
  background: #0A0B0D;
  border-radius: 50%;
}
.disp-tag {
  position: absolute;
  left: 18px;
  top: -2px;
  font-family: var(--mono);
  font-size: 0.62rem;
  color: var(--text);
  background: #0A0B0DCC;
  padding: 0.05rem 0.3rem;
  border-radius: 3px;
  white-space: nowrap;
  pointer-events: none;
}
.disp-call span {
  display: block;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: var(--c);
  border: 2px solid #0A0B0D;
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--c) 45%, transparent);
  animation: disp-ping 1.8s ease-out infinite;
}
@keyframes disp-ping {
  0%   { box-shadow: 0 0 0 2px color-mix(in srgb, var(--c) 55%, transparent); }
  70%  { box-shadow: 0 0 0 10px transparent; }
  100% { box-shadow: 0 0 0 0 transparent; }
}

/* --- radio channels --- */
.disp-ch { padding: 0.55rem 0; border-bottom: 1px solid var(--line-soft); }
.disp-ch:last-child { border-bottom: none; }
.disp-ch.is-empty { opacity: 0.45; }
.disp-ch-head { display: flex; align-items: center; gap: 0.5rem; font-size: 0.88rem; }
.disp-ch-head b { color: var(--text-strong); }
.disp-freq {
  font-family: var(--mono);
  font-size: 0.66rem;
  color: var(--muted);
  border: 1px solid var(--line);
  border-radius: 100px;
  padding: 0.05rem 0.4rem;
}
.disp-n { margin-left: auto; font-family: var(--mono); font-size: 0.75rem; color: var(--muted); }
.disp-ch-body { display: flex; flex-wrap: wrap; gap: 0.3rem; margin-top: 0.45rem; }
.disp-who {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0.2rem 0.5rem;
  font: inherit;
  font-size: 0.75rem;
  color: var(--text);
  background: #FFFFFF0A;
  border: 1px solid var(--line);
  border-left: 2px solid var(--c);
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: background-color 0.15s ease, transform 0.15s ease;
}
.disp-who:hover { background: #FFFFFF16; transform: translateY(-1px); }
.disp-who b { font-family: var(--mono); color: var(--text-strong); }

/* --- calls --- */
.disp-call-row {
  display: grid;
  grid-template-columns: 3px 1fr auto;
  gap: 0.65rem;
  padding: 0.6rem 0;
  border-bottom: 1px solid var(--line-soft);
  cursor: pointer;
  transition: background-color 0.15s ease;
}
.disp-call-row:last-child { border-bottom: none; }
.disp-call-row:hover { background: #FFFFFF08; }
.disp-pri { background: var(--c); border-radius: 2px; }
.disp-call-row.is-live .disp-pri { box-shadow: 0 0 8px 1px var(--c); }
.disp-call-main { min-width: 0; display: flex; flex-direction: column; gap: 0.1rem; }
.disp-call-main b { font-size: 0.88rem; color: var(--text-strong); }
.disp-call-main span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.disp-when { font-family: var(--mono); font-size: 0.68rem; color: var(--muted); white-space: nowrap; }
.disp-units { display: flex; flex-wrap: wrap; gap: 0.25rem; margin-top: 0.3rem; }
.disp-unit {
  font-family: var(--mono);
  font-size: 0.68rem;
  padding: 0.1rem 0.35rem;
  border-radius: 3px;
  background: #FFFFFF0F;
  color: var(--text);
}
.disp-unit em { font-style: normal; color: var(--muted); }
.disp-unit.is-scene { background: color-mix(in srgb, var(--good) 20%, transparent); color: var(--good); }

/* ------------------------------------------------------------ information */

/* FAQ accordions are native <details>: no script to load, no state to keep in
   sync, and they still work if the JS never arrives. */
.faq-group { display: flex; flex-direction: column; gap: 0.5rem; }
.faq-item {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
}
.faq-item summary {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.9rem 1.1rem;
  cursor: pointer;
  font-weight: 600;
  font-size: 0.95rem;
  color: var(--text-strong);
  list-style: none;
  transition: background-color 0.15s ease;
}
.faq-item summary::-webkit-details-marker { display: none; }
.faq-item summary::after {
  content: '';
  margin-left: auto;
  width: 8px;
  height: 8px;
  border-right: 2px solid var(--muted);
  border-bottom: 2px solid var(--muted);
  transform: rotate(45deg) translate(-2px, -2px);
  transition: transform 0.18s ease;
  flex: none;
}
.faq-item[open] summary::after { transform: rotate(-135deg) translate(-2px, -2px); }
.faq-item summary:hover { background: #FFFFFF08; }
.faq-a { padding: 0 1.1rem 1rem; }
.faq-a p { margin: 0; color: var(--muted); font-size: 0.9rem; max-width: 62ch; }
.faq-a code, .cmd-list code, .fmt code {
  font-family: var(--mono);
  font-size: 0.86em;
}

/* Callsign commands. */
.cmd-list { list-style: none; margin: 0.75rem 0 0; padding: 0; display: grid; gap: 0.85rem; }
.cmd-list li { display: flex; flex-direction: column; gap: 0.3rem; }
.cmd-list code {
  align-self: flex-start;
  background: #FFFFFF0F;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: 0.3rem 0.6rem;
  color: var(--text-strong);
  font-size: 0.85rem;
}

/* Allowed / denied examples, so the two are impossible to mix up. */
.fmt { display: grid; gap: 0.6rem; }
.fmt-row {
  display: grid;
  grid-template-columns: 1fr;
  gap: 0.25rem;
  padding: 0.65rem 0.75rem;
  background: #FFFFFF08;
  border-radius: var(--radius-sm);
}
.fmt-row b { font-size: 0.85rem; color: var(--text-strong); }
.fmt-row .ok,
.fmt-row .no {
  font-family: var(--mono);
  font-size: 0.8rem;
  display: flex;
  align-items: center;
  gap: 0.4rem;
}
.fmt-row .ok { color: var(--good); }
.fmt-row .no { color: var(--bad); }
.fmt-row .ok::before { content: '✓'; }
.fmt-row .no::before { content: '✕'; }

/* --- callsign checker & list ------------------------------------------- */

.cs-check { display: flex; gap: 0.6rem; }
.cs-check input {
  flex: 1 1 auto;
  min-width: 0;
  font-family: var(--mono);
  font-size: 0.95rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  padding: 0.6rem 0.85rem;
  background: #FFFFFF0A;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  color: var(--text-strong);
}
.cs-check input:focus {
  outline: none;
  border-color: color-mix(in srgb, var(--accent) 60%, transparent);
  background: #FFFFFF12;
}
.cs-check .btn { flex: none; }

.cs-result { display: none; margin-top: 0.9rem; padding: 0.75rem 0.9rem; border-radius: var(--radius-sm); border-left: 3px solid var(--line); background: #FFFFFF08; }
.cs-result b { display: block; font-size: 0.95rem; color: var(--text-strong); }
.cs-result span { display: block; margin-top: 0.2rem; font-size: 0.85rem; color: var(--muted); }
.cs-result code { font-family: var(--mono); font-size: 0.85em; color: var(--text-strong); }
.cs-result--idle { display: block; }
.cs-result--good { display: block; border-left-color: var(--good); }
.cs-result--bad  { display: block; border-left-color: var(--bad); }
.cs-result--warn { display: block; border-left-color: var(--warn); }

.cs-head { display: flex; align-items: flex-end; justify-content: space-between; gap: 1rem; flex-wrap: wrap; }
.cs-head input {
  font-size: 0.85rem;
  padding: 0.45rem 0.75rem;
  background: #FFFFFF0A;
  border: 1px solid var(--line);
  border-radius: 100px;
  color: var(--text-strong);
  min-width: 9rem;
}
.cs-head input:focus { outline: none; border-color: color-mix(in srgb, var(--accent) 55%, transparent); }

/* A wrapping bag of tags rather than a table: these are short tokens, and the
   question being answered is "is mine in here", not "how do they rank". */
.cs-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin-top: 0.8rem;
  max-height: 18rem;
  overflow-y: auto;
}
.cs-tag {
  font-family: var(--mono);
  font-size: 0.8rem;
  padding: 0.3rem 0.6rem;
  background: #FFFFFF0F;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  color: var(--text);
  white-space: nowrap;
}

/* --- level system ------------------------------------------------------ */

.lvl-fig { display: flex; flex-direction: column; gap: 0.3rem; }
.lvl-fig .dash-fig-n { font-size: 2.2rem; }
.lvl-fig code { font-family: var(--mono); font-size: 0.5em; }

.lvl-list { list-style: none; margin: 0.75rem 0 0; padding: 0; display: grid; gap: 0.75rem; }
.lvl-list li {
  font-size: 0.88rem;
  color: var(--muted);
  padding-left: 0.9rem;
  border-left: 2px solid var(--line);
  max-width: 60ch;
}
.lvl-list li b { color: var(--text-strong); font-weight: 600; }

/* The unlock ladder. A list rather than a chart: the reader wants to find one
   row ("when do I get a bike?"), not compare magnitudes. */
.lvl-track { display: grid; gap: 0.15rem; margin-top: 0.9rem; }
.lvl-step {
  display: grid;
  grid-template-columns: 5.5rem 1fr;
  gap: 0.9rem;
  align-items: baseline;
  padding: 0.6rem 0;
  border-bottom: 1px solid var(--line-soft);
}
.lvl-step:last-child { border-bottom: none; }
.lvl-num {
  font-family: var(--mono);
  font-size: 0.8rem;
  color: var(--accent);
  text-align: right;
  white-space: nowrap;
}
.lvl-step b { display: block; font-size: 0.92rem; color: var(--text-strong); }
.lvl-step span { display: block; margin-top: 0.1rem; }

@media (max-width: 560px) {
  .lvl-step { grid-template-columns: 1fr; gap: 0.15rem; }
  .lvl-num { text-align: left; }
}

/* --- unlock browser ---------------------------------------------------- */

.unlock-brands { display: flex; flex-wrap: wrap; gap: 0.4rem; margin: 0.9rem 0 0.2rem; }
.unlock-brand {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.35rem 0.7rem;
  background: transparent;
  border: 1px solid var(--line);
  border-radius: 100px;
  color: var(--muted);
  font: inherit;
  font-size: 0.8rem;
  cursor: pointer;
  transition: color 0.15s ease, border-color 0.15s ease, background-color 0.15s ease;
}
.unlock-brand:hover { color: var(--text); background: #FFFFFF0A; }
.unlock-brand.is-on {
  color: var(--text-strong);
  border-color: color-mix(in srgb, var(--accent) 55%, transparent);
  background: color-mix(in srgb, var(--accent) 12%, transparent);
}
.unlock-n {
  font-family: var(--mono);
  font-size: 0.7rem;
  color: var(--muted);
  padding-left: 0.15rem;
}

/* The brand colour is the server's own, used as an identifying dot beside the
   name — never as the text colour, which stays in the normal ink. */
.unlock-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  flex: none;
  display: inline-block;
  margin-right: 0.45rem;
  vertical-align: 0.05em;
}

.unlock-lv {
  font-family: var(--mono);
  color: var(--accent);
  font-variant-numeric: tabular-nums;
}

.unlock-sort {
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  color: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
  cursor: pointer;
}
.unlock-sort:hover { color: var(--text); }
.unlock-sort.is-on { color: var(--text-strong); }
.unlock-sort.is-on::after { content: ' ▾'; font-size: 0.9em; }
.unlock-sort.is-on[data-dir="down"]::after { content: ' ▴'; }

#unlock-table { margin-top: 0.4rem; }
#unlock-table tbody { display: block; max-height: 26rem; overflow-y: auto; }
#unlock-table thead, #unlock-table tbody tr { display: table; width: 100%; table-layout: fixed; }
#unlock-table th:first-child, #unlock-table td:first-child { width: 5rem; }
#unlock-table th:last-child, #unlock-table td:last-child { width: 11rem; }

.unlock-extra {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
  gap: 1.25rem;
  margin-top: 1.5rem;
  padding-top: 1.25rem;
  border-top: 1px solid var(--line);
}
.unlock-extra-group h4 {
  margin: 0 0 0.5rem;
  font-family: var(--mono);
  font-size: 0.64rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
}
.unlock-extra-row {
  display: grid;
  grid-template-columns: 3rem 1fr;
  gap: 0.5rem;
  padding: 0.35rem 0;
  font-size: 0.85rem;
  color: var(--text);
  border-bottom: 1px solid var(--line-soft);
}
.unlock-extra-row:last-child { border-bottom: none; }

/* ------------------------------------------------------------------ store */

/* Category filter above the packs. Mirrors the categories Tebex returns, so
   adding one there adds a chip here. */
.store-cats {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin: 0 0 1.75rem;
}
.store-cats:empty { display: none; }

.store-cat {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  background: transparent;
  border: 1px solid var(--line);
  border-radius: 100px;
  padding: 0.42rem 0.9rem;
  color: var(--muted);
  font: inherit;
  font-size: 0.85rem;
  cursor: pointer;
  transition: color 0.15s ease, border-color 0.15s ease, background-color 0.15s ease;
}
.store-cat:hover { color: var(--text); background: #FFFFFF0A; }
.store-cat.is-on {
  color: var(--text-strong);
  border-color: color-mix(in srgb, var(--accent) 60%, transparent);
  background: color-mix(in srgb, var(--accent) 14%, transparent);
}
.store-cat-n {
  font-family: var(--mono);
  font-size: 0.68rem;
  color: var(--muted);
  background: #FFFFFF12;
  border-radius: 100px;
  padding: 0.05rem 0.4rem;
}
.store-cat.is-on .store-cat-n { color: var(--accent); }

.store-groups { display: flex; flex-direction: column; gap: 2.5rem; }

.store-group-head {
  display: flex;
  align-items: baseline;
  gap: 0.75rem;
  margin-bottom: 1rem;
  padding-bottom: 0.6rem;
  border-bottom: 1px solid var(--line-soft);
}
.store-group-head h3 { margin: 0; }
.store-count {
  font-family: var(--mono);
  font-size: 0.7rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
}
/* Filtered to a single category — its heading only repeats the chip above. */
.store-group--solo .store-group-head { display: none; }

/* ------------------------------------------------------- closing section */

/* The four onboarding steps and the call to action, as one centred block. */
.closer { border-top: 1px solid var(--line-soft); }

.onboard {
  list-style: none;
  margin: 2.25rem 0 0;
  padding: 0;
  grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
  counter-reset: none;
}

.onboard-step {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  padding: 1.35rem 1.15rem;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  transition: border-color 0.25s ease, transform 0.3s cubic-bezier(0.16, 0.84, 0.44, 1);
}
.onboard-step:hover {
  border-color: var(--accent-dim);
  transform: translateY(-3px);
}
.onboard-step b { color: var(--text-strong); font-size: 0.98rem; }
.onboard-step .small { line-height: 1.6; }

.onboard-n {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  border: 1px solid color-mix(in srgb, var(--accent) 55%, transparent);
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  color: var(--accent);
  font-family: var(--mono);
  font-size: 0.82rem;
  line-height: 1;
}

.closer-cta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 0.75rem;
  margin-top: 2.5rem;
}
/* The quiet link sits on its own line under the two buttons. */
.closer-cta .hero-tertiary { flex-basis: 100%; text-align: center; margin-top: 0.35rem; }

/* ------------------------------------------------------------------ rules */

.rules-hero { text-align: center; padding: 4rem 0 2.75rem; }
.rules-hero .lede { margin-left: auto; margin-right: auto; }

.rule-filters {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.5rem;
  margin: 1.75rem 0 1.25rem;
}
.rule-pill {
  --tone: var(--muted);
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  background: transparent;
  border: 1px solid color-mix(in srgb, var(--tone) 40%, transparent);
  border-radius: 100px;
  padding: 0.35rem 0.9rem;
  color: var(--tone);
  font: inherit;
  font-size: 0.8rem;
  cursor: pointer;
  transition: background-color 0.15s ease, border-color 0.15s ease;
}
.rule-pill::before {
  content: '';
  width: 7px; height: 7px;
  border-radius: 50%;
  background: var(--tone);
}
.rule-pill:hover { background: color-mix(in srgb, var(--tone) 12%, transparent); }
.rule-pill[aria-pressed='true'] {
  background: color-mix(in srgb, var(--tone) 20%, transparent);
  border-color: var(--tone);
}
.rule-pill--conduct    { --tone: var(--red); }
.rule-pill--engagement { --tone: var(--warn); }
.rule-pill--duty       { --tone: var(--accent); }
.rule-pill--medical    { --tone: var(--good); }

.rule-tools {
  display: flex;
  gap: 0.6rem;
  max-width: 46rem;
  margin: 0 auto;
}
.rule-search {
  position: relative;
  flex: 1;
  display: flex;
  align-items: center;
}
.rule-search svg {
  position: absolute;
  left: 0.9rem;
  width: 16px; height: 16px;
  color: var(--muted);
  pointer-events: none;
}
.rule-search input {
  width: 100%;
  background: #FFFFFF0A;
  border: 1px solid var(--line);
  border-radius: 100px;
  padding: 0.6rem 1rem 0.6rem 2.4rem;
  color: var(--text-strong);
  font: inherit;
  font-size: 0.9rem;
}
.rule-search input::placeholder { color: var(--muted); }
.rule-search input:focus {
  outline: none;
  background: #FFFFFF12;
  border-color: #FFFFFF33;
}
.rule-expand { white-space: nowrap; padding: 0.6rem 1.1rem; font-size: 0.85rem; }

.rule-tally {
  margin: 1rem 0 0;
  font-family: var(--mono);
  font-size: 0.72rem;
  letter-spacing: 0.08em;
  color: var(--muted);
}

.rule-stack { display: flex; flex-direction: column; gap: 0.75rem; }

.rule-cat {
  --tone: var(--muted);
  background: var(--surface);
  border: 1px solid var(--line);
  border-left: 3px solid var(--tone);
  border-radius: var(--radius);
  overflow: hidden;
}
.rule-cat.is-hidden { display: none; }

/*
 * These MUST come after .rule-cat. They are single-class selectors just like it,
 * so specificity ties and source order decides — declared earlier, the base
 * rule's muted fallback silently won and every section rendered grey.
 *
 * The tone drives the left edge, the icon tint, the rule references and the
 * count badge, so one variable colours the whole section.
 */
.rule-cat--conduct    { --tone: var(--red); }
.rule-cat--engagement { --tone: var(--warn); }
.rule-cat--duty       { --tone: var(--accent); }
.rule-cat--medical    { --tone: var(--good); }

.rule-head {
  display: grid;
  grid-template-columns: 38px minmax(0, 1fr) 20px;
  align-items: center;
  gap: 0.9rem;
  width: 100%;
  padding: 1rem 1.15rem;
  background: transparent;
  border: none;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
  transition: background-color 0.15s ease;
}
.rule-head:hover { background: #FFFFFF08; }

.rule-ico {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 38px; height: 38px;
  border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--tone) 16%, transparent);
  color: var(--tone);
}
.rule-ico svg { width: 19px; height: 19px; }

.rule-title { display: flex; flex-direction: column; gap: 0.2rem; min-width: 0; }
.rule-name { display: flex; align-items: center; flex-wrap: wrap; gap: 0.55rem; }
.rule-name b { color: var(--text-strong); font-size: 0.98rem; font-weight: 600; }
.rule-count {
  font-family: var(--mono);
  font-size: 0.64rem;
  letter-spacing: 0.06em;
  color: var(--tone);
  border: 1px solid color-mix(in srgb, var(--tone) 45%, transparent);
  border-radius: 100px;
  padding: 0.08rem 0.45rem;
  white-space: nowrap;
}
.rule-blurb { font-size: 0.83rem; color: var(--muted); }

.rule-chev {
  width: 20px; height: 20px;
  color: var(--muted);
  transition: transform 0.28s cubic-bezier(0.16, 0.84, 0.44, 1);
}
.rule-cat.is-open .rule-chev { transform: rotate(180deg); }

/* Animating grid-template-rows from 0fr to 1fr expands to the content's own
   height without needing to measure it in JavaScript. */
.rule-body {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 0.32s cubic-bezier(0.16, 0.84, 0.44, 1);
}
.rule-cat.is-open .rule-body { grid-template-rows: 1fr; }
/* Padding-free by design — see the note in pages.js. */
.rule-inner { overflow: hidden; min-height: 0; }

.rule-list {
  list-style: none;
  margin: 0;
  padding: 0 1.15rem 0.5rem 1.15rem;
  display: flex;
  flex-direction: column;
}
.rule-item {
  display: grid;
  grid-template-columns: 3.6rem minmax(0, 1fr);
  gap: 0.9rem;
  padding: 0.7rem 0 0.7rem 0.85rem;
  border-left: 2px solid color-mix(in srgb, var(--tone) 45%, transparent);
  border-bottom: 1px solid var(--line-soft);
}
.rule-item:last-child { border-bottom: none; }
.rule-item.is-hidden { display: none; }

.rule-ref {
  font-family: var(--mono);
  font-size: 0.72rem;
  color: var(--tone);
  letter-spacing: 0.04em;
  padding-top: 0.12rem;
}
.rule-text { font-size: 0.9rem; line-height: 1.65; }
.rule-text b { color: var(--text-strong); }
/* Marks the part of a rule that matched what was typed. */
.rule-text mark {
  background: color-mix(in srgb, var(--accent) 30%, transparent);
  color: var(--text-strong);
  border-radius: 3px;
  padding: 0 0.15em;
}

.rule-empty { margin: 1.5rem 0 0; text-align: center; }

@media (max-width: 620px) {
  .rule-tools { flex-direction: column; }
  .rule-expand { width: 100%; }
  .rule-head { grid-template-columns: 32px minmax(0, 1fr) 18px; gap: 0.7rem; padding: 0.85rem 0.9rem; }
  .rule-ico { width: 32px; height: 32px; }
  .rule-item { grid-template-columns: 3rem minmax(0, 1fr); gap: 0.6rem; }
  .rule-list { padding-left: 0.9rem; padding-right: 0.9rem; }
}

/* ------------------------------------------------------------------ table */

.table-wrap { overflow-x: auto; border: 1px solid var(--line); border-radius: var(--radius); }
table { border-collapse: collapse; width: 100%; font-size: 0.92rem; }
th, td { text-align: left; padding: 0.7rem 1rem; border-bottom: 1px solid var(--line-soft); }
th {
  font-family: var(--mono);
  font-size: 0.68rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
  background: var(--surface-2);
  font-weight: 500;
}
tr:last-child td { border-bottom: none; }
td.num { font-variant-numeric: tabular-nums; white-space: nowrap; }

code, kbd {
  font-family: var(--mono);
  font-size: 0.875em;
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: 4px;
  padding: 0.1em 0.4em;
  color: var(--accent-soft);
}
kbd { color: var(--text-strong); }

/* ----------------------------------------------------------------- footer */

.site-footer {
  flex: none;
  border-top: 1px solid var(--line);
  background: var(--ground-2);
  padding: 2.5rem 0 2rem;
  margin-top: 3rem;
}
.footer-grid {
  display: grid;
  grid-template-columns: 2fr 1fr 1fr;
  gap: 2rem;
  margin-bottom: 2rem;
}
@media (max-width: 700px) { .footer-grid { grid-template-columns: 1fr; } }
.footer-grid h4 {
  font-family: var(--mono);
  font-size: 0.7rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted);
  font-weight: 500;
  margin-bottom: 0.75rem;
}
.footer-links { display: flex; flex-direction: column; gap: 0.4rem; }
.footer-links a { color: var(--text); font-size: 0.9rem; }
.footer-links a:hover { color: var(--accent); }

.legal {
  border-top: 1px solid var(--line-soft);
  padding-top: 1.5rem;
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  font-size: 0.8rem;
  color: var(--muted);
}
/* Required by the Cfx/Rockstar Creator PLA §2.3 — do not remove */
.legal .disclaimer {
  font-family: var(--mono);
  font-size: 0.7rem;
  letter-spacing: 0.06em;
  color: var(--muted);
  line-height: 1.6;
}


/* ------------------------------------------------------------- utilities */

.hide { display: none !important; }
.sr-only {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0,0,0,0); white-space: nowrap; border: 0;
}
.center { text-align: center; }
.divider { height: 1px; background: var(--line-soft); border: 0; margin: 0; }

/* A labelled rule for marking a real boundary between groups — the local
   departments and the paid-pack federal agencies. The line fades out at both
   ends so it reads as a soft break rather than a hard table border. */
.section-divider {
  display: flex;
  align-items: center;
  gap: 1.1rem;
  margin: 2.5rem 0 0.5rem;
}
.section-divider::before,
.section-divider::after {
  content: "";
  flex: 1;
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--line), transparent);
}
.section-divider span {
  font-family: var(--mono);
  font-size: 0.66rem;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--muted);
  white-space: nowrap;
}

/* Account / session chip in the header */
.user-chip {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: 100px;
  padding: 0.25rem 0.8rem 0.25rem 0.25rem;
  font-size: 0.85rem;
  color: var(--text);
  text-decoration: none;
}
.user-chip:hover { border-color: var(--accent-dim); text-decoration: none; color: var(--text-strong); }
.user-chip img { width: 24px; height: 24px; border-radius: 50%; flex: none; }
