/* ==========================================================================
   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: 1560px) {
  /* TEN items is a lot; tighten well before anything is forced to wrap.
     1340 -> 1440 when Dispatch made it nine links, 1440 -> 1560 when Info made
     it ten: measured, the full-padding nav plus the header actions was 44px
     wider than the viewport at 1441. Each link costs about 15px of padding at
     full size, so every link added pushes this threshold up by roughly 150px. */
  .nav { gap: 0.1rem; }
  .nav a { padding: 0.45rem 0.7rem; font-size: 0.85rem; }
  .nav-sep { margin: 0 0.05rem; }
}
/*
 * A second step, added when Info made it ten links.
 *
 * Measured: at 1280 the once-tightened nav plus the header actions came to 19px
 * wider than the viewport. 1280 is an extremely common laptop width, and the
 * alternative — dropping the collapse breakpoint on top of it — would have hidden
 * the whole nav behind a hamburger on those machines to save nineteen pixels.
 * Trimming the horizontal padding recovers far more than that and keeps every
 * link on screen.
 */
@media (max-width: 1360px) {
  .nav { gap: 0.05rem; }
  .nav a { padding: 0.45rem 0.5rem; font-size: 0.82rem; }
}
.nav a:hover { background: #FFFFFF14; color: var(--text-strong); text-decoration: none; }
.nav a[aria-current="page"] { background: #FFFFFF1A; color: var(--text-strong); }

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

/*
 * Shared by the panels that drop out of the header. The hamburger menu that
 * originally owned this is gone — Information is a nav tab now — but the live
 * viewer panel still animates in with it, so the keyframe stays.
 */
@keyframes hdr-pop {
  from { opacity: 0; transform: translateY(-4px) scale(0.98); }
  to   { opacity: 1; transform: none; }
}

/*
 * 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; }
/* --- live viewer count ---------------------------------------------------
   Quieter than the menu button on purpose: it is ambient information, not a
   control most people can act on. Everyone sees the number; only staff get a
   panel, and only then does it start behaving like something clickable. */
.hdr-eye { position: relative; display: inline-flex; }
.hdr-eye[hidden] { display: none; }
.hdr-eye-btn {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  height: 32px;
  margin-left: 0.55rem;
  padding: 0 0.5rem;
  border: 0;
  border-radius: 100px;
  background: transparent;
  color: var(--muted);
  font-family: var(--mono);
  font-size: 0.76rem;
  font-variant-numeric: tabular-nums;
  /* Default, not pointer: for a visitor with no panel behind it there is
     nothing to click, and a pointer would promise otherwise. */
  cursor: default;
  transition: color 0.15s ease, background-color 0.15s ease;
}
.hdr-eye-btn svg { width: 16px; height: 16px; opacity: 0.8; }
.hdr-eye-btn.is-clickable { cursor: pointer; }
.hdr-eye-btn.is-clickable:hover,
.hdr-eye-btn[aria-expanded="true"] {
  color: var(--text-strong);
  background: #FFFFFF14;
}
.hdr-eye-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.hdr-eye-panel {
  position: absolute;
  top: calc(100% + 0.5rem);
  left: 0;
  z-index: 60;
  min-width: 15rem;
  max-height: 22rem;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 0.6rem;
  background: #0F1113F5;
  backdrop-filter: blur(12px);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: 0 18px 40px #00000080;
  animation: hdr-pop 0.16s cubic-bezier(0.2, 0.9, 0.3, 1);
}
.hdr-eye-panel[hidden] { display: none; }
.eye-head {
  font-family: var(--mono);
  font-size: 0.7rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
  padding: 0 0.15rem 0.5rem;
  border-bottom: 1px solid var(--line-soft);
}
.eye-list { list-style: none; margin: 0.4rem 0 0; padding: 0; display: flex; flex-direction: column; }
.eye-list li {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: 0.5rem;
  padding: 0.3rem 0.15rem;
  font-size: 0.85rem;
}
.eye-list img { width: 24px; height: 24px; border-radius: 100px; }
.eye-name { color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.eye-when {
  font-family: var(--mono);
  font-size: 0.68rem;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.eye-note { margin: 0.5rem 0 0; padding: 0 0.15rem; font-size: 0.76rem; color: var(--muted); }

.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; }
/*
 * The supporter pill, sitting inside .lb-name so it ellipsises with the name
 * rather than pushing the value off the row. Colour comes from an inline style
 * (the tier's own), so only the shape is set here.
 */
.lb-sup {
  display: inline-block;
  margin-left: 0.4rem;
  padding: 0 0.32rem;
  border: 1px solid currentColor;
  border-radius: 3px;
  font-family: var(--mono);
  font-size: 0.6rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  vertical-align: 1px;
  /* Never let a badge squeeze the name out of a narrow card. */
  flex: none;
}
.lb-val {
  font-family: var(--mono);
  font-size: 0.8rem;
  color: var(--accent);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* The supporter wall reuses the team page's group/tier shell; only the list of
   names is its own, because supporters have no avatars or roles to show. */
.sup-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  gap: 0.35rem 1rem;
}
.sup-list li {
  color: var(--text);
  padding: 0.3rem 0;
  border-bottom: 1px solid var(--line, rgba(255, 255, 255, 0.06));
  overflow: hidden;
  text-overflow: ellipsis;
  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; }
/* Members: name, then four right-aligned columns. */
.dash-table--member th:nth-child(n+2) { text-align: right; }
.dash-table--member .dash-t-name {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  max-width: 18rem;
}
.dash-t-av { border-radius: 100px; flex: none; }
/* "Last on site" is the column this table was asked for, so it reads a step
   brighter than the two dates either side of it. */
.dash-table--member .dash-t-seen { color: var(--text-strong); }
.dash-table--member tr.is-idle .dash-t-seen { color: var(--muted); }
/* Linked but never active — present, but not competing for attention with the
   people who actually use the site. */
.dash-table--member tr.is-idle { color: var(--muted); }
.dash-table--member tr.is-idle .dash-t-name { color: var(--muted); }
.dash-table--member tr.is-idle .dash-t-av { opacity: 0.5; }
.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. */
/* ------------------------------------------------------------ jump links */

/*
 * A sticky row of section links, used on the long Information page.
 *
 * top: 68px is the header's own height (.site-header .shell), so this parks
 * directly beneath it rather than sliding under it. z-index sits below the
 * header's 50 for the same reason.
 */
.jump {
  position: sticky;
  top: 68px;
  z-index: 40;
  background: #0A0B0DE6;
  backdrop-filter: blur(12px) saturate(1.1);
  border-bottom: 1px solid var(--line-soft);
}
.jump .shell {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  padding-top: 0.55rem;
  padding-bottom: 0.55rem;
  /* Scrollable rather than wrapping: on a narrow screen a wrapped second row
     would push the content down the page on every single scroll frame. */
  overflow-x: auto;
  scrollbar-width: none;
}
.jump .shell::-webkit-scrollbar { display: none; }
.jump a {
  flex: none;
  font-family: var(--mono);
  font-size: 0.72rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
  padding: 0.4rem 0.85rem;
  border-radius: 100px;
  border: 1px solid transparent;
  white-space: nowrap;
  transition: color 0.15s ease, background-color 0.15s ease, border-color 0.15s ease;
}
.jump a:hover {
  color: var(--text-strong);
  background: #FFFFFF14;
  text-decoration: none;
}
.jump a.is-active {
  color: var(--accent);
  background: color-mix(in srgb, var(--accent) 14%, transparent);
  border-color: color-mix(in srgb, var(--accent) 40%, transparent);
}

/*
 * Anchor targets have to clear BOTH sticky bars.
 *
 * Without this an #anchor jump scrolls the heading to y=0, which is underneath
 * the 68px header and the jump bar on top of it — you land on a section whose
 * title you cannot see, which reads as the link being broken.
 */
#faq,
#callsigns,
#levels { scroll-margin-top: 140px; }

.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; }

/* ---------------------------------------------------------------- checkout */
/*
 * The checkout page. Deliberately a two-column layout on desktop: the order
 * summary is OURS (our type, our wording) and sits beside the Tebex payment
 * frame, so the page still reads as this site with a payment widget in it
 * rather than as somebody else's checkout wearing our header.
 *
 * On narrow screens the summary goes above the frame, because a 760px frame
 * pushed below a sidebar would put the thing you came to do off-screen.
 */
.checkout-layout {
  display: grid;
  gap: 1.5rem;
  grid-template-columns: 1fr;
  align-items: start;
}

@media (min-width: 900px) {
  .checkout-layout { grid-template-columns: minmax(220px, 300px) 1fr; }
}

.checkout-side {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 1.25rem;
}

.checkout-side:empty { display: none; }

.checkout-items {
  list-style: none;
  margin: 0 0 1rem;
  padding: 0;
  display: grid;
  gap: 0.5rem;
}

.checkout-items li {
  display: flex;
  justify-content: space-between;
  gap: 0.75rem;
  font-size: 0.95rem;
}

.checkout-total {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 0.75rem;
  margin: 0;
  padding-top: 0.85rem;
  border-top: 1px solid var(--line-soft);
}

.checkout-total strong { font-size: 1.25rem; }

/* Min-height so the panel does not collapse to nothing and jump the page when
   the frame loads into it. */
.checkout-frame {
  min-height: 420px;
  border-radius: var(--radius);
  overflow: hidden;
}

.checkout-frame iframe {
  width: 100%;
  border: 0;
  display: block;
}

/* -------------------------------------------------------------------- cart */
/* Header cart. Hidden until it holds something — see layout.js. */
.cart-btn { display: inline-flex; align-items: center; gap: 0.4rem; }
.cart-count {
  display: inline-grid;
  place-items: center;
  min-width: 1.35rem;
  height: 1.35rem;
  padding: 0 0.35rem;
  border-radius: 100px;
  background: var(--accent, #c8a04a);
  color: #14181d;
  font-size: 0.72rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.cart-title { font-size: 1rem; margin: 0 0 0.85rem; }

.cart-items { list-style: none; margin: 0 0 1rem; padding: 0; display: grid; gap: 0.65rem; }

/* Thumb / name / price / remove. The name column is the flexible one so long
   pack names wrap instead of pushing the price off the panel. */
.cart-item {
  display: grid;
  grid-template-columns: auto 1fr auto auto;
  align-items: center;
  gap: 0.6rem;
  font-size: 0.92rem;
}

.cart-thumb { width: 34px; height: 34px; object-fit: cover; border-radius: var(--radius-sm); }
.cart-name { min-width: 0; }
.cart-price { font-variant-numeric: tabular-nums; white-space: nowrap; }

.cart-remove {
  background: none;
  border: 0;
  color: var(--muted, #9aa7b4);
  font-size: 1.15rem;
  line-height: 1;
  padding: 0.15rem 0.35rem;
  cursor: pointer;
  border-radius: var(--radius-sm);
}
.cart-remove:hover { color: #fff; background: rgba(255,255,255,0.08); }
.cart-remove:disabled { opacity: 0.4; cursor: default; }

.cart-line {
  display: flex;
  justify-content: space-between;
  margin: 0 0 0.4rem;
  font-size: 0.9rem;
  color: var(--muted, #9aa7b4);
}

/*
 * Store pack buttons.
 *
 * Blue, unlike .btn--primary which stays white site-wide on purpose (see the
 * note there: white outranks any colour on a near-black ground, and it keeps
 * red and blue reserved for meaning). The store is the one page where the
 * accent IS the meaning — there is exactly one thing to press on each card.
 */
.btn--cart {
  background: var(--accent);
  color: #08111F;
}
.btn--cart:hover { background: var(--accent-soft); color: #08111F; }

/*
 * Already in the cart. Deliberately still a button and still clickable — it
 * goes to the checkout — because a disabled control gives no answer to "so
 * where did it go?". Quieter than the call to action so the eye moves past it
 * to the packs not yet chosen.
 */
.btn--cart.is-in-cart {
  background: transparent;
  color: var(--accent-soft);
  box-shadow: inset 0 0 0 1px var(--accent-dim);
}
.btn--cart.is-in-cart:hover {
  background: rgba(76, 141, 246, 0.12);
  color: var(--accent-soft);
}
.btn--cart.is-in-cart::before { content: '\2713'; margin-right: 0.15rem; }

/* ------------------------------------------------- pack full description */
/*
 * The "View description" panel renders the SAME HTML authored on Tebex, which
 * is heavily inline-styled for Tebex's own dark store (59 inline styles on the
 * Retro pack alone). That mostly suits this site, so the styling here does not
 * fight it — it only contains the three things that would otherwise break the
 * card layout.
 */
.btn-desc {
  display: block;
  width: 100%;
  margin-top: 0.5rem;
  padding: 0.5rem;
  background: none;
  border: 0;
  color: var(--muted, #9aa7b4);
  font: inherit;
  font-size: 0.85rem;
  cursor: pointer;
  border-radius: var(--radius-sm);
}
.btn-desc:hover { color: #fff; background: rgba(255,255,255,0.05); }

.pack-desc {
  margin-top: 0.9rem;
  padding-top: 0.9rem;
  border-top: 1px solid var(--line-soft);
  /* Long descriptions would otherwise make one card metres taller than the
     others in the same row. */
  max-height: 30rem;
  overflow-y: auto;
}

/* 1. Images are sized for a full-width store page, not a card column. */
.pack-desc img { max-width: 100%; height: auto; border-radius: var(--radius-sm); }

/* 2. The description opens with its own <h1> — correct on a Tebex package page,
      wrong inside a card that already has an <h3> above it. Demoted visually
      without touching the markup, so the heading order still reads sensibly. */
.pack-desc h1 { font-size: 1.15rem !important; margin: 0 0 0.5rem !important; }
.pack-desc h2 { font-size: 1.05rem !important; }

/* 3. Nothing in there may push the card sideways. */
.pack-desc * { max-width: 100%; box-sizing: border-box; }
.pack-desc table { display: block; overflow-x: auto; }

/*
 * The live Tebex descriptions open with a full-PAGE wrapper —
 * max-width:980px; margin:0 auto; padding:28px 24px; background:#0e1218 —
 * which is right on a Tebex package page and wrong inside a card column, where
 * it reads as a box within a box and wastes a quarter of the width on padding.
 *
 * !important is unavoidable: those are inline styles, and nothing else beats
 * them. Scoped to the panel's own first child so it cannot leak anywhere.
 */
.pack-desc > div:first-child {
  max-width: none !important;
  margin: 0 !important;
  padding: 0 !important;
  background: transparent !important;
}

/* ---------------------------------------------------------- package page */
.pack-buy {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.85rem;
}
.pack-buy .btn { min-width: 12rem; }
.pack-buy--foot {
  margin-top: 2rem;
  padding-top: 1.5rem;
  border-top: 1px solid var(--line-soft);
}
.pack-price { font-size: 1.6rem; font-weight: 800; }

.pack-shot {
  width: 100%;
  max-width: 980px;
  border-radius: var(--radius);
  margin-bottom: 1.75rem;
}

/*
 * On the package page the description is the content, not an aside — so it gets
 * the page's reading width and no scroll box, unlike the card variant.
 */
.pack-desc--page {
  max-height: none;
  overflow: visible;
  border-top: 0;
  padding-top: 0;
  margin-top: 0;
  max-width: 980px;
}
.pack-desc--page h1 { font-size: 1.6rem !important; }

/* ------------------------------------------------------------------ sale
   The catalogue has always carried `onSale` and the discount amount, and until
   now nothing rendered either of them. The old price stays on screen struck
   through, because "50% off" only means something next to the number it is off. */
.pack-badge {
  position: absolute;
  top: 0.7rem;
  left: 0.7rem;
  z-index: 2;
  padding: 0.3rem 0.55rem;
  border-radius: var(--radius-sm);
  background: var(--good);
  color: #08130d;
  font-family: var(--display);
  font-size: 0.95rem;
  letter-spacing: 0.04em;
  line-height: 1;
  text-transform: uppercase;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.45);
}
/* On a package page there is no card corner to pin to, so it sits in the flow
   under the title instead. */
.pack-badge--inline {
  position: static;
  display: inline-block;
  box-shadow: none;
}
/* The card is the positioning context for the corner badge. Set here rather
   than on .pack-card's own rule so this block is self-contained and removing
   the sale styling cannot leave a stray `position` behind. */
.pack-card { position: relative; }

/* The struck-through original, beside the price it is no longer. */
.pc-was {
  margin-left: 0.45rem;
  color: var(--muted);
  text-decoration: line-through;
  font-variant-numeric: tabular-nums;
  font-size: 0.95rem;
}

/* A card on sale earns a little more presence in the grid. */
.pack-card.is-sale { border-color: color-mix(in srgb, var(--good) 45%, var(--line-soft)); }

/* -------------------------------------------------- store copy we author
   Markup written by us in data/store-extras.json. Unlike the Tebex HTML above,
   which has to be wrestled into shape with !important, this can rely on real
   class names - so a pack write-up reads as a designed page rather than a wall
   of bullets. */
.pack-desc .pk-lead { font-size: 1.05rem; margin: 0 0 1.1rem; }

/* The one argument the page is making, pulled out of the prose so it is read
   before the list rather than after it. */
.pk-hook {
  margin: 1rem 0 1.6rem;
  padding: 1rem 1.15rem;
  border: 1px solid var(--line-soft);
  border-left: 3px solid var(--accent);
  border-radius: var(--radius-sm);
  background: rgba(76, 141, 246, 0.06);
}
.pack-desc .pk-hook p { margin: 0; }
.pk-hook-k {
  display: block;
  font-size: 0.7rem;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--accent-soft);
  margin-bottom: 0.45rem;
}
.pack-desc .pk-hook-sub {
  margin-top: 0.65rem;
  font-size: 0.9rem;
  color: var(--muted);
}

/* Name and price on one line, the descriptor beneath - so the eye can run down
   the prices without reading every line. */
.pk-ledger { list-style: none; margin: 0.8rem 0 1.3rem; padding: 0; }
.pk-ledger li {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 0 1rem;
  padding: 0.7rem 0;
  border-bottom: 1px solid rgba(255, 255, 255, 0.06);
}
.pk-ledger li:last-child { border-bottom: 0; }
.pk-ledger a { font-weight: 600; text-decoration: none; }
.pk-ledger a:hover { text-decoration: underline; }
.pk-price {
  text-align: right;
  white-space: nowrap;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.pk-note {
  grid-column: 1 / -1;
  margin-top: 0.15rem;
  font-size: 0.875rem;
  line-height: 1.45;
  color: var(--muted);
}

/* The sum, laid out like a receipt because that is the argument. */
.pk-total {
  margin: 1.3rem 0 1.6rem;
  padding: 0.95rem 1.15rem;
  border: 1px solid var(--line-soft);
  border-radius: var(--radius-sm);
  background: rgba(255, 255, 255, 0.02);
}
.pk-total-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 1rem;
  padding: 0.32rem 0;
  font-variant-numeric: tabular-nums;
}
.pk-total-row small { color: var(--muted); font-weight: 400; margin-left: 0.35rem; }
.pk-strike { color: var(--muted); text-decoration: line-through; }
.pk-total-row--now {
  margin-top: 0.4rem;
  padding-top: 0.65rem;
  border-top: 1px solid rgba(255, 255, 255, 0.08);
  font-size: 1.1rem;
  font-weight: 600;
}
.pk-total-row--save { color: var(--good); font-weight: 600; }

/* Prose lists that are notes, not navigation: a dot instead of a bullet, and
   room to breathe between them. */
.pk-notes { list-style: none; margin: 0.6rem 0 0; padding: 0; }
.pk-notes li { position: relative; padding-left: 1.15rem; margin-bottom: 0.65rem; }
.pk-notes li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.6em;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--muted);
}
.pk-steps { margin: 0.6rem 0 0; padding-left: 1.2rem; }
.pk-steps li { margin-bottom: 0.65rem; padding-left: 0.2rem; }

.pack-gallery {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 0.85rem;
  margin-top: 1.75rem;
  max-width: 980px;
}
.pack-gallery img { width: 100%; border-radius: var(--radius-sm); display: block; }

/* On the package page the description link is a plain link, not a full-width
   card control. */
.pack-card .btn-desc { text-decoration: none; text-align: center; }

/* ---------------------------------------------------------------- xp preview
   The "what would this pack do for me" table on the store page. Rows are the
   only thing separating the packs, so the divider does the work a heading row
   would otherwise need to. */
.xp-preview-table td {
  padding: 0.5rem 0.25rem;
  border-bottom: 1px solid rgba(255, 255, 255, 0.07);
  font-variant-numeric: tabular-nums;
}
.xp-preview-table tr:last-child td { border-bottom: 0; }
.xp-preview-table td:nth-child(2) { text-align: right; white-space: nowrap; }
.xp-preview-table td:nth-child(3) { text-align: right; white-space: nowrap; width: 8rem; }
.xp-preview-table { width: 100%; border-collapse: collapse; }
.xp-preview-table th {
  padding: 0 0.25rem 0.4rem;
  text-align: left;
  font-size: 0.75rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted, rgba(255, 255, 255, 0.55));
  font-weight: 600;
}
.xp-preview-table th:nth-child(2),
.xp-preview-table th:nth-child(3) { text-align: right; }
@media (max-width: 30rem) {
  .xp-preview-table td:nth-child(3),
  .xp-preview-table th:nth-child(3) { display: none; }
}

/* --------------------------------------------------------------- xp worth
   The same question on a package's OWN page. Server-rendered as a generic
   reference table, swapped for the reader's real numbers by xp-preview.js once
   they sign in - so the block is never empty and never lies. */
.xp-worth { margin-top: 1.5rem; }
.xp-figure {
  font-family: var(--display, inherit);
  font-size: clamp(2.25rem, 7vw, 3.25rem);
  line-height: 1;
  margin: 0.25rem 0 0.15rem;
  font-variant-numeric: tabular-nums;
}
.xp-gate {
  margin-top: 1rem;
  padding-top: 1rem;
  border-top: 1px solid rgba(255, 255, 255, 0.08);
}
.xp-gate .btn { width: 100%; }
@media (min-width: 34rem) {
  .xp-gate .btn { width: auto; }
}
