/* ============================================================================
   xkillerbees.com

   Keyed to the xKillerbees mascot mark: black, one gold, and chrome. Grounds
   are near-black with a warm bias so the gold reads as part of the same family
   rather than a highlighter laid over cool grey. Dark is the theme; the light
   variant exists only behind the toggle.

   Gold is the chrome, not the page. The masthead, the wordmark, the utility
   strip, the drip and the footer are gold on every page and never anything
   else — that is the mark, and it is what makes the site one site. Everything
   inside .shell takes the hue of the section it belongs to instead.

   This is not the old cycling, which was deleted for good reason and is not
   coming back: nothing here picks a colour by position. A hue always answers
   the same question — *what does this belong to* — so /minecraft is green
   wherever you meet it, a card pointing at /minecraft is green on any page
   that lists it, and the nav's current-section slab is the colour of the page
   under it. One thing on screen is coloured by where it goes, and two things
   that go to the same place are the same colour. A grid of eight cards in
   eight hues is a map; a grid of eight cards in eight hues assigned by
   nth-child is a paint chart.

   The --c-* set carries all of it: section chrome through --sect, the strands
   on the timeline and the campaign logs through build.js's TRACK map, and the
   cards through data-sec. Both of the latter print a key. The values are warm
   and muted so they sit beside the gold instead of fighting it.

   The site is a maintained record — server addresses, modpack versions, patch
   dates — so numbers are mono and tabular throughout.
   ========================================================================= */

@font-face{font-family:'Plex Sans';src:url('/fonts/plex-sans-400.woff2') format('woff2');
  font-weight:400;font-style:normal;font-display:swap}
@font-face{font-family:'Plex Sans';src:url('/fonts/plex-sans-600.woff2') format('woff2');
  font-weight:600;font-style:normal;font-display:swap}
@font-face{font-family:'Plex Cond';src:url('/fonts/plex-cond-600.woff2') format('woff2');
  font-weight:600;font-style:normal;font-display:swap}
@font-face{font-family:'Plex Cond';src:url('/fonts/plex-cond-700.woff2') format('woff2');
  font-weight:700;font-style:normal;font-display:swap}
@font-face{font-family:'Plex Mono';src:url('/fonts/plex-mono-400.woff2') format('woff2');
  font-weight:400;font-style:normal;font-display:swap}
@font-face{font-family:'Plex Mono';src:url('/fonts/plex-mono-500.woff2') format('woff2');
  font-weight:500;font-style:normal;font-display:swap}

/* ------------------------------------------------------------- tokens ---- */
:root{
  --bg:#0a0908;
  --surface:#161311;
  --raised:#1f1b16;
  --ink-bg:#070605;
  --text:#f0ebe1;
  --muted:#a89d89;
  --soft:#8d8271;   /* warm counterpart of the old slate --soft, same AA job */
  --rule:#2a2520;
  --rule-strong:#3d362c;

  --accent:#ffc20e;
  --accent-hover:#ffdb5e;
  --accent-wash:rgba(255,194,14,.12);
  --accent-glow:rgba(255,194,14,.26);

  --go:#4ade80;
  /* orange, not amber: the old #f59e0b is now within a few degrees of the
     accent, and a warning that looks like a link is not a warning */
  --warn:#fb923c;
  --stop:#f87171;
  --cool:#7ba7e8;

  /* The section hues. Each one is a place on the site first and a strand on
     the timeline second — the same colour does both jobs, which is the only
     reason the timeline's key is legible without one. Warm-leaning and
     desaturated so none of them reads as the brand gold. */
  /* The front door is the brand's own page, so it keeps the brand's colour —
     the one section whose hue is the gold rather than beside it. */
  --c-home:#ffc20e;
  --c-roster:#6fc7c0;     /* strand only: "who joined" */
  --c-foundry:#d9789a;    /* also: story beats */
  --c-ff14:#7ba7e8;
  /* Deeper and redder than the gold on purpose. WoW's own colour is this
     bronze, and at #d9a441 a whole page of it read as the brand accent with
     the lights down — which on the one site-wide hue is the one thing a
     section colour must not do. */
  --c-wow:#c67d3e;
  --c-minecraft:#6fbf73;
  --c-legacy:#c98bd9;     /* also: "everything else", and campaign fights */
  --c-projects:#e07a4a;   /* also: the group itself, and character moments */
  /* The spine is deliberately the neutral: every strand's colour runs down
     this page and a hue of its own would be a seventh one competing. Warm bone
     rather than the old #a89d89, which sat close enough to --muted that a
     heading in it read as disabled text rather than as a choice. */
  --c-timeline:#cdbfa4;
  /* The exception: the arcade chrome is the brand gold outright. It is the one
     section named after the site, and a bee game in any other hue reads as
     borrowed. The whole arcade UI keys off this. */
  --c-arcade:#ffc20e;

  /* the wordmark's finish, reused on display type so the logo does not look
     pasted onto someone else's page */
  --chrome:linear-gradient(178deg,#ffffff 0%,#eef1f6 28%,#9aa2b0 50%,
    #c9cfd9 58%,#f7f9fc 78%,#ffffff 100%);
  --chrome-edge:#14110c;

  --shadow:0 1px 2px rgba(0,0,0,.6),0 8px 28px rgba(0,0,0,.45);
  --shadow-lift:0 2px 4px rgba(0,0,0,.6),0 16px 44px rgba(0,0,0,.55);

  --sans:'Plex Sans',ui-sans-serif,system-ui,-apple-system,'Segoe UI',Roboto,sans-serif;
  --cond:'Plex Cond','Plex Sans',ui-sans-serif,system-ui,sans-serif;
  --mono:'Plex Mono',ui-monospace,'SF Mono',Menlo,Consolas,monospace;

  /* the shear on every slab-shaped control — nav pill, buttons, band heads.
     One angle everywhere, or it reads as a mistake rather than a style. */
  --skew:-11deg;

  /* The masthead's height, named because the nav drawer hangs off the bottom of
     it and has to know how much of the window is left below — and because
     anything scrolled to by an in-page link has to clear it. See the
     `scroll-margin-top` rule further down. */
  --mast-h:4.4rem;
  /* the height of the contents strip once it is stuck against the masthead —
     one row of pills, whatever the page's section count */
  --jump-h:4.25rem;

  --rail:16.5rem;
  --measure:68ch;
  /* the column itself: prose stops at --measure, media and card grids fill this */
  --wide:58rem;
  --step:clamp(.9rem,.86rem + .2vw,.98rem);
}

/* The dark palette above is the theme. This is the opt-in variant. */
:root[data-theme="light"]{
  /* Warm paper rather than blue-white. A straight inversion of the dark theme
     lands on cool grey, and gold on cool grey goes muddy; on cream it stays
     gold. The accent darkens to a bronze because #ffc20e on paper is
     unreadable — same hue, the contrast the ground demands. */
  --bg:#f7f3ea;
  --surface:#fffdf8;
  --raised:#efe8db;
  --ink-bg:#efe8d9;
  --text:#1a1611;
  --muted:#5b5245;
  --soft:#6b6153;
  --rule:#e2d9c7;
  --rule-strong:#c9bda6;

  --accent:#8a5a00;
  --accent-hover:#6b4600;
  --accent-wash:rgba(138,90,0,.10);
  --accent-glow:rgba(138,90,0,.18);

  --go:#166534;
  --warn:#9a3412;
  --stop:#b91c1c;
  --cool:#1d4ed8;

  /* the same hues, darkened to clear AA on paper */
  --c-home:#8a5a00;
  --c-roster:#0f6d66;
  --c-foundry:#a8325c;
  --c-ff14:#1d4ed8;
  --c-wow:#8a4a15;
  --c-minecraft:#166534;
  --c-legacy:#6d28d9;
  --c-projects:#9a3412;
  --c-timeline:#6b5b3e;
  --c-arcade:#8a5a00;

  /* chrome needs a dark keyline to read on paper; on cream it becomes a
     graphite bevel instead of a silver one */
  --chrome:linear-gradient(178deg,#4a4238 0%,#2b251d 30%,#6b6153 52%,
    #2b251d 72%,#4a4238 100%);
  --chrome-edge:#fffdf8;

  --shadow:0 1px 2px rgba(43,37,29,.07),0 6px 20px rgba(43,37,29,.08);
  --shadow-lift:0 2px 4px rgba(43,37,29,.09),0 14px 36px rgba(43,37,29,.14);
}

/* ------------------------------------------------------------- accent ----
   Every element that carries its own --accent re-derives the tints from it.
   The derived values have to be declared on the same elements as --accent, not
   once on :root — a custom property inherits its *computed* value, so a child
   that overrides --accent would otherwise keep its parent's wash. */
[data-section],[data-sec],.shell,.cards>.card,.linklist>li{
  --accent-hover:color-mix(in srgb,var(--accent) 66%,#fff);
  --accent-wash:color-mix(in srgb,var(--accent) 14%,transparent);
  --accent-glow:color-mix(in srgb,var(--accent) 30%,transparent);
}
:root[data-theme="light"] [data-section],
:root[data-theme="light"] [data-sec],
:root[data-theme="light"] .shell,
:root[data-theme="light"] .cards>.card,
:root[data-theme="light"] .linklist>li{
  --accent-hover:color-mix(in srgb,var(--accent) 76%,#000);
  --accent-wash:color-mix(in srgb,var(--accent) 10%,transparent);
  --accent-glow:color-mix(in srgb,var(--accent) 22%,transparent);
}

/* --------------------------------------------------------- section hue ----
   `--sect` is the colour of the page you are on. It is declared on <body> and
   deliberately NOT applied there: body keeps the gold, so the utility strip,
   the masthead, the drip and the footer are the same on every page. .shell is
   where the page starts, and that is where --accent becomes the section's.

   Anything that wants the page's colour outside .shell — the top wash, the
   nav's current slab — reaches for --sect by name rather than being handed it,
   so a new piece of chrome cannot pick the hue up by accident. */
body{--sect:var(--accent)}
body[data-section="home"]{--sect:var(--c-home)}
body[data-section="ff14"]{--sect:var(--c-ff14)}
body[data-section="wow"]{--sect:var(--c-wow)}
body[data-section="minecraft"]{--sect:var(--c-minecraft)}
body[data-section="foundry"]{--sect:var(--c-foundry)}
body[data-section="legacy"]{--sect:var(--c-legacy)}
body[data-section="projects"]{--sect:var(--c-projects)}
body[data-section="timeline"]{--sect:var(--c-timeline)}
body[data-section="arcade"]{--sect:var(--c-arcade)}
.shell{--accent:var(--sect)}

/* Anything that names a section takes that section's hue — a timeline entry, a
   key swatch, a nav item, a card pointing at it. One rule set for all four, so
   a card and the nav item it duplicates cannot drift apart. Only one nav item
   is current or in-branch at a time, so the bar still shows a single colour. */
[data-sec="home"]{--accent:var(--c-home)}
[data-sec="roster"]{--accent:var(--c-roster)}
[data-sec="ff14"]{--accent:var(--c-ff14)}
[data-sec="wow"]{--accent:var(--c-wow)}
[data-sec="minecraft"]{--accent:var(--c-minecraft)}
[data-sec="legacy"]{--accent:var(--c-legacy)}
[data-sec="projects"]{--accent:var(--c-projects)}
[data-sec="foundry"]{--accent:var(--c-foundry)}
[data-sec="timeline"]{--accent:var(--c-timeline)}
[data-sec="arcade"]{--accent:var(--c-arcade)}

/* --------------------------------------------------------------- base ---- */
*,*::before,*::after{box-sizing:border-box}
html{-webkit-text-size-adjust:100%}
body{
  margin:0;background:var(--bg);color:var(--text);
  font:400 var(--step)/1.68 var(--sans);
  text-rendering:optimizeLegibility;
  position:relative;min-height:100vh;
  /* The full-bleed openers are laid out against 100vw, which counts the
     scrollbar the content column does not have, so their margin box overhangs
     the window by the scrollbar's width and the page scrolls sideways for it.
     `clip`, not `hidden`: hidden would make the body a scroll container and
     take the masthead's stickiness with it. */
  overflow-x:clip;
}
/* Two light sources over the top of every page: the section's own hue on the
   left, the brand gold on the right. The gold one is fixed rather than derived
   — it is the mark's light and it falls on every page, which is what keeps a
   green page and a pink page recognisably the same site. */
body::before{
  content:"";position:fixed;inset:0 0 auto;height:32rem;pointer-events:none;z-index:0;
  background:
    radial-gradient(60rem 22rem at 18% -6rem,
      color-mix(in srgb,var(--sect) 30%,transparent),transparent 70%),
    radial-gradient(40rem 18rem at 88% -8rem,rgba(255,194,14,.07),transparent 72%);
  opacity:.7;
}
:root[data-theme="light"] body::before{
  background:
    radial-gradient(60rem 22rem at 18% -6rem,
      color-mix(in srgb,var(--sect) 22%,transparent),transparent 70%),
    radial-gradient(40rem 18rem at 88% -8rem,rgba(138,90,0,.06),transparent 72%);
  opacity:.5;
}
body>*{position:relative;z-index:1}

img{max-width:100%;height:auto}
a{color:var(--accent);text-decoration-thickness:1px;text-underline-offset:.18em}
a:hover{color:var(--accent-hover)}
:focus-visible{outline:2px solid var(--accent);outline-offset:2px;border-radius:3px}

.skip{position:absolute;left:-9999px;top:0;z-index:100;background:var(--surface);
  color:var(--text);padding:.6rem 1rem;border:1px solid var(--rule);border-radius:4px}
.skip:focus{left:.5rem;top:.5rem}

/* Every `#` anchor on the site landed its heading *behind* the masthead.
   The bar is `position:sticky` at `top:0`, so scrolling a heading to y=0 puts
   it under the bar rather than at the top of what you can see — which is every
   band-head's own anchor link on every page, and the arcade's "how the boards
   work" jump. Keyed off `--mast-h` so it cannot drift from the thing it is
   clearing, plus a little air. */
[id]{scroll-margin-top:calc(var(--mast-h) + 1rem)}
/* A page with a contents strip has a *second* sticky bar over the same line,
   and the strip is the one thing on the page whose whole job is scrolling to
   headings — so on those pages everything clears both. `--jump-h` is the
   stuck height, which is a single row of pills and does not depend on how many
   there are. */
main:has(.jump) [id]{scroll-margin-top:calc(var(--mast-h) + var(--jump-h) + .75rem)}

/* ----------------------------------------------------------- masthead ---- */
.masthead{
  position:sticky;top:0;z-index:40;
  display:flex;align-items:center;gap:1rem;
  padding:0 clamp(1rem,3vw,2rem);height:var(--mast-h);
  background:color-mix(in srgb,var(--bg) 88%,transparent);
  backdrop-filter:saturate(1.6) blur(14px);
  border-bottom:1px solid var(--rule);
}
/* Was the whole section palette laid along the edge in nav order. With one
   accent there is nothing to lay out, so the edge is gold and the honey hangs
   off it — the logo's own wet underside, on every page. */
.masthead::after{
  content:"";position:absolute;left:0;right:0;bottom:-2px;height:2px;
  background:var(--accent);
}
/* The drip hangs off the gold edge. It lives in a clip so that narrow windows
   can run it at its own scale — see the note in the responsive block — without
   the overhang scrolling the page sideways. */
.drip-wrap{
  position:absolute;left:0;right:0;top:100%;
  height:1.5rem;overflow:hidden;pointer-events:none;z-index:1;
}
.drip{
  display:block;width:100%;height:100%;
  fill:var(--accent);
}
.drip .lo{fill:color-mix(in srgb,var(--accent) 78%,#000)}

.brand{display:flex;align-items:center;gap:.6rem;text-decoration:none;color:var(--text);margin-right:auto}
.brand-mark{
  width:2rem;height:2rem;flex:none;border-radius:5px;
  filter:drop-shadow(0 0 10px var(--accent-glow));
  transition:transform .18s ease;
}
.brand:hover .brand-mark{transform:scale(1.06) rotate(-4deg)}
.brand-name{font:700 1.12rem/1 var(--cond);letter-spacing:.005em;text-transform:uppercase}
.brand-name b{color:var(--accent);font-weight:700}

.nav-toggle{
  display:none;align-items:center;gap:.45rem;
  font:500 .76rem/1 var(--mono);color:var(--muted);
  background:var(--surface);border:1px solid var(--rule);border-radius:4px;
  padding:.44rem .62rem;cursor:pointer;transition:color .14s,border-color .14s;
}
.nav-toggle:hover{color:var(--text);border-color:var(--accent)}
.nav-toggle span{display:block;width:.8rem;height:1px;background:currentColor;
  box-shadow:0 4px 0 currentColor,0 -4px 0 currentColor}

/* ---------------------------------------------------------- head tools ----
   Skewed slabs, the same angle as every other control. The arcade badge is a
   live count off the games registry, not a decoration. */
.head-tools{display:flex;align-items:center;gap:.4rem;padding-left:.7rem;
  border-left:1px solid var(--rule)}
.icon-btn{
  position:relative;display:grid;place-items:center;
  width:2.3rem;height:2.3rem;flex:none;
  background:var(--raised);color:var(--muted);
  border:0;border-radius:0;padding:0;cursor:pointer;
  transform:skewX(var(--skew));
  transition:background .16s,color .16s;
}
.icon-btn svg{width:1.05rem;height:1.05rem;fill:currentColor;
  transform:skewX(calc(var(--skew) * -1))}
.icon-btn:hover{background:var(--accent);color:#1a1200}
:root[data-theme="light"] .icon-btn:hover{color:#fffdf8}
.badge{
  position:absolute;top:-.32rem;right:-.32rem;
  min-width:1.1rem;height:1.1rem;padding:0 .2rem;
  display:grid;place-items:center;border-radius:50%;
  background:var(--accent);color:#1a1200;
  font:500 .68rem/1 var(--mono);
  transform:skewX(calc(var(--skew) * -1));
  box-shadow:0 0 0 2px var(--bg);
}
:root[data-theme="light"] .badge{color:#fffdf8}

.theme-toggle{width:auto;padding:0 .7rem;font:700 .68rem/1 var(--cond);
  letter-spacing:.08em;text-transform:uppercase}
.theme-toggle::before{content:"Light";transform:skewX(calc(var(--skew) * -1))}
:root[data-theme="light"] .theme-toggle::before{content:"Dark"}

/* --------------------------------------------------------- utility bar ----
   The strip above the masthead: where to find us on the left, whether
   anything is on fire on the right. */
.util{background:var(--ink-bg);border-bottom:1px solid var(--rule);font-size:.76rem}
.util-in{
  max-width:76rem;margin:0 auto;padding:0 clamp(1rem,3vw,2rem);
  display:flex;align-items:center;justify-content:space-between;
  gap:.75rem;min-height:2.2rem;flex-wrap:wrap;
}
.util-l,.util-r{display:flex;align-items:center;gap:.15rem}
.util .chip{
  display:inline-flex;align-items:center;gap:.4rem;
  padding:.3rem .55rem;color:var(--muted);font-weight:600;
  letter-spacing:.03em;text-decoration:none;transition:color .14s;
}
.util .chip:hover{color:var(--accent)}
.util .chip svg{width:.85rem;height:.85rem;fill:currentColor;flex:none}
/* The one loud thing in the strip. The server-widget embeds that used to sit
   at the foot of nine pages are gone, so this is the site's only door into
   Discord and it has to read as a button rather than another grey label. */
.util .chip-discord{
  color:#120e05;background:var(--accent);border-radius:999px;
  padding:.3rem .7rem;margin-right:.35rem;font-weight:700;letter-spacing:.05em;
  box-shadow:0 0 0 1px var(--accent-glow),0 1px 10px -3px var(--accent-glow);
  transition:background .14s,box-shadow .14s,transform .14s;
}
.util .chip-discord:hover{
  color:#120e05;background:var(--accent-hover);transform:translateY(-1px);
  box-shadow:0 0 0 1px var(--accent-glow),0 3px 14px -3px var(--accent-glow);
}
/* the accent is a bronze on paper, so the pill flips to cream text — the same
   swap .btn makes */
:root[data-theme="light"] .util .chip-discord,
:root[data-theme="light"] .util .chip-discord:hover{color:#fffdf8}
.util .chip-discord:active{transform:none}
.util .chip-discord svg{width:1rem;height:1rem}
@media (prefers-reduced-motion:reduce){
  .util .chip-discord,.util .chip-discord:hover{transition:none;transform:none}
}

/* -------------------------------------------------------------- shell ---- */
.shell{
  max-width:76rem;margin:0 auto;padding:0 clamp(1rem,3vw,2rem);
}

/* ------------------------------------------------------------- topnav ---- */
.topnav{margin-right:auto}
.topnav ul{list-style:none;margin:0;padding:0}
.tn-l0{display:flex;align-items:center;gap:.15rem}
.tn-l0>li{position:relative}
.tn-l0>li>a{
  position:relative;
  display:flex;align-items:center;gap:.3rem;
  font:700 .84rem/1 var(--cond);letter-spacing:.085em;text-transform:uppercase;
  color:var(--muted);text-decoration:none;
  padding:.62rem .78rem;white-space:nowrap;
  transition:color .14s;
}
.tn-l0>li>a:hover,.tn-l0>li>a.in-branch{color:var(--text)}
/* The current section is a skewed gold slab, not a tinted rounded rect. It is
   the loudest thing in the bar on purpose — the nav is nine items wide. */
.tn-l0>li>a[aria-current="page"],
.tn-l0>li>a.in-branch{color:var(--text)}
.tn-l0>li>a[aria-current="page"]{color:#1a1200}
.tn-l0>li>a[aria-current="page"]::before{
  content:"";position:absolute;inset:.24rem -.06rem;z-index:-1;
  background:linear-gradient(100deg,
    color-mix(in srgb,var(--accent) 82%,#000),var(--accent-hover));
  transform:skewX(var(--skew));
}
:root[data-theme="light"] .tn-l0>li>a[aria-current="page"]{color:#fffdf8}
.tn-l0>li>a.in-branch::before{
  content:"";position:absolute;inset:auto .5rem .34rem;height:2px;
  background:var(--accent);transform:skewX(var(--skew));
}
.caret{width:.34rem;height:.34rem;border-right:1.5px solid currentColor;
  border-bottom:1.5px solid currentColor;transform:translateY(-1px) rotate(45deg);
  opacity:.6;transition:transform .16s}
.has-sub:hover>a .caret,.has-sub:focus-within>a .caret{transform:translateY(-2px) rotate(225deg)}

/* dropdown panel — opens on hover and on keyboard focus, no JS required */
.tn-panel{
  position:absolute;top:calc(100% + .45rem);left:0;z-index:50;
  min-width:14rem;padding:.45rem;
  background:var(--surface);border:1px solid var(--rule-strong);
  border-radius:8px;box-shadow:var(--shadow-lift);
  opacity:0;visibility:hidden;transform:translateY(-4px);
  transition:opacity .14s,transform .14s,visibility .14s;
}
.has-sub:hover>.tn-panel,
.has-sub:focus-within>.tn-panel,
.has-sub.tap-open>.tn-panel,
.has-sub>.tn-panel:hover{opacity:1;visibility:visible;transform:none}
/* keeps the pointer from falling through the gap under the trigger */
.tn-panel::before{content:"";position:absolute;left:0;right:0;top:-.5rem;height:.5rem}

.tn-panel a{
  display:block;text-decoration:none;color:var(--muted);
  font-size:.86rem;line-height:1.35;padding:.4rem .6rem;border-radius:5px;
  transition:color .12s,background .12s;
}
.tn-panel a:hover{color:var(--text);background:var(--accent-wash)}
.tn-panel a[aria-current="page"]{color:var(--accent);background:var(--accent-wash);font-weight:600}
/* third level: indented under its parent rather than a second flyout */
.tn-panel .tn-l2{margin:.1rem 0 .25rem;border-left:1px solid var(--rule);margin-left:.75rem}
.tn-panel .tn-l2 a{font-size:.82rem;padding-left:.85rem;color:var(--soft)}
.tn-panel .tn-l2 a:hover{color:var(--text)}
.tn-panel .has-sub>a{font-weight:600;color:var(--text)}

/* --------------------------------------------------------------- main ---- */
main{padding:2.25rem 0 5rem;min-width:0}

/* The column is --wide; only running text stops at --measure. Squeezing card
   and video grids into a reading measure is what made the embeds tiny.
   With the nav overhead there's no left rail to balance against, so the lone
   column would otherwise sit against the left edge of a wide page. */
article{min-width:0;width:100%;max-width:var(--wide);margin-inline:auto}

/* .cabs is a <ul> that is a grid of tiles, not a list of sentences — the
   reading measure would squeeze four cabinets into two. */
article>p,article>ul:not(.linklist):not(.cabs),article>ol,article>figure,
article>blockquote,.hero-copy>p,.hero-copy>ul{max-width:var(--measure)}

.crumb{font:500 .75rem/1 var(--mono);color:var(--soft);
  display:flex;gap:.5rem;flex-wrap:wrap;margin-bottom:1.15rem;
  text-transform:uppercase;letter-spacing:.08em}
.crumb a{color:var(--soft);text-decoration:none}
.crumb a:hover{color:var(--accent)}
.crumb span{color:var(--rule-strong)}

.page-head{margin:0 0 2.1rem;padding-bottom:1.15rem;position:relative;
  border-bottom:1px solid var(--rule)}
.page-head::after{
  content:"";position:absolute;left:0;bottom:-1px;width:4.5rem;height:2px;
  background:var(--accent);box-shadow:0 0 12px var(--accent-glow)}
/* The display tier, and the only place the wordmark's finish is reused: page
   titles take the brushed-chrome gradient with a hard keyline under it, the
   same way the logo's lettering is built. h2 and below stay plain text — a
   page of chrome headings is a car show, not a record. */
.page-head h1{
  font:700 clamp(1.95rem,1.5rem + 1.9vw,2.8rem)/1.06 var(--cond);
  margin:0;letter-spacing:.005em;text-transform:uppercase;text-wrap:balance;
  background:var(--chrome);-webkit-background-clip:text;background-clip:text;
  color:transparent;-webkit-text-fill-color:transparent;
  filter:drop-shadow(0 2px 0 var(--chrome-edge));
  width:fit-content;
}
.subtitle{margin:.55rem 0 0;color:var(--muted);font:500 .78rem/1 var(--mono);
  text-transform:uppercase;letter-spacing:.12em}

article h2,article h3,article h4{
  font-family:var(--cond);font-weight:600;line-height:1.2;
  text-wrap:balance;position:relative}
/* the section rule carries a short leading dash in the section's hue —
   border-image rather than a pseudo-element so h2.patch can stay a flex box */
article h2{font-size:1.5rem;margin:2.7rem 0 .85rem;padding-top:1.15rem;
  border-top:2px solid var(--rule);
  border-image:linear-gradient(90deg,var(--accent) 0 3.5rem,var(--rule) 3.5rem 100%) 1}
article h3{font-size:1.15rem;margin:1.95rem 0 .55rem}
article h4{font-size:.95rem;margin:1.5rem 0 .45rem;color:var(--accent);
  font-family:var(--mono);font-weight:500;text-transform:uppercase;letter-spacing:.1em}
article>h2:first-child,article>h3:first-child{margin-top:0;border-top:0;padding-top:0}

.anchor{position:absolute;left:-1.15rem;top:auto;color:var(--rule-strong);
  text-decoration:none;opacity:0;font-weight:400;transition:opacity .14s}
h2:hover .anchor,h3:hover .anchor,.anchor:focus{opacity:1;color:var(--accent)}

/* several pages link a URL using the URL as its own text; on a phone one of
   those is wider than the viewport and pushes the whole page sideways */
article a{overflow-wrap:break-word}

article p{margin:0 0 1rem}
article ul{margin:0 0 1.2rem;padding-left:1.15rem}
article li{margin:.24rem 0}
article ul li::marker{color:var(--accent);opacity:.65}

figure{margin:1.7rem 0}
figure img{display:block;width:100%;border:1px solid var(--rule);border-radius:6px;
  background:var(--surface);box-shadow:var(--shadow)}
figcaption{margin-top:.55rem;font:400 .82rem/1.45 var(--sans);color:var(--muted)}
/* `![…](… | wide)`. A figure normally stops at the reading measure, which is
   right for a picture beside its paragraph and wrong for one that is a chart:
   a roadmap set at 68ch is unreadable type next to half a column of nothing.
   The caption stays at the measure — it is still prose. */
article>figure.wide{max-width:none}
article>figure.wide>figcaption{max-width:var(--measure)}

code{font:400 .88em/1.4 var(--mono);background:var(--ink-bg);
  padding:.14em .4em;border-radius:4px;font-variant-numeric:tabular-nums;
  border:1px solid var(--rule)}
code.copy{cursor:pointer;color:var(--accent);font-weight:500;
  transition:border-color .14s,background .14s}
code.copy:hover{border-color:var(--accent);background:var(--accent-wash)}
code.copy::after{content:"copy";font-size:.72em;color:var(--soft);margin-left:.55em;
  text-transform:uppercase;letter-spacing:.07em}
code.copy.copied{border-color:var(--go)}
code.copy.copied::after{content:"copied";color:var(--go)}
code.copy.copyfail::after{content:"press ctrl+c";color:var(--warn)}

/* patch entries: version, date, and whether it actually shipped */
article h2.patch,article h3.patch{
  display:flex;align-items:baseline;gap:.65rem;flex-wrap:wrap;
  font-size:1.12rem;margin:2.3rem 0 .65rem;padding-top:1.05rem}
.patch-v{font:500 1.16rem/1 var(--mono);font-variant-numeric:tabular-nums;
  color:var(--accent);letter-spacing:-.02em}
.patch-d{font-family:var(--sans);font-weight:400;font-size:.9rem;color:var(--muted)}
.patch-s{margin-left:auto;font:500 .66rem/1 var(--mono);text-transform:uppercase;
  letter-spacing:.1em;color:var(--soft);border:1px solid var(--rule-strong);
  border-radius:3px;padding:.26rem .44rem;white-space:nowrap}
h2.patch.shipped .patch-s,h3.patch.shipped .patch-s{
  color:var(--go);border-color:currentColor;background:rgba(16,185,129,.08)}

/* -------------------------------------------------------- tab switcher ---- */
/* Two bodies of content on one page, one shown at a time. Both panels are in
   the markup and the inactive one carries `hidden`, so .tab-panel must never
   be given a display rule without the [hidden] line below staying under it —
   an author `display` beats the UA sheet's [hidden]{display:none} at equal
   specificity, and the panel would sit there looking like a stylesheet bug. */
.tabs{margin:0 0 1rem}
.tab-bar{display:flex;gap:.34rem;flex-wrap:wrap;margin:0 0 2rem;padding:.34rem;
  border:1px solid var(--rule);border-radius:9px;background:var(--ink-bg);
  width:fit-content;max-width:100%}
.tab-btn{appearance:none;cursor:pointer;border:1px solid transparent;
  border-radius:6px;padding:.56rem 1.05rem;background:none;color:var(--muted);
  font:500 .72rem/1 var(--mono);text-transform:uppercase;letter-spacing:.11em;
  transition:color .14s,background .14s,border-color .14s}
.tab-btn:hover{color:var(--text)}
.tab-btn[aria-selected="true"]{color:var(--accent);background:var(--accent-wash);
  border-color:var(--rule-strong)}
.tab-panel[hidden]{display:none}
/* the bar above is the separation, so the panel's own first band does not
   also need its 3.4rem of clear air */
.tab-panel>.band-head:first-child{margin-top:0}
/* A panel is a body of content, so prose inside one is still prose: without
   this it runs the full width of the article and a paragraph either side of a
   switch is set to two different measures on the same page. */
.tab-panel>p,.tab-panel>ul:not(.linklist):not(.cabs),.tab-panel>ol,
.tab-panel>blockquote{max-width:var(--measure)}
.tab-panel>figure{max-width:var(--measure)}
.tab-panel>figure.wide{max-width:none}

/* ----------------------------------------------------- patch countdown ---- */
/* Sits above the schedule on any patch-notes page. The numbers come from
   site.js, so the card has to read fine while the clock is still hidden. */
.pcount{border:1px solid var(--rule);border-radius:8px;overflow:hidden;
  background:linear-gradient(180deg,var(--raised),var(--surface));
  box-shadow:var(--shadow);margin:0 0 2.2rem}
.pc-head{display:flex;align-items:center;gap:.5rem;
  font:500 .7rem/1 var(--mono);text-transform:uppercase;letter-spacing:.14em;
  color:var(--muted);padding:.78rem 1rem;border-bottom:1px solid var(--rule);
  background:var(--ink-bg)}
.pc-head::before{content:"";width:.45rem;height:.45rem;border-radius:50%;flex:none;
  background:var(--accent);box-shadow:0 0 0 3px var(--accent-wash)}
.pc-body{display:flex;align-items:baseline;gap:.7rem;flex-wrap:wrap;
  padding:1rem 1rem .35rem}
/* a link on a patch page, where it points at that patch's own entry; plain
   text on a page where the thing being counted to is not elsewhere on it */
.pc-v{font:500 1.5rem/1 var(--mono);font-variant-numeric:tabular-nums;
  letter-spacing:-.03em;color:var(--accent);text-decoration:none}
a.pc-v:hover{text-decoration:underline}
.pc-when{font-size:.9rem;color:var(--muted)}
.pc-clock{display:flex;gap:.5rem;flex-wrap:wrap;padding:.85rem 1rem 1.05rem}
.pc-u{display:flex;flex-direction:column;align-items:center;gap:.34rem;
  min-width:3.9rem;padding:.6rem .45rem;border:1px solid var(--rule);
  border-radius:6px;background:var(--ink-bg)}
.pc-n{font:500 1.5rem/1 var(--mono);font-variant-numeric:tabular-nums;
  letter-spacing:-.03em;color:var(--text)}
.pc-l{font:400 .6rem/1 var(--mono);text-transform:uppercase;letter-spacing:.11em;
  color:var(--soft)}
.pc-state{margin:0;padding:.9rem 1rem 1.05rem;font:500 .78rem/1 var(--mono);
  text-transform:uppercase;letter-spacing:.11em;color:var(--warn)}
.pc-note{margin:0;padding:0 1rem 1.05rem;font-size:.8rem;color:var(--soft)}
@media (max-width:600px){
  .pc-u{flex:1 1 3.9rem}
}

/* ---------------------------------------------------------- changelog ---- */
/* a bare date on its own line: the scan anchor down a long release log */
.logdate{margin:1.9rem 0 .5rem;display:flex;align-items:center;gap:.75rem}
.logdate::after{content:"";flex:1;height:1px;background:var(--rule)}
.logdate time{font:500 .72rem/1 var(--mono);font-variant-numeric:tabular-nums;
  text-transform:uppercase;letter-spacing:.1em;color:var(--soft);
  border:1px solid var(--rule-strong);border-radius:3px;padding:.3rem .5rem;
  white-space:nowrap}
.logdate+.rel{margin-top:0}

.rel{display:flex;align-items:baseline;gap:.55rem;flex-wrap:wrap;margin:0 0 .45rem}
.rel-k{font:500 .66rem/1 var(--mono);text-transform:uppercase;letter-spacing:.11em;
  color:var(--soft)}
.rel-v{font:500 1rem/1 var(--mono);font-variant-numeric:tabular-nums;
  letter-spacing:-.02em;color:var(--accent)}
.rel-x{color:var(--muted);font-size:.86rem;min-width:0;overflow-wrap:anywhere}
.rel-file{font:400 .78rem/1 var(--mono);text-decoration:none;color:var(--soft);
  border:1px solid var(--rule);border-radius:3px;padding:.26rem .45rem;
  display:inline-flex;align-items:center;gap:.28rem;
  transition:color .14s,border-color .14s,background .14s}
.rel-file:hover{color:var(--accent);border-color:var(--accent);background:var(--accent-wash)}

/* ------------------------------------------------------------- status ---- */
/* ---------------------------------------------------------------- rack ----
   The live-servers band. Its heading is a skewed gold slab hanging off the
   top-left corner of the rack, overlapping whatever sits above it. */
.rack{position:relative;margin:0 0 2.4rem}
.rack-head{
  position:relative;z-index:2;
  display:inline-flex;align-items:baseline;gap:.6rem;
  margin:0 0 0 1.4rem;padding:.42rem 1.3rem;
  background:linear-gradient(100deg,
    color-mix(in srgb,var(--accent) 82%,#000),var(--accent-hover));
  color:#1a1200;transform:skewX(var(--skew));
  box-shadow:0 0 0 2px var(--chrome-edge),0 12px 30px -16px var(--accent-glow);
}
:root[data-theme="light"] .rack-head{color:#fffdf8}
.rack-head>*{transform:skewX(calc(var(--skew) * -1))}
.rack-head .d{font:700 .95rem/1 var(--cond);text-transform:uppercase;letter-spacing:.06em}
.rack-head .n{font:500 .72rem/1 var(--mono)}

/* Three abreast, divided by rules rather than stacked as a list — the row
   count is small and fixed, and side by side it reads as a rack of machines.
   The count divides by what is actually running, not by three: build.js sets
   data-cols, so two servers make two halves rather than two thirds and a hole
   where a third server used to be. */
.rack .status{
  margin:-1px 0 0;border-radius:0;
  display:grid;grid-template-columns:repeat(var(--rack-cols,3),minmax(0,1fr));
}
.rack .status[data-cols="1"]{--rack-cols:1}
.rack .status[data-cols="2"]{--rack-cols:2}
.rack .status-row{
  grid-template-columns:auto 1fr;grid-template-rows:auto auto;
  align-content:center;gap:.25rem .9rem;
  border-bottom:0;border-right:1px solid var(--rule);
}
/* Under three the cells are wider than their contents, and a name left-aligned
   against a divider that is no longer beside it reads as a layout that lost
   something. Centre the pair in the cell instead. */
.rack .status[data-cols="1"] .status-row,
.rack .status[data-cols="2"] .status-row{
  grid-template-columns:auto auto;justify-content:center;
}
.rack .status-row:last-child{border-right:0}
.rack .rack-glyph{grid-row:span 2}
.rack .st-state{grid-column:2;justify-self:start}

.status{border:1px solid var(--rule);border-radius:8px;
  background:linear-gradient(180deg,var(--raised),var(--surface));
  margin:0 0 2.2rem;overflow:hidden;box-shadow:var(--shadow)}
.status-row{display:grid;grid-template-columns:auto 1fr auto auto;align-items:center;
  gap:.9rem;padding:1rem 1.2rem;border-bottom:1px solid var(--rule);
  text-decoration:none;color:var(--text);transition:background .14s}
.status-row:last-child{border-bottom:0}
a.status-row:hover{background:var(--accent-wash)}
.rack-glyph{width:2.3rem;height:2.3rem;flex:none;color:var(--accent);display:block}
.rack-glyph svg{width:100%;height:100%;display:block}
.rack-text{display:flex;flex-direction:column;gap:.16rem;min-width:0}
.dot{width:.5rem;height:.5rem;border-radius:50%;background:var(--rule-strong);flex:none;
  box-shadow:0 0 0 3px color-mix(in srgb,currentColor 16%,transparent),
             0 0 10px color-mix(in srgb,currentColor 50%,transparent)}
.dot.online{color:var(--go);background:currentColor}
.dot.offline,.dot.down,.dot.retired{color:var(--stop);background:currentColor}
.dot.maintenance,.dot.updating{color:var(--warn);background:currentColor}
.st-name{font:700 1.05rem/1.1 var(--cond);text-transform:uppercase;letter-spacing:.03em}
.st-detail{color:var(--accent);font:500 .68rem/1 var(--mono);
  text-transform:uppercase;letter-spacing:.09em}
.rack .st-detail{text-align:left}
.st-state{display:inline-flex;align-items:center;gap:.4rem;
  font:500 .66rem/1 var(--mono);text-transform:uppercase;letter-spacing:.1em;
  color:var(--soft);border:1px solid currentColor;border-radius:3px;padding:.28rem .5rem;
  background:color-mix(in srgb,currentColor 10%,transparent)}
.st-state.online{color:var(--go)}
.st-state.offline,.st-state.down,.st-state.retired{color:var(--stop)}
.st-state.maintenance,.st-state.updating{color:var(--warn)}

/* ------------------------------------------------------------ address ----
   The line a visitor came for. Same skewed head as the rack, then a slab with
   the hostname set as large as the band headings — as a row in .notes it was
   body copy in the middle of three equal panels, which is the one thing on a
   server page that should not have to be read for. */
.addr{position:relative;margin:0 0 2.4rem}
.addr-slab{margin:-1px 0 0;display:flex;align-items:center;gap:1.1rem;
  padding:1.35rem 1.5rem;border:1px solid var(--rule);
  background:linear-gradient(180deg,var(--raised),var(--surface));
  box-shadow:var(--shadow)}
.addr-glyph{width:2.8rem;height:2.8rem}
.addr-main{display:flex;flex-direction:column;gap:.5rem;min-width:0}
/* Big enough to read across a room, and it wraps rather than overflowing —
   a hostname is one unbroken token, so it needs anywhere. */
.addr-val{font-size:clamp(1.05rem,3.4vw,1.75rem);line-height:1.15;
  padding:.3em .5em;align-self:start;max-width:100%;
  overflow-wrap:anywhere;border-color:var(--accent-wash)}
/* The base ::after chip scales with the value; hold it at a readable size and
   set it as a chip rather than a trailing word. `anywhere` above applies to
   the chip's own text too, which was splitting it as "C / OPY" on a phone. */
.addr-val::after{font-size:.6rem;margin-left:.7em;border:1px solid currentColor;
  border-radius:3px;padding:.24em .45em;vertical-align:.18em;
  white-space:nowrap;overflow-wrap:normal}
/* The pack already ships the entry, so there is nothing to copy. Same size and
   weight as a hostname — it is the same answer to the same question — but set
   as plain text with no field around it and no chip after it, because every
   affordance here would be offering an action that isn't needed. */
.addr-val.is-builtin{background:none;border:0;padding:0;color:var(--go);
  font-family:var(--cond);font-weight:700;letter-spacing:.02em;
  text-transform:uppercase}
.addr-val.is-builtin::after{content:none}
.addr-glyph.is-builtin{color:var(--go)}
.addr-note{margin:0 !important;color:var(--muted);font-size:.86rem;line-height:1.6}

@media (max-width:600px){
  .addr-slab{align-items:flex-start;gap:.85rem;padding:1.1rem 1.05rem}
  .addr-glyph{width:2.1rem;height:2.1rem;margin-top:.2rem}
  /* A phone has no room for the hostname and its chip on one line, and the
     hostname is the thing that must not break. Drop the chip below it. */
  .addr-val::after{display:block;width:max-content;margin:.6em 0 0}
  /* Same reason the label must not split into "SERVER / ADDRESS": let the
     slab wrap between its two parts instead of inside them. */
  .addr .rack-head{flex-wrap:wrap;row-gap:.3rem}
  .addr .rack-head .d{white-space:nowrap}
}

/* --------------------------------------------------------------- hero ---- */
/* intro copy on the left, a widget alongside it */
.hero{display:grid;grid-template-columns:minmax(0,1fr) 22rem;
  gap:clamp(1.5rem,3vw,3rem);align-items:start;margin:0 0 2.4rem}
.hero-copy>*:last-child{margin-bottom:0}
.hero-side{min-width:0}
/* A picture in the side column is the column, so it starts where the prose
   starts rather than carrying a figure's usual leading gap. */
.hero-side>figure:first-child{margin-top:.35rem}
@media (max-width:860px){
  .hero{grid-template-columns:minmax(0,1fr);gap:1.5rem}
  .hero-side{max-width:22rem}
}

/* -------------------------------------------------------------- cards ---- */
.cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(16rem,1fr));
  gap:1rem;margin:1.3rem 0 2.2rem}
.card{position:relative;display:flex;flex-direction:column;
  padding:1.35rem 1.35rem 1.4rem;text-decoration:none;color:var(--text);
  background:linear-gradient(165deg,var(--raised),var(--surface));
  border:1px solid var(--rule);border-radius:0;overflow:hidden;
  transition:border-color .18s,transform .18s,box-shadow .18s}
/* A short skewed bar in the top-left corner rather than a full edge: it is the
   same slab shape as the nav pill and the band heads, at card scale. */
.card::before{content:"";position:absolute;left:0;top:0;width:4.6rem;height:4px;
  background:var(--accent);transform:skewX(var(--skew)) translateX(-.4rem)}
.card:hover{border-color:var(--accent);transform:translateY(-4px);box-shadow:var(--shadow-lift)}

/* The emblem is a watermark behind the corner now. The title is set in
   condensed caps, and a boxed icon beside it was competing for the same first
   glance. Decorative and aria-hidden either way — the title says where it goes. */
.card-art{position:absolute;right:-.6rem;bottom:-.9rem;width:6.6rem;height:6.6rem;
  color:var(--accent);opacity:.07;pointer-events:none;
  transition:opacity .18s,transform .18s}
.card-art svg{display:block;width:100%;height:100%}
.card:hover .card-art{opacity:.16;transform:translateY(-.25rem)}

.card-title{position:relative;font:700 1.28rem/1.05 var(--cond);
  text-transform:uppercase;letter-spacing:.02em;
  display:flex;align-items:baseline;gap:.4rem;margin:0 0 .55rem}
.card:hover .card-title{color:var(--accent)}
.ext{font-size:.75em;color:var(--soft)}
.card-note{position:relative;color:var(--muted);font-size:.88rem;line-height:1.6;
  margin:0 0 1.05rem}
.card-go{position:relative;margin-top:auto;
  font:700 .74rem/1 var(--cond);text-transform:uppercase;letter-spacing:.1em;
  color:var(--accent);display:inline-flex;align-items:center;gap:.4rem}
.card-go i{font-style:normal;transition:transform .18s}
.card:hover .card-go i{transform:translateX(.25rem)}

/* ----------------------------------------------------------- linklist ---- */
.linklist{list-style:none;margin:1.2rem 0 2.2rem;padding:0;border-top:1px solid var(--rule)}
.linklist li{border-bottom:1px solid var(--rule);padding:.68rem .5rem;margin:0;
  display:grid;grid-template-columns:minmax(0,1fr) auto;gap:.2rem 1rem;align-items:baseline;
  border-radius:4px;transition:background .14s}
.linklist li:hover{background:var(--accent-wash)}
.linklist a{text-decoration:none;display:contents}
.ll-name{font-weight:600;color:var(--accent)}
.linklist a:hover .ll-name{text-decoration:underline}
.ll-host{font:400 .75rem/1 var(--mono);color:var(--soft);text-align:right;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:14rem}
.ll-note{grid-column:1/-1;color:var(--muted);font-size:.85rem}

/* `@links grid` — the same rows in columns, for a rack that is a set rather
   than a reading order. Nine class calculators one per line is a ladder of
   accent-coloured names down a whole screen; three across is a block you scan
   in one movement.

   Columns rather than a grid, because a grid aligns rows: one long hostname
   wrapping a name onto a second line would then push a gap into the two rows
   beside it. Column count comes from the 17rem minimum, so a phone gets one
   without a breakpoint. */
.linklist.grid{columns:3 17rem;column-gap:1.6rem;border-top:0}
.linklist.grid li{break-inside:avoid;border-top:1px solid var(--rule);
  border-bottom:0;padding:.6rem .5rem}

/* --------------------------------------------------------------- table ---
   There is no markdown table syntax in the renderer, so @table is the only way
   to get columns. The first cell of every row is a <th>, which is what makes a
   keybinding list scannable down the left edge instead of reading as prose. */
.tw{overflow-x:auto;margin:1.2rem 0 2rem;border:1px solid var(--rule);border-radius:8px;
  background:linear-gradient(180deg,var(--raised),var(--surface))}
.dt{border-collapse:collapse;width:100%;font-size:.9rem}
.dt th,.dt td{text-align:left;padding:.55rem .95rem;border-bottom:1px solid var(--rule);
  vertical-align:top}
.dt tbody tr:last-child th,.dt tbody tr:last-child td{border-bottom:0}
.dt thead th{font:600 .68rem/1 var(--mono);text-transform:uppercase;letter-spacing:.12em;
  color:var(--soft);white-space:nowrap;background:var(--accent-wash)}
.dt tbody th{font:600 .88rem/1.4 var(--cond);color:var(--text);white-space:nowrap}
.dt td{color:var(--muted);line-height:1.5}
.dt tbody tr:hover th,.dt tbody tr:hover td{background:var(--accent-wash)}
/* A keybinding is a key, so the left column of a two-column table reads as one. */
.dt.keys tbody th{font:500 .82rem/1.4 var(--mono);color:var(--accent)}

/* --------------------------------------------------------------- party ---
   The roster. Character first and largest — it is the name used at the table —
   with the player under it, because the same PC can outlive the seat. */
.party{list-style:none;display:grid;grid-template-columns:repeat(auto-fit,minmax(15rem,1fr));
  gap:.75rem;margin:1.3rem 0 2.2rem;padding:0}
.pc{position:relative;display:flex;flex-direction:column;gap:.2rem;margin:0;
  padding:1rem 1.15rem;overflow:hidden;border:1px solid var(--rule);border-radius:8px;
  background:linear-gradient(180deg,var(--raised),var(--surface))}
.pc::before{content:"";position:absolute;left:0;top:0;bottom:0;width:2px;background:var(--accent)}
.pc-name{font:600 1.05rem/1.25 var(--cond)}
.pc-build{font:500 .7rem/1.3 var(--mono);text-transform:uppercase;letter-spacing:.1em;
  color:var(--accent)}
.pc-player{margin-top:.15rem;font-size:.83rem;color:var(--soft)}
.pc-player::before{content:"played by ";color:var(--soft)}
.pc-note{margin-top:.35rem;font-size:.85rem;line-height:1.5;color:var(--muted)}

/* ----------------------------------------------------------- campaigns ---
   A shelf of campaigns, each a row rather than a card: the state pill and the
   system want to line up down the page so the list can be read as a column of
   "what is running now" without reading any of the notes. */
.camps{list-style:none;margin:1.3rem 0 2.2rem;padding:0;display:flex;
  flex-direction:column;gap:.55rem}
.camp{margin:0}
.camp>a,.camp>div{position:relative;display:grid;grid-template-columns:1fr auto;
  gap:.15rem .9rem;align-items:baseline;padding:.85rem 1.1rem;overflow:hidden;
  text-decoration:none;color:var(--text);border:1px solid var(--rule);border-radius:8px;
  background:linear-gradient(180deg,var(--raised),var(--surface));
  transition:border-color .16s,transform .16s,box-shadow .16s}
.camp>a::before{content:"";position:absolute;left:0;top:0;bottom:0;width:2px;
  background:var(--accent);transform:scaleY(0);transform-origin:top;transition:transform .18s ease}
.camp>a:hover{border-color:var(--rule-strong);transform:translateY(-2px);box-shadow:var(--shadow-lift)}
.camp>a:hover::before{transform:scaleY(1)}
.camp-name{font:600 1.02rem/1.3 var(--cond)}
.camp>a:hover .camp-name{color:var(--accent)}
.camp-meta{display:flex;align-items:center;gap:.65rem;justify-self:end}
.camp-sys{font:500 .7rem/1 var(--mono);text-transform:uppercase;letter-spacing:.1em;
  color:var(--soft);white-space:nowrap}
.camp-state{font:600 .66rem/1 var(--mono);text-transform:uppercase;letter-spacing:.11em;
  padding:.28rem .5rem;border-radius:4px;white-space:nowrap;
  background:color-mix(in srgb,currentColor 14%,transparent)}
.camp-state.playing{color:var(--go)}
.camp-state.finished{color:var(--cool)}
.camp-state.prepping{color:var(--warn)}
.camp-state.hiatus{color:var(--soft)}
.camp-note{grid-column:1/-1;margin-top:.15rem;font-size:.86rem;line-height:1.5;color:var(--muted)}

/* --------------------------------------------------------------- vault ---
   A password gate in front of content the page is not allowed to show yet.
   It reads as a closed door rather than a form: the whole thing sits on the
   warning colour, so nobody mistakes it for an optional extra. */
.vault{margin:1.4rem 0 2.2rem;border:1px solid color-mix(in srgb,var(--warn) 35%,var(--rule));
  border-radius:8px;background:color-mix(in srgb,var(--warn) 7%,var(--surface))}
.vault.open{border-color:var(--rule);background:none;padding:0}
.vault-lock{padding:1.35rem 1.4rem;display:flex;flex-direction:column;gap:.55rem}
/* site.js hides the form by setting `hidden` once the content is out, and a
   bare `display:flex` above beats the UA stylesheet's `[hidden]{display:none}`
   — so without this the gate and a stale "Checking…" sit on top of the content
   they just revealed, forever. Any display rule on a element JS hides by
   attribute needs its own `[hidden]` escape hatch. */
.vault-lock[hidden]{display:none}
/* The word has to carry across a scroll — this is the one thing on the page
   somebody might regret not reading. */
.vault-head{display:flex;align-items:center;gap:.55rem;margin:0;
  font:700 1.45rem/1.1 var(--cond);text-transform:uppercase;letter-spacing:.06em;
  color:var(--warn)}
.vault-bang{display:flex;align-items:center;justify-content:center;flex:none;
  width:1.55rem;height:1.55rem;border-radius:50%;font:700 1rem/1 var(--cond);
  color:var(--bg);background:var(--warn)}
.vault-warn{margin:0 0 .35rem;font-size:.9rem;line-height:1.55;color:var(--text);max-width:52ch}
.vault-lock label{font:600 .7rem/1 var(--mono);text-transform:uppercase;letter-spacing:.13em;
  color:var(--warn)}
/* Both controls clear a 44px touch target — this is the one form on the site
   and it is most likely to be used on a phone, at the table. */
.vault-row{display:flex;gap:.5rem;flex-wrap:wrap}
.vault-row input,.vault-row button{min-height:2.75rem}
.vault-row input{flex:1 1 12rem;min-width:0;font:400 .92rem/1.2 var(--sans);color:var(--text);
  padding:.55rem .7rem;border:1px solid var(--rule-strong);border-radius:6px;
  background:var(--bg)}
.vault-row input:focus-visible{outline:2px solid var(--accent);outline-offset:1px}
.vault-row button{font:600 .78rem/1 var(--mono);text-transform:uppercase;letter-spacing:.09em;
  color:var(--bg);background:var(--warn);border:0;border-radius:6px;padding:.6rem 1.05rem;
  cursor:pointer;transition:filter .14s}
.vault-row button:hover:not(:disabled){filter:brightness(1.1)}
.vault-row button:disabled{opacity:.55;cursor:default}
.vault-msg{margin:0;min-height:1.1rem;font-size:.82rem;color:var(--muted)}
.vault-msg.bad{color:var(--stop)}
/* Content arrives already rendered, so its own margins are correct — the
   wrapper must not add a second set on top. */
.vault-body>:first-child{margin-top:0}
.vault-body>:last-child{margin-bottom:0}

/* ------------------------------------------------------------- diagram ---
   A drawn figure from DIAGRAMS. The SVG carries a viewBox and no intrinsic
   size, so it scales to whatever column it lands in — including inside a
   vault, which is narrower than the article. */
.diagram{margin:1.6rem 0 1.9rem}
.diagram svg{display:block;width:100%;height:auto;border:1px solid var(--rule);
  border-radius:8px;background:var(--surface);padding:.9rem}
.diagram figcaption{margin-top:.6rem;color:var(--muted);font-size:.88rem;
  line-height:1.55;max-width:62ch}

/* ---------------------------------------------------------------- jump ---
   The only nav that lives inside an article. It wears the pill shape and the
   one shear the rest of the site uses, but not the accent fill — it sits
   directly under an opener and a row of gold pills there would compete with
   the opener's own CTA for the eye. */
.jump{position:sticky;top:var(--mast-h);z-index:20;
  margin:1.4rem 0 2rem;padding:.9rem 1rem 1rem;border:1px solid var(--rule);
  border-radius:10px;background:linear-gradient(180deg,var(--raised),var(--surface));
  transition:padding .16s,border-color .16s,box-shadow .16s}
.jump-k{margin:0 0 .6rem;font:600 .64rem/1 var(--mono);text-transform:uppercase;
  letter-spacing:.14em;color:var(--soft)}
.jump ul{display:flex;flex-wrap:wrap;gap:.4rem;margin:0;padding:0;list-style:none}

/* Stuck against the masthead it is a bar rather than a card: the label moves
   beside the pills, the row stops wrapping, and what does not fit scrolls —
   a strip that follows you down the page must not also grow to three rows and
   take a third of a phone screen with it. `--jump-h` above is this height. */
.jump.is-stuck{display:flex;align-items:center;gap:.7rem;
  padding:.42rem .6rem;border-radius:8px;border-color:var(--rule-strong);
  box-shadow:var(--shadow)}
.jump.is-stuck .jump-k{margin:0;flex:none}
.jump.is-stuck ul{flex:1;min-width:0;flex-wrap:nowrap;overflow-x:auto;
  scroll-behavior:smooth;scrollbar-width:none;-ms-overflow-style:none}
.jump.is-stuck ul::-webkit-scrollbar{display:none}
.jump.is-stuck a{white-space:nowrap}
@media (max-width:600px){
  /* no room for both, and the pills are the part that does something */
  .jump.is-stuck .jump-k{display:none}
}
/* The section you are in, filled rather than outlined — hover is the same
   accent and this has to still read as the answer while the pointer is
   somewhere else on the row. */
.jump a.is-on,.jump a.is-on:hover{background:var(--accent);border-color:var(--accent);
  color:var(--ink-bg)}
.jump a{display:block;padding:.34em .72em;border:1px solid var(--rule-strong);
  border-radius:4px;transform:skewX(var(--skew));background:var(--surface);
  color:var(--muted);font:600 .8rem/1.35 var(--cond);text-decoration:none;
  transition:color .15s,border-color .15s,background .15s}
/* The shear is on the slab, so the word inside is sheared back — the same
   correction every other pill on the site makes. */
.jump a>span{display:block;transform:skewX(calc(var(--skew) * -1))}
.jump a:hover,.jump a:focus-visible{color:var(--accent);border-color:var(--accent);
  background:var(--accent-wash)}

/* -------------------------------------------------------------- ladder ---
   Level-range bars on one axis: @ladder. Lanes are packed in build.js, so the
   only thing CSS decides is how wide the window is and what a bar looks like.

   Bars are positioned in percent of the whole 13-60 window and the axis is
   drawn over the same track, which is what keeps the two aligned at any width.
   The track carries a min-width and the wrapper scrolls, because thirty
   dungeons across forty-eight levels has a floor below which the labels stop
   being words — under that floor the honest answer is a scrollbar, not 4px
   type. The wrapper is focusable so the scroll is reachable from the keyboard. */
/* A figure is normally a picture with a caption and stops at the reading
   measure. This one is a chart: at 68ch a desktop with room for all forty-eight
   levels would still be handing you a scrollbar. */
article>figure.ladder{max-width:none}
.ladder{margin:1.5rem 0 2rem}
.ld-key{display:flex;flex-wrap:wrap;gap:.5rem .95rem;margin:0 0 .7rem}
.ld-chip{display:inline-flex;align-items:center;gap:.4rem;
  font:600 .7rem/1.4 var(--mono);text-transform:uppercase;letter-spacing:.08em;
  color:var(--soft)}
.ld-chip::before{content:"";width:.85rem;height:.85rem;border-radius:3px;
  border:1px solid var(--cool);background:color-mix(in srgb,var(--cool) 22%,transparent)}
.ld-chip.is-new::before{border-color:var(--go);
  background:color-mix(in srgb,var(--go) 22%,transparent)}
.ld-chip.is-gate::before{border-color:var(--accent);background:transparent;
  border-left-width:4px}

.ld-scroll{overflow-x:auto;overflow-y:hidden;border:1px solid var(--rule);
  border-radius:8px;background:linear-gradient(180deg,var(--raised),var(--surface))}
.ld-scroll:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
/* 52rem is the floor a six-level bar needs to hold "Blackfathom Deeps" without
   an ellipsis — the narrowest bar the chart has more than one of. */
.ld-track{position:relative;min-width:52rem;padding:.75rem .75rem .25rem}

.ld-lane{position:relative;height:2.45rem;margin-bottom:.2rem}
.ld-bar{position:absolute;top:0;bottom:0;display:flex;flex-direction:column;
  align-items:center;justify-content:center;gap:.05rem;overflow:hidden;
  padding:0 .3rem;border:1px solid var(--cool);border-radius:4px;
  background:color-mix(in srgb,var(--cool) 18%,transparent);
  /* the fill is translucent and neighbouring bars touch, so without this the
     two borders between them read as one thick rule belonging to neither */
  box-shadow:0 0 0 1px var(--surface);
  text-align:center;line-height:1.15;cursor:default}
.ld-bar b{display:block;max-width:100%;overflow:hidden;text-overflow:ellipsis;
  white-space:nowrap;font:600 .74rem/1.2 var(--cond);color:var(--text)}
.ld-bar i{font:500 .63rem/1 var(--mono);font-style:normal;color:var(--muted)}
.ld-bar.is-new{border-color:var(--go);
  background:color-mix(in srgb,var(--go) 18%,transparent)}
/* Faction gating is a third fact on a bar that already carries two colours, so
   it is a thickened edge rather than a fourth hue. */
.ld-bar.is-gated{border-left:4px solid var(--accent)}

.ld-axis{position:relative;height:1.55rem;margin-top:.25rem;
  border-top:1px solid var(--rule-strong)}
.ld-tick{position:absolute;top:0;transform:translateX(-50%);padding-top:.32rem;
  font:600 .68rem/1 var(--mono);color:var(--soft);white-space:nowrap}
.ld-tick.is-last{transform:translateX(-100%)}
.ld-tick::before{content:"";position:absolute;top:0;left:50%;width:1px;height:.24rem;
  background:var(--rule-strong)}
.ld-tick.is-last::before{left:auto;right:0}

/* ------------------------------------------------------------ timeline ---
   A year spine with entries hanging off it. The rule is drawn on the <ol> and
   inset from its ends, so it runs continuously behind every year marker but
   stops short of dangling past the first and last one. Entries carry their own
   --accent via data-sec, which is what makes the WoW stretch read gold and the
   FF14 stretch blue without the page naming a colour. */
/* The key sits on the spine's left margin so its swatches line up under the
   year column rather than floating loose above the entries. */
.tl-key{list-style:none;display:flex;flex-wrap:wrap;gap:.35rem 1.1rem;
  margin:1.5rem 0 0;padding:0 0 .9rem 5.25rem;border-bottom:1px solid var(--rule);
  font:500 .78rem/1.4 var(--sans);color:var(--muted)}
.tl-key>li{display:flex;align-items:center;gap:.42rem;margin:0}
.tl-sw{width:.62rem;height:.62rem;border-radius:2px;background:var(--accent);flex:none}

.timeline{list-style:none;margin:1.1rem 0 2.4rem;padding:0 0 0 5.25rem;position:relative}
.timeline::before{content:"";position:absolute;left:4.1rem;top:.6rem;bottom:.6rem;
  width:1px;background:var(--rule-strong)}
.tl-year{position:relative;margin:0 0 1.6rem}
.tl-year:last-child{margin-bottom:0}
.tl-year::before{content:"";position:absolute;left:-1.44rem;top:.42rem;
  width:9px;height:9px;border-radius:50%;background:var(--accent);
  box-shadow:0 0 0 4px var(--bg)}
.tl-y{position:absolute;left:-5.25rem;top:0;width:3.4rem;margin:0;text-align:right;
  border:0;padding:0;font:600 1rem/1.35 var(--mono);font-variant-numeric:tabular-nums;
  color:var(--muted);letter-spacing:0}
.tl-y::after{display:none}
.tl-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:.5rem}
.tl-e{position:relative;margin:0;padding:.62rem .95rem;overflow:hidden;
  border:1px solid var(--rule);border-radius:7px;
  background:linear-gradient(180deg,var(--raised),var(--surface))}
.tl-e::before{content:"";position:absolute;left:0;top:0;bottom:0;width:2px;background:var(--accent)}
.tl-t{display:block;font:600 .97rem/1.32 var(--cond)}
.tl-n{display:block;margin-top:.22rem;font-size:.86rem;line-height:1.45;color:var(--muted)}
/* An era labels a span of years rather than a moment in one, so it reads as a
   rule across the column instead of another card on the pile. */
.tl-era{margin:.35rem 0 .1rem;padding:0;display:flex;align-items:center;gap:.6rem;
  font:600 .68rem/1 var(--mono);text-transform:uppercase;letter-spacing:.14em;
  color:var(--accent)}
.tl-era::after{content:"";flex:1;height:1px;background:var(--rule)}

/* ------------------------------------------------------------- embeds ---- */
.embeds{display:grid;gap:.9rem;margin:1.5rem 0 2.2rem}
/* two across on a full-width column. A smaller track minimum lets four videos
   fall into a 3+1 row and shrinks each one to thumbnail size. */
.embeds.multi{grid-template-columns:repeat(auto-fit,minmax(min(100%,22rem),1fr))}
.yt{position:relative;display:block;padding:0;border:1px solid var(--rule);
  border-radius:8px;overflow:hidden;background:var(--ink-bg);cursor:pointer;
  aspect-ratio:16/9;width:100%;transition:border-color .16s,transform .16s}
.yt:hover{border-color:var(--accent);transform:translateY(-2px)}
.yt img{width:100%;height:100%;object-fit:cover;display:block;opacity:.82;transition:opacity .18s}
.yt:hover img{opacity:1}
.yt-play{position:absolute;inset:0;margin:auto;width:3.3rem;height:2.35rem;border-radius:7px;
  background:rgba(11,15,22,.82);border:1px solid rgba(226,232,240,.24);
  transition:background .16s,border-color .16s,transform .16s}
.yt-play::after{content:"";position:absolute;inset:0;margin:auto;width:0;height:0;
  border-style:solid;border-width:.42rem 0 .42rem .7rem;
  border-color:transparent transparent transparent #e2e8f0}
.yt:hover .yt-play{background:var(--accent);border-color:transparent;transform:scale(1.06)}
.yt:hover .yt-play::after{border-left-color:#0d1117}
.yt iframe{width:100%;height:100%;border:0;display:block}

/* -------------------------------------------------------- arcade floor ---
   The picker on /arcade. A tile is a cabinet: an emblem, what kind of game it
   is, and what its board's best currently is — enough to choose from before
   anything loads, since the game's own code only arrives on the game's page.

   A cabinet that is announced but not built stands on the same floor, dashed
   and dimmed: what is coming next is part of what an arcade is. */
/* 15.5rem, not 17.5: four cabinets have to clear the column at 1152px, and at
   the wider minimum the fourth wrapped onto a row of its own. */
.cabs{list-style:none;margin:1.4rem 0 2.2rem;padding:0;display:grid;
  grid-template-columns:repeat(auto-fit,minmax(15.5rem,1fr));gap:1rem}
.cab>a,.cab>div{display:flex;flex-direction:column;height:100%;
  border:1px solid var(--rule);border-radius:0;background:var(--surface);
  overflow:hidden;text-decoration:none;color:inherit;
  transition:border-color .18s,transform .18s,box-shadow .18s}
.cab>a:hover{border-color:var(--cab,var(--c-arcade));transform:translateY(-4px);
  box-shadow:var(--shadow-lift)}

/* The floor: the game's own machine standing in a pool of its own light, with
   the chips on the panel's edges. `--cab` is set per tile in build.js off the
   game's `trim`, and it is the one place the arcade's amber gives way — a row
   of ten identical panels was ten of the same cabinet from across the room.

   The drawing is absolutely positioned rather than centred content: it is a
   16/9 viewBox in a 16/9 box, so it wants the whole panel and not a slot in
   the middle of it. */
.cab-art{
  position:relative;
  aspect-ratio:16/9;
  color:var(--c-arcade);
  /* Dark in both themes, like .arcade-screen and for the same reason: an
     arcade floor is a dark room, and the machine's own colours only mean
     anything against one. The light theme lights the card, not the room. */
  background:radial-gradient(72% 108% at 50% 96%,
    color-mix(in srgb,var(--cab,var(--c-arcade)) 20%,transparent),#0b0a08 74%);
  border-bottom:1px solid var(--rule);
  overflow:hidden;
}
.cab-svg{position:absolute;inset:0;width:100%;height:100%;display:block;
  filter:drop-shadow(0 6px 14px rgba(0,0,0,.45));
  transform-origin:50% 100%;transition:transform .18s}
.cab>a:hover .cab-svg{transform:scale(1.045)}
/* The marquee's own sign. textLength in the markup makes any name fit the
   plastic, so this only has to set the face. */
.cab-mq{font:700 12px/1 var(--cond);fill:#140d02;letter-spacing:.04em}

/* ------------------------------------------------- the screens, playing ---
   Every machine on the floor is showing its own game rather than a still of
   it: the wasps close, the belt runs, the lantern burns down, the beat walks
   into the line. An arcade with sixteen frozen screens is a showroom.

   This is a shared vocabulary rather than sixteen bespoke animations —
   a dozen keyframes tuned per element by custom properties. `--d` is the
   loop, `--dl` the offset into it (negative, so a row of three starts
   mid-stride rather than in step), and `--tx`/`--ty`/`--r`/`--s`/`--o1`/`--o2`
   the amplitude. Two rules the vocabulary is worth keeping to:

   - Only `transform` and `opacity` move. Nothing animates an SVG geometry
     attribute, so a tile costs a composite rather than a layout, and sixteen
     of them at once stay cheap enough to leave running.
   - A CSS `transform` REPLACES a `transform=` presentation attribute rather
     than composing with it. Every card on this floor that sits at an angle is
     therefore wrapped in a <g> that takes the animation, and keeps its own
     rotation on the rect inside.
   - The same goes for `opacity`, and that one bites where it cannot be seen:
     a keyframe beats the attribute WHILE RUNNING, and under reduced motion
     nothing runs and the attribute is all there is. So anything animating
     opacity keeps the opacity it was drawn at as an attribute, with `--o1`
     and `--o2` straddling it. Drop the attribute and the still version of
     Dread Delve is two solid amber slabs where its lantern light was.

   All of it sits inside a no-preference query. A grid of sixteen moving
   pictures is precisely what reduced motion is asking not to be shown, and
   every drawing was complete before any of this was added. */
@media (prefers-reduced-motion:no-preference){
  .cab-svg [class*="cs-"]{
    animation-duration:var(--d,4s);
    animation-delay:var(--dl,0s);
    animation-iteration-count:infinite;
    animation-timing-function:ease-in-out;
  }
  /* Anything turning or scaling has to say which box it turns in: an SVG
     element's transform-box is the viewBox by default, so `50% 50%` without
     this is the middle of the screen rather than the middle of the thing. */
  .cs-throb,.cs-spin,.cs-swing,.cs-flicker,.cs-wing,.cs-meter,.cs-drain{
    transform-box:fill-box}

  /* Something hovering, closing, or circling: out and back, forever. */
  .cs-drift{animation-name:cs-drift;animation-direction:alternate}
  @keyframes cs-drift{from{transform:translate(0,0)}
    to{transform:translate(var(--tx,0px),var(--ty,0px))}}

  /* Something crossing the glass and coming round again. Only for a group
     whose contents are evenly spaced by exactly `--tx`, so the wrap lands one
     step along and there is no jump to see — the belt and the beat strip. */
  .cs-run{animation-name:cs-run;animation-timing-function:linear}
  @keyframes cs-run{from{transform:translate(0,0)}
    to{transform:translate(var(--tx,0px),var(--ty,0px))}}

  /* The same journey for a thing that is alone on it: it fades in where it
     comes from and out where it goes, so the loop has no seam to find. */
  .cs-pass{animation-name:cs-pass;animation-timing-function:linear}
  @keyframes cs-pass{
    0%{transform:translate(0,0);opacity:0}
    18%,82%{opacity:var(--o,1)}
    100%{transform:translate(var(--tx,0px),var(--ty,0px));opacity:0}}

  /* A light, a glow, a thing that means something right now. */
  .cs-pulse{animation-name:cs-pulse;animation-direction:alternate}
  @keyframes cs-pulse{from{opacity:var(--o1,.3)}to{opacity:var(--o2,1)}}

  /* Breathing, landing, firing — scale about its own middle. */
  .cs-throb{animation-name:cs-throb;animation-direction:alternate;
    transform-origin:50% 50%}
  @keyframes cs-throb{from{transform:scale(1)}to{transform:scale(var(--s,1.14))}}

  /* A flame. Never quite the same shape twice, which is the whole of it. */
  .cs-flicker{animation-name:cs-flicker;transform-origin:50% 100%}
  @keyframes cs-flicker{
    0%{transform:scale(1,1);opacity:1}
    20%{transform:scale(.9,1.12);opacity:.8}
    40%{transform:scale(1.08,.93);opacity:1}
    60%{transform:scale(.95,1.07);opacity:.86}
    80%{transform:scale(1.05,.97);opacity:1}
    100%{transform:scale(1,1);opacity:1}}

  /* A wing. Both of a bee's are one path on these drawings, so this is the
     blur of a beat rather than the beat — squashed toward the body and back,
     fast enough that the eye reads it as a hum. */
  .cs-wing{animation-name:cs-wing;transform-origin:50% 100%;
    animation-timing-function:linear}
  @keyframes cs-wing{from{transform:scaleY(1)}50%{transform:scaleY(.5)}
    to{transform:scaleY(1)}}

  /* A pivot: a bell on its wire. Hangs from the top of its own box. */
  .cs-swing{animation-name:cs-swing;animation-direction:alternate;
    transform-origin:50% 0}
  @keyframes cs-swing{from{transform:rotate(calc(var(--r,6deg) * -1))}
    to{transform:rotate(var(--r,6deg))}}

  /* A wheel or a ring, turning on its own centre. */
  .cs-spin{animation-name:cs-spin;animation-timing-function:linear;
    transform-origin:50% 50%}
  @keyframes cs-spin{to{transform:rotate(360deg)}}

  /* A sweep, turning on somebody ELSE's centre — a cleave coming round a
     boss. `--ox`/`--oy` are in the screen's own 80x52 units, so this is the
     one turn that keeps the viewBox as its reference box. */
  .cs-sweep{animation-name:cs-spin;animation-timing-function:linear;
    transform-box:view-box;transform-origin:var(--ox,50%) var(--oy,50%)}

  /* A bar filling and a bar draining, both scaleX from the bar's left edge —
     which is why neither has to animate a width. `--f1`/`--f2` are multiples
     of the width the bar is drawn at, so a bar drawn at 46 of 80 reaches the
     far side at 1.7 and the drawing stays the honest resting state. */
  .cs-meter,.cs-drain{transform-origin:0 50%;animation-timing-function:linear}
  .cs-meter{animation-name:cs-meter}
  @keyframes cs-meter{0%{transform:scaleX(var(--f1,.4))}
    90%{transform:scaleX(var(--f2,1.5))}100%{transform:scaleX(var(--f1,.4))}}
  .cs-drain{animation-name:cs-drain}
  @keyframes cs-drain{0%{transform:scaleX(1)}86%{transform:scaleX(.05)}
    100%{transform:scaleX(1)}}

  /* A route being walked. The dashes travel the path rather than a figure
     travelling it, which is how a lane reads as a lane at eighty pixels wide.
     `--dash` must be a whole number of dash periods or the loop stutters. */
  .cs-path{animation-name:cs-path;animation-timing-function:linear}
  @keyframes cs-path{to{stroke-dashoffset:var(--dash,-10)}}
}
/* Shared <defs> for the cabinets. It renders nothing itself and must not take
   up a line of the article. */
.cab-defs{position:absolute;width:0;height:0;overflow:hidden}
/* The chips keep the top band and the drawing keeps out of it: a chip is a
   fixed number of rem in a panel that is not, so the machine is scaled about
   its own base to leave that band clear at every tile width rather than
   clearing it at the width this was drawn at. */
.cab-kind,.cab-state{
  position:absolute;top:.6rem;z-index:1;
  display:inline-flex;align-items:center;gap:.35rem;
  padding:.24rem .5rem;border:1px solid var(--rule-strong);
  background:color-mix(in srgb,var(--ink-bg) 82%,transparent);
  font:500 .6rem/1 var(--mono);text-transform:uppercase;letter-spacing:.11em;
}
.cab-kind{left:.6rem;color:var(--c-arcade)}
.cab-state{right:.6rem;color:var(--go)}
.cab.is-soon .cab-state{color:var(--soft)}

.cab-body{display:flex;flex-direction:column;flex:1;padding:1.15rem 1.2rem 1.25rem}
.cab-name{font:700 1.38rem/1 var(--cond);text-transform:uppercase;
  letter-spacing:.02em;margin-bottom:.55rem}
.cab>a:hover .cab-name{color:var(--c-arcade)}
.cab-note{font-size:.86rem;line-height:1.6;color:var(--muted);margin-bottom:1.1rem}
.cab-foot{margin-top:auto;display:flex;align-items:flex-end;
  justify-content:space-between;gap:1rem;
  padding-top:.95rem;border-top:1px solid var(--rule)}
/* Filled from /api/arcade after the page loads. Empty until then, and empty
   for good if the board cannot be reached — a tile is fine without it. */
.cab-top{font:500 .64rem/1.35 var(--mono);text-transform:uppercase;
  letter-spacing:.09em;color:var(--soft);min-width:0}
.cab-top b{display:block;margin-top:.28rem;font-weight:500;font-size:1.02rem;
  color:var(--c-arcade);font-variant-numeric:tabular-nums}
.cab-top i{display:block;font-style:normal;font-size:.62rem;color:var(--muted);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.cab-top:empty{display:none}
.cab.is-soon>div{opacity:.62;border-style:dashed}

/* ------------------------------------------------------------ stat rail ---
   Beside the arcade opener. Values arrive from the shell; the dash they ship
   with is the honest state until they do. */
.astats{display:grid;gap:.6rem}
.astat{
  display:flex;align-items:baseline;justify-content:space-between;gap:1rem;
  margin:0 !important;padding:.8rem 1.05rem;
  background:var(--surface);border:1px solid var(--rule);
  border-left:3px solid var(--accent);
}
.astat .k{font:500 .68rem/1 var(--mono);text-transform:uppercase;
  letter-spacing:.11em;color:var(--muted)}
.astat .v{font:700 1.3rem/1 var(--cond);text-transform:uppercase;color:var(--text)}
.astat .v.hi{color:var(--accent)}
.astat .v.num{font-family:var(--mono);font-weight:500;font-size:1.1rem;
  font-variant-numeric:tabular-nums}

/* ---------------------------------------------------------------- notes ---
   Standing panels. Same slab as a card, minus the link affordances. */
.notes{display:grid;grid-template-columns:repeat(auto-fit,minmax(16rem,1fr));
  gap:1rem;margin:1.3rem 0 2.2rem}
.note-card{padding:1.25rem 1.3rem 1.35rem;background:var(--surface);
  border:1px solid var(--rule);border-top:3px solid var(--accent)}
.note-card h3{margin:0 0 .55rem !important;padding:0 !important;border:0 !important;
  font:700 1.05rem/1.15 var(--cond) !important;text-transform:uppercase;
  letter-spacing:.03em}
.note-card h3::before,.note-card h3::after{display:none !important}
.note-card p{margin:0 !important;color:var(--muted);font-size:.88rem;line-height:1.62}
/* A panel that shows the thing it describes. The picture sits above the
   heading and bleeds to the card's edges — inset it and the card reads as a
   slab with a stamp on it rather than as a screenshot with a note under it.
   Fixed 16:9 and `cover`, because these arrive as press captures and UI grabs
   at two different shapes and a row of ragged tops is worse than a crop. */
/* Scoped to the cards that actually carry one: a `@notes shots` block may mix
   pictured and unpictured panels, and dropping the top padding on a card with
   no image leaves its heading sitting on the accent border. */
.notes.has-shots .note-card:has(.note-shot){padding-top:0;overflow:hidden}
/* `max-width:none` is load-bearing: the global `img{max-width:100%}` caps this
   at the card's *content* box, so the negative margins pull it out on the left
   and leave a gap the width of the padding on the right. */
.note-shot{display:block;width:calc(100% + 2.6rem);max-width:none;
  margin:0 -1.3rem 1.05rem;
  aspect-ratio:16/9;object-fit:cover;border-bottom:1px solid var(--rule)}

/* ---------------------------------------------------------------- shots ---
   @gallery: a contact sheet at full `--wide`. The tile count comes from the
   block (`data-n`), but every row still collapses on its own — `auto-fit` with
   a floor is what keeps three 18rem tiles from becoming three 6rem stamps on a
   phone. */
.shots{display:grid;gap:.85rem;margin:1.4rem 0 2.2rem;
  grid-template-columns:repeat(auto-fit,minmax(min(100%,18rem),1fr))}
.shots[data-n="2"]{grid-template-columns:repeat(auto-fit,minmax(min(100%,22rem),1fr))}
.shots figure{margin:0;border:1px solid var(--rule);border-radius:8px;
  overflow:hidden;background:var(--surface)}
.shots img{display:block;width:100%;aspect-ratio:16/9;object-fit:cover}
.shots figcaption{padding:.6rem .75rem .7rem;color:var(--muted);
  font-size:.82rem;line-height:1.5;border-top:1px solid var(--rule)}

/* --------------------------------------------------------------- roster ---
   @roster: one card per person, and the answer is what you see first — the
   colour, the dot and the word, with the name under them. As a table the same
   three columns were twelve rows of identical grey and counting the yeses was
   work. The tally across the top is the count done for you.

   `--ans` is declared per card so the dot, the rule and the word are all one
   decision; `.ro-tally li` carries it too so the strip and the cards cannot
   disagree about what green means. */
.roster{margin:1.5rem 0 2.2rem}
.ro-tally,.ro-list{margin:0;padding:0;list-style:none}
.ro-tally{display:flex;flex-wrap:wrap;gap:.5rem;margin-bottom:.9rem}
.ro-tally li{display:flex;align-items:baseline;gap:.45rem;padding:.42rem .72rem;
  border:1px solid var(--rule);border-radius:6px;background:var(--surface)}
.ro-n{font:500 1.15rem/1 var(--mono);font-variant-numeric:tabular-nums;
  letter-spacing:-.02em;color:var(--ans)}
.ro-k{font:500 .65rem/1 var(--mono);text-transform:uppercase;letter-spacing:.11em;
  color:var(--soft)}

.ro-list{display:grid;grid-template-columns:repeat(auto-fill,minmax(13.5rem,1fr));
  gap:.7rem}
.ro{padding:.8rem .9rem .9rem;border:1px solid var(--rule);
  border-left:3px solid var(--ans);border-radius:8px;
  background:color-mix(in srgb,var(--ans) 7%,var(--surface))}
.ro-a{display:flex;align-items:center;gap:.45rem;margin:0 0 .3rem !important;
  font:500 .68rem/1.2 var(--mono);text-transform:uppercase;letter-spacing:.1em;
  color:var(--ans)}
/* filled for a yes, a ring for anything short of one — the shape says it
   again for anyone who does not get the colour */
.ro-mark{width:.6rem;height:.6rem;flex:none;border-radius:50%;
  border:2px solid var(--ans)}
.ro-who{margin:0 !important;font:700 1.15rem/1.1 var(--cond);
  text-transform:uppercase;letter-spacing:.02em;color:var(--text)}
.ro-note{margin:.35rem 0 0 !important;color:var(--muted);font-size:.84rem;
  line-height:1.55}

.roster [data-a="yes"]{--ans:var(--go)}
.roster [data-a="yes"] .ro-mark{background:var(--ans)}
.roster [data-a="maybe"]{--ans:var(--accent)}
.roster [data-a="unknown"]{--ans:var(--soft)}
.roster [data-a="unknown"] .ro-mark{border-style:dotted}
/* Out is a real answer and an early one is the most useful kind, so it is
   stated in red rather than greyed out into something you have to squint at. */
.roster [data-a="no"]{--ans:var(--stop)}

/* ------------------------------------------------------------ pull quote -- */
.pull{
  margin:0 0 1.4rem !important;padding:.9rem 0 .9rem 1.2rem;
  border-left:3px solid var(--accent);
  background:linear-gradient(90deg,var(--accent-wash),transparent 70%);
  max-width:32rem;
}
.pull p{margin:0 !important;font:700 1.35rem/1 var(--cond);
  text-transform:uppercase;letter-spacing:.02em;color:var(--accent-hover)}
:root[data-theme="light"] .pull p{color:var(--accent)}
.pull footer{margin-top:.45rem;font-size:.78rem;color:var(--soft)}

/* ------------------------------------------------------------- arcade ---
   The cabinet: HUD, screen and scoreboard. The screen keeps its own dark
   palette in both themes — the canvas paints a lit arena, and a light-theme
   version of it would need every hit flash and particle re-tuned to read
   against white for no gain.

   It takes the full --wide column rather than the reading measure: it is the
   page's subject, not something illustrating the prose.

   One column at every width, board under the screen. The board beside it cost
   the arena a third of its width, and the canvas is a fixed 900x600 surface
   scaled by CSS — so that third came straight off the type the games draw.
   Hive Mind's smallest label is 10.5 canvas px, which at the old two-column
   width landed near 7 real ones. Stacked, --wide is a hair over 900 and the
   arena draws about 1:1. The board is what you read between runs, not during
   one, so it loses nothing by sitting below. */
.arcade{display:grid;grid-template-columns:minmax(0,1fr);gap:1.1rem;
  align-items:start;margin:1.5rem 0 2.4rem}
.arcade-stage{min-width:0;display:flex;flex-direction:column;gap:.55rem}

.arcade-hud{display:flex;flex-wrap:wrap;align-items:center;gap:.4rem .9rem;
  padding:.6rem .9rem;border:1px solid var(--rule);border-radius:8px 8px 0 0;
  border-bottom:0;background:linear-gradient(180deg,var(--raised),var(--surface))}
.hud-stat{display:flex;align-items:baseline;gap:.45rem}
.hud-stat .k{font:500 .64rem/1 var(--mono);text-transform:uppercase;letter-spacing:.14em;
  color:var(--soft)}
.hud-stat b{font:700 1.05rem/1 var(--cond);font-variant-numeric:tabular-nums;color:var(--text)}
.hud-meter{flex:1 1 8rem;min-width:7rem}
/* A meter is whatever that game cannot afford to lose — the hive, a fuel tank —
   so it has to be readable at a glance from the corner of the eye while the
   canvas has the player's attention, and obvious when it is nearly gone. */
.meter{flex:1;height:.55rem;border-radius:99px;overflow:hidden;
  background:color-mix(in srgb,var(--text) 12%,transparent)}
.meter>i{display:block;height:100%;width:100%;border-radius:99px;
  background:var(--c-arcade);transition:width .12s linear,background .2s}
.meter.low>i{background:var(--stop)}
/* The buff tray gets a row of its own, held open whether or not anything is in
   it. Inline in the counters row it was wide enough to wrap the moment a pill
   appeared, which grew the HUD and pushed the arena down a line mid-rally —
   the one thing above the canvas that must never move while a pellet is in
   play. `min-height` is one pill's box (.22rem padding twice + .64rem/1 text),
   so catching a buff changes what the row says and never how tall it is. */
.hud-buffs{display:flex;gap:.4rem;flex-wrap:wrap;order:3;flex:1 0 100%;
  margin-left:0;min-height:1.08rem}
.buff{display:inline-flex;align-items:center;gap:.3rem;padding:.22rem .45rem;border-radius:4px;
  font:600 .64rem/1 var(--mono);text-transform:uppercase;letter-spacing:.08em;
  color:var(--buff);background:color-mix(in srgb,var(--buff) 16%,transparent)}
.buff i{font-style:normal;opacity:.7;font-variant-numeric:tabular-nums}

/* Sound toggle. Sits at the far end of the counters row, above the buff tray,
   because it is the one control there that is not about the run in progress.
   The two halves of the icon swap rather than the icon being redrawn, so the
   speaker itself never moves between states. */
.hud-mute{order:1;margin-left:auto;
  display:inline-flex;align-items:center;justify-content:center;
  width:1.9rem;height:1.9rem;padding:0;border:1px solid var(--rule-strong);
  border-radius:6px;background:var(--surface);color:var(--soft);cursor:pointer;
  transition:color .15s,border-color .15s}
.hud-mute:hover{color:var(--text);border-color:var(--accent)}
.hud-mute:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
.hud-mute .mute-off{display:none}
.hud-mute.is-off{color:var(--soft);opacity:.75}
.hud-mute.is-off .mute-on{display:none}
.hud-mute.is-off .mute-off{display:inline}

.arcade-screen{position:relative;border:1px solid var(--rule);border-radius:0 0 8px 8px;
  overflow:hidden;background:#0a0d14;line-height:0}
/* No image-rendering hint. The shell sizes the backing store to the device, so
   the surface maps 1:1 and there is nothing to resample — and `crisp-edges`,
   which was here to keep the arena from going mushy when a 900px surface was
   blown up on a phone, actively hurt once a ratio came out fractional: nearest
   neighbour drops pixel columns, and a dropped column in a 10.5px label is a
   missing letter stem. Let the browser do whatever the odd half pixel needs. */
.arcade-screen canvas{display:block;width:100%;height:auto;
  touch-action:none;cursor:crosshair}

.arcade-overlay{position:absolute;inset:0;display:flex;flex-direction:column;
  align-items:center;justify-content:center;gap:.55rem;text-align:center;
  padding:1.4rem;line-height:1.5;
  background:radial-gradient(ellipse at center,rgba(10,13,20,.86),rgba(10,13,20,.97));
  color:#e2e8f0}
.arcade-overlay[hidden]{display:none}
.arcade-overlay h2{margin:0;border:0;padding:0;font:700 2rem/1.05 var(--cond);
  text-transform:uppercase;letter-spacing:.03em;color:var(--c-arcade)}
.arcade-overlay h2::after{display:none}
.ov-lead{margin:0;max-width:42ch;font-size:.94rem;color:#cbd5e1}
.ov-note{margin:0;max-width:44ch;font-size:.84rem;color:#94a3b8}
.ov-best{margin:0;font:500 .8rem/1 var(--mono);color:#94a3b8}
.arcade-overlay a{color:var(--c-arcade)}
.ov-score{display:flex;flex-direction:column;gap:.15rem;margin:.2rem 0}
.ov-score b{font:700 3rem/1 var(--cond);font-variant-numeric:tabular-nums;color:#fde68a}
.ov-score span{font:500 .75rem/1 var(--mono);text-transform:uppercase;letter-spacing:.12em;
  color:#94a3b8}

/* ---- the record deck, under Bone Comb's glass.
   The one cabinet with a piece of page inside the stage. It keeps the screen's
   dark palette in both themes rather than the card's, because it sits directly
   under the canvas and a light slab bolted to the bottom of a lit arena reads
   as a rendering fault. Everything in it is written by the game; the classes
   are here so a cabinet cannot ship its own stylesheet.
   Two columns down to 620px, where the little player goes full width above the
   controls rather than shrinking to a stamp beside them. */
.arcade-deck{display:grid;grid-template-columns:minmax(0,1fr);gap:.55rem;
  margin-top:.55rem;padding:.7rem .85rem;border:1px solid var(--rule);
  border-radius:8px;background:#0f0d18;color:#cbd5e1}
/* On the machine it sits on the bezel under the glass, so it loses the card's
   border the way the HUD above the glass does. */
.cabinet .arcade-deck{margin-top:.6rem;border-color:rgba(255,194,14,.16);
  background:rgba(0,0,0,.42)}
/* Picking "your own link" on the record screen sends the player down here, and
   the deck is below the glass they are looking at — so it has to say where they
   landed. Two seconds, then it goes back to being furniture. */
.arcade-deck.is-asked{border-color:var(--c-arcade);
  box-shadow:0 0 0 3px color-mix(in srgb,var(--c-arcade) 22%,transparent)}
@media (prefers-reduced-motion:no-preference){
  .arcade-deck{transition:border-color .18s,box-shadow .18s}
}
.arcade-deck .deck-head{display:flex;align-items:baseline;justify-content:space-between;
  gap:.8rem;flex-wrap:wrap}
.arcade-deck .deck-head b{font:600 .68rem/1 var(--mono);text-transform:uppercase;
  letter-spacing:.16em;color:#8b7fd8}
.deck-state{font:500 .78rem/1.3 var(--cond);color:#e2e8f0;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:60%}
.arcade-deck.is-live .deck-state{color:#a3e635}

.deck-body{display:grid;grid-template-columns:200px minmax(0,1fr);gap:.8rem;align-items:start}
/* 16:9 with the player filling it, and the placeholder centred in the same box
   so the row does not change height when a record goes on. */
.deck-screen{position:relative;aspect-ratio:16/9;border-radius:6px;overflow:hidden;
  background:#06050a;border:1px solid rgba(255,255,255,.08)}
.deck-screen iframe{position:absolute;inset:0;width:100%;height:100%;border:0}
/* Nothing may reach the iframe. A click that lands in it moves focus there and
   every keystroke after that belongs to YouTube rather than to the game — which
   on a cabinet played entirely with the arrow keys is the whole game gone. */
.deck-shield{position:absolute;inset:0;cursor:default}
.deck-blank{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;
  padding:.5rem;text-align:center;font:500 .7rem/1.4 var(--mono);color:#64748b}
.deck-blank[hidden]{display:none}

.deck-controls{display:flex;flex-direction:column;gap:.5rem;min-width:0}
.deck-row{display:flex;flex-wrap:wrap;align-items:flex-end;gap:.45rem}
.deck-row.deck-link{flex-wrap:nowrap}
.deck-field{display:flex;flex-direction:column;gap:.2rem;flex:1 1 8rem;min-width:0}
.deck-field.narrow{flex:0 0 5.2rem}
.deck-field span{font:500 .6rem/1 var(--mono);text-transform:uppercase;
  letter-spacing:.14em;color:#8b7fd8}
.deck-field input{width:100%;min-width:0;padding:.4rem .5rem;border-radius:5px;
  border:1px solid rgba(255,255,255,.14);background:#171426;color:#e2e8f0;
  font:500 .8rem/1.2 var(--mono)}
.deck-field input:focus-visible{outline:2px solid var(--c-arcade);outline-offset:1px}

.deck-btn{display:inline-flex;align-items:center;gap:.3rem;padding:.42rem .6rem;
  border:1px solid rgba(255,255,255,.16);border-radius:5px;background:#171426;
  color:#cbd5e1;cursor:pointer;font:600 .7rem/1 var(--mono);text-transform:uppercase;
  letter-spacing:.08em;white-space:nowrap}
.deck-btn:hover{border-color:var(--c-arcade);color:#fff}
.deck-btn:focus-visible{outline:2px solid var(--c-arcade);outline-offset:2px}
.deck-btn[disabled]{opacity:.5;cursor:default}
/* `display:inline-flex` above beats the UA stylesheet's `[hidden]{display:none}`,
   and the deck hides a button with the attribute — so it needs saying twice. */
.deck-btn[hidden]{display:none}
.deck-btn.primary{background:var(--c-arcade);border-color:var(--c-arcade);color:#1a1206}
.deck-btn.primary:hover{filter:brightness(1.1);color:#1a1206}
.deck-btn.tiny{padding:.32rem .45rem;font-size:.72rem}
.deck-btn kbd{padding:0 .22rem;border:1px solid rgba(255,255,255,.25);border-radius:3px;
  font:600 .62rem/1.3 var(--mono)}

/* Sting Curtain's shelf: nine records and the house tune, as one press each.
   `auto-fill` rather than a column count, so the row that is three across on a
   card is one across on a phone without a second breakpoint saying so. */
.deck-shelf{display:grid;grid-template-columns:repeat(auto-fill,minmax(9.5rem,1fr));
  gap:.35rem}
.deck-pick{display:flex;flex-direction:column;gap:.12rem;min-width:0;
  padding:.4rem .5rem;border:1px solid rgba(255,255,255,.16);border-radius:5px;
  background:#171426;color:#cbd5e1;cursor:pointer;text-align:left}
.deck-pick b{font:600 .74rem/1.25 var(--cond);color:#e2e8f0;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.deck-pick span{font:500 .62rem/1.3 var(--mono);color:#8b7fd8;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.deck-pick:hover{border-color:var(--c-arcade)}
.deck-pick:hover b{color:#fff}
.deck-pick:focus-visible{outline:2px solid var(--c-arcade);outline-offset:2px}
/* The one that is on. `aria-pressed` carries it rather than a class, so the
   lit row and what a screen reader is told cannot come apart.
   Literal honey rather than `--c-arcade`, which is the *theme's* arcade colour
   and goes dark under light mode — on a slab that stays near-black in both
   themes that reads as the lit row having gone out. */
.deck-pick[aria-pressed="true"]{border-color:#ffc20e;
  background:rgba(255,194,14,.12)}
.deck-pick[aria-pressed="true"] b{color:#ffc20e}

.deck-sync{display:inline-flex;align-items:center;gap:.3rem}
.deck-off{font:500 .68rem/1 var(--mono);color:#94a3b8;min-width:5.4rem;text-align:center}
.deck-note{margin:0;font:500 .7rem/1.5 var(--mono);color:#8ba0b8}
.deck-note.warn{color:#fca5a5}
/* The failure timeline. Selectable on purpose — every fault this cabinet can
   hit lives in another origin, so the trace is the only thing there is to
   hand anyone, and it has to be copyable out of the page. */
.deck-diag{margin:0;padding:.45rem .55rem;border:1px solid rgba(255,255,255,.12);
  border-radius:4px;background:rgba(0,0,0,.32);color:#7c8fa6;
  font:500 .64rem/1.6 var(--mono);white-space:pre-wrap;overflow-x:auto;user-select:text}
.deck-diag[hidden]{display:none}
/* A long trace must not grow the cabinet page; it scrolls in place. */
.deck-diag{max-height:11rem;overflow-y:auto}
/* `.deck-row` is a flex container, and `display:flex` beats the `hidden`
   attribute — so the deck's hidden rows need this said out loud, the same way
   `.deck-btn[hidden]` above already does. Without it the "can you hear it?"
   question is on the glass permanently. */
.deck-row[hidden]{display:none}

@media (max-width:620px){
  .deck-body{grid-template-columns:minmax(0,1fr)}
  .deck-row.deck-link{flex-wrap:wrap}
}

.arcade-keys{margin:0;font:500 .74rem/1.6 var(--mono);color:var(--soft)}
.arcade-keys kbd{display:inline-block;min-width:1.35rem;text-align:center;
  padding:.1rem .3rem;margin-right:.12rem;border:1px solid var(--rule-strong);
  border-bottom-width:2px;border-radius:4px;background:var(--raised);
  font:600 .7rem/1.3 var(--mono);color:var(--text)}

/* ---- the pick-up key, under the arena.
   Read once before the first run and glanced at afterwards, so the bead does
   most of the work and the words are only there to attach a name to a colour
   the first time. The bead is drawn the way the drop itself is — a flat circle
   with a ring — so the two read as the same object.
   Two fixed columns rather than a wrapping row: four of these across the stage
   land three-and-one at most desktop widths, and a key that breaks unevenly
   reads as a paragraph that got away rather than as a list of four things. */
.arcade-legend{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));
  gap:.25rem 1.1rem;margin:0;padding:0;list-style:none}
.arcade-legend li{display:flex;align-items:center;gap:.4rem;margin:0;
  font:500 .72rem/1.6 var(--mono);color:var(--soft)}
.arcade-legend b{font-weight:600;color:var(--text)}
/* The bead is fixed to the colour the drop falls in, so it cannot take a token
   that flips with the theme. The hairline round it is what keeps a pale mint
   dot from dissolving into the light theme's card. */
.arcade-legend .pu{width:.62rem;height:.62rem;border-radius:50%;flex:none;
  background:var(--pu);box-shadow:0 0 0 1px color-mix(in srgb,var(--text) 22%,transparent)}

.arcade-note{margin:0 0 .9rem;padding:.7rem .9rem;grid-column:1/-1;
  border:1px solid var(--rule);border-left:2px solid var(--accent);border-radius:6px;
  font-size:.87rem;color:var(--muted);background:var(--surface)}
.arcade-note.warn{border-left-color:var(--warn);
  background:color-mix(in srgb,var(--warn) 7%,var(--surface))}
.arcade-note[hidden]{display:none}

/* ---- scoreboard */
.arcade-board{min-width:0;border:1px solid var(--rule);border-radius:8px;
  background:linear-gradient(180deg,var(--raised),var(--surface));overflow:hidden}
/* Full width under the screen, so the head reads across rather than down: the
   title at one end and who you are signed in as at the other. It wraps back to
   two lines when there is not room for both. */
.board-head{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;
  gap:.5rem .9rem;padding:.85rem .9rem;border-bottom:1px solid var(--rule)}
.board-head h2{margin:0;border:0;padding:0;font:600 .72rem/1 var(--mono);
  text-transform:uppercase;letter-spacing:.14em;color:var(--soft)}
.board-head h2::after{display:none}
.board-auth{display:flex;align-items:center;gap:.45rem;min-height:1.9rem;flex-wrap:wrap}
.board-auth .face{border-radius:50%;flex:none}
.auth-who{font:600 .85rem/1 var(--cond);color:var(--text);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.auth-note{font:500 .72rem/1.4 var(--mono);color:var(--soft)}

.board-list{list-style:none;margin:0;padding:0;max-height:26rem;overflow-y:auto}
.board-list li{display:grid;grid-template-columns:1.6rem auto minmax(0,1fr) auto auto;
  align-items:center;gap:.5rem;margin:0;padding:.42rem .9rem;
  border-bottom:1px solid var(--rule);font-size:.85rem}
.board-list li:last-child{border-bottom:0}
/* Your own row is the one people scan for. */
.board-list li.is-me{background:var(--accent-wash)}
.board-list .rank{font:600 .72rem/1 var(--mono);color:var(--soft);text-align:right;
  font-variant-numeric:tabular-nums}
.board-list li:nth-child(-n+3) .rank{color:var(--c-arcade)}
.board-list .face{width:1.5rem;height:1.5rem;border-radius:50%;flex:none;
  background:var(--rule-strong);object-fit:cover}
.board-list .who{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--text)}
.board-list .wv{font:500 .68rem/1 var(--mono);color:var(--soft)}
.board-list .pts{font:600 .88rem/1 var(--cond);font-variant-numeric:tabular-nums;
  color:var(--text)}
.board-note{margin:0;padding:.7rem .9rem;border-top:1px solid var(--rule);
  font-size:.78rem;line-height:1.5;color:var(--soft)}

/* ---- the master leaderboard, at the top of /arcade. Same slab as a cabinet's
   board; a row is a <details> so a player opens onto the cabinets they hold. */
.standings{margin:1.4rem 0 2.4rem;border:1px solid var(--rule);border-radius:8px;
  background:linear-gradient(180deg,var(--raised),var(--surface));overflow:hidden}
.st-tabs{display:flex;gap:.3rem}
.st-tab{font:600 .72rem/1 var(--mono);text-transform:uppercase;letter-spacing:.12em;
  padding:.55rem .75rem;border:1px solid var(--rule);border-radius:6px;cursor:pointer;
  background:transparent;color:var(--soft)}
.st-tab[aria-selected="true"]{color:var(--text);border-color:var(--c-arcade);background:var(--accent-wash)}
.st-window{margin:0;font:500 .72rem/1.4 var(--mono);color:var(--soft)}
.st-cols,.standings summary{display:grid;align-items:center;gap:.5rem;
  grid-template-columns:2rem 1.5rem minmax(0,1fr) repeat(3,2.6rem)}
.st-cols{padding:.5rem .9rem;border-bottom:1px solid var(--rule);
  font:600 .66rem/1 var(--mono);text-transform:uppercase;letter-spacing:.1em;color:var(--soft)}
.st-cols span:nth-child(n+4){text-align:right}
.st-list{list-style:none;margin:0;padding:0}
.st-list>li{margin:0;border-bottom:1px solid var(--rule)}
.st-list>li:last-child{border-bottom:0}
.standings summary{padding:.5rem .9rem;cursor:pointer;list-style:none;font-size:.88rem}
.standings summary::-webkit-details-marker{display:none}
.standings summary:hover,.standings details[open] summary{background:var(--accent-wash)}
.standings .rank{font:600 .72rem/1 var(--mono);color:var(--soft);text-align:right;
  font-variant-numeric:tabular-nums}
.st-list>li:nth-child(-n+3) .rank{color:var(--c-arcade)}
.standings .face{width:1.5rem;height:1.5rem;border-radius:50%;
  background:var(--rule-strong);object-fit:cover}
.standings .who{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--text)}
.standings .n{text-align:right;font:600 .95rem/1 var(--cond);font-variant-numeric:tabular-nums;color:var(--soft)}
/* The column the list is ordered by is the one that reads. */
[data-sorted="firsts"] .n[data-col="firsts"],
[data-sorted="seconds"] .n[data-col="seconds"]{color:var(--text)}
.st-places{list-style:none;margin:0;padding:.2rem .9rem .7rem 4.5rem;font-size:.82rem}
.st-places li{display:grid;grid-template-columns:2.2rem minmax(0,1fr) auto;gap:.5rem;
  align-items:center;margin:0;padding:.22rem 0}
.st-place{font:600 .7rem/1 var(--mono);color:var(--soft)}
.st-place.p1{color:var(--c-arcade)}
.st-places .pts{font:600 .82rem/1 var(--cond);font-variant-numeric:tabular-nums;color:var(--muted)}
@media (max-width:600px){
  .st-cols,.standings summary{grid-template-columns:1.6rem 1.5rem minmax(0,1fr) repeat(3,2rem)}
  .st-places{padding-left:.9rem}
}

/* ---- buttons inside the cabinet */
.arcade .btn{display:inline-flex;align-items:center;justify-content:center;gap:.45rem;
  min-height:2.5rem;padding:.5rem 1.2rem;border:1px solid transparent;border-radius:6px;
  font:600 .76rem/1 var(--mono);text-transform:uppercase;letter-spacing:.1em;
  text-decoration:none;cursor:pointer;transition:filter .14s,background .14s,color .14s}
/* 44px, like the vault's controls — Start is the one button on this page that
   is certainly going to be pressed with a thumb. */
.arcade .btn.primary{margin-top:.35rem;color:#1a1206;background:var(--c-arcade);border:0;
  min-height:2.75rem;padding:.7rem 1.7rem;font-size:.82rem}
.arcade .btn.primary:hover{filter:brightness(1.1)}
/* Discord's own blurple, on purpose — a sign-in button that does not look like
   the service it signs you into is a button people hesitate over. */
.arcade .btn.discord{color:#fff;background:#5865f2;min-width:9rem}
.arcade .btn.discord:hover{background:#4752c4}
.arcade .btn.ghost{color:var(--soft);background:none;border-color:var(--rule-strong);
  min-height:1.9rem;padding:.3rem .6rem;font-size:.66rem;margin-left:auto}
.arcade .btn.ghost:hover{color:var(--text);border-color:var(--accent)}
.arcade .btn:focus-visible{outline:2px solid var(--accent);outline-offset:2px}

/* ------------------------------------------------------------- cabinet ---
   The upright the arena sits in: hood, marquee, bezel, control panel, coin
   door, kick plate, T-molding. Everything build.js emits around `.arcade`.

   Two things decide the shape of this block, and both are worth reading before
   changing any of it.

   1. THE CHROME DOES NOT COME OUT OF THE ARENA. The canvas is a fixed 900x600
      surface scaled by CSS and the type the games draw is already at its floor
      (Hive Mind's smallest label is 10.5 canvas px). So the cabinet breaks OUT
      past --wide rather than squeezing the glass: from 1280 up the arena is
      ~1045px against the 926 it had, and at its narrowest it is 973.

   2. IT IS ADDITIVE. Everything below the breakpoint is the page as it was —
      the base rules here only make the wrappers inert, and every cabinet rule
      lives inside the one min-width query. Not tidiness: it is the only
      arrangement in which "a phone keeps the layout it had" is something the
      stylesheet guarantees, rather than something a second set of rules tries
      to put back and gets subtly wrong.

   The breakpoint is 1200px, NOT the drawer's 1140 or the phone block's 900.
   Below ~1200 there is no width to spend on furniture: a cabinet at 901px left
   the arena 737px wide against the 869 it gets there today, which is rule 1
   broken. 1200 is where the machine can wear its side panels and still hand
   the glass more than it had.

   The cabinet is dark in both themes, like the screen already is. A cream
   arcade cabinet is a different piece of furniture; on paper it only loses the
   glow.

   Proportions: a real upright is about twice as tall as it is wide. This is
   roughly square, because the tall part of a real one is the empty box below
   the panel and the glass here is 3:2 — the honest shape for this arena is a
   widescreen upright. The parts that are pure furniture (coin door, kick
   plate) are held to the shortest height that still reads. */

/* ---- inert: no cabinet, the page exactly as it was.
   Every furniture element has to be named here. `.cabinet-art` was the one
   left out, and it does not fail subtly: outside the query it has no sizing,
   so the two bezel SVGs render at their intrinsic size in the flow and a phone
   gets a screenful of honeycomb where the game should be. It survived a
   box-for-box comparison against the old markup because the boxes compared
   kept their width and height — the art was pushing them *down* the page.
   Anything added to the cabinet goes in this list at the same time. */
.cabinet{margin:1.5rem 0 2.4rem}
.cabinet-hood,.cabinet-head,.cabinet-vent,.cabinet-art,.cabinet-door,
.cabinet-kick,.cabinet-lip,.cabinet-sticks,.cabinet-start{display:none}
.cabinet .arcade{margin:0}
.cabinet-panel{margin-top:.55rem}
.cabinet+.arcade-board{margin:1.1rem 0 2.4rem}

@media (min-width:1200px){
.cabinet{
  --cab-w:min(76rem,calc(100vw - 4rem));
  /* Stops growing where --cab-w stops growing. With a wider ceiling the side
     panels kept widening after the cabinet had hit its 76rem cap, so the arena
     got *smaller* as the window got bigger — 1044px at 1280 down to 1010 at
     1600, which is backwards. */
  --cab-side:clamp(3.4rem,5.4vw,4.4rem);
  --wood:#1b1710;          /* the cabinet's laminate */
  --wood-hi:#2f281c;
  --wood-lo:#0c0a07;
  --tmold:#e9b420;         /* the plastic edging down the vertical edges */
  --plate:#0a0805;         /* the black bezel the monitor is set into */
  --cab-glass:#05070c;

  position:relative;
  /* The breakout. The cabinet is wider than the column it stands in, so the
     margins go negative — and it stays centred on the column, not the window.
     4rem of air rather than 2: at the breakpoint itself the machine filled the
     window edge to edge, and the panel's perspective flare had nowhere to go. */
  margin:2.2rem calc((100% - var(--cab-w))/2) 3.4rem;
  padding:0 .55rem;
  background:
    /* textured laminate. A flat area this size with a flat colour on it reads
       as a div, not as a moulded box. */
    repeating-linear-gradient(112deg,rgba(255,255,255,.016) 0 2px,transparent 2px 5px),
    linear-gradient(90deg,rgba(0,0,0,.55) 0,transparent 7%,transparent 93%,rgba(0,0,0,.55) 100%),
    linear-gradient(180deg,var(--wood-hi) 0,var(--wood) 6%,var(--wood) 88%,var(--wood-lo) 100%);
  border-radius:14px 14px 3px 3px;
  box-shadow:inset 0 1px 0 rgba(255,255,255,.07),0 40px 70px -28px rgba(0,0,0,.9);
}
/* T-molding: the bright plastic edging down both vertical edges. It is the
   most recognisable thing about an upright and it costs two pseudo-elements. */
.cabinet::before,.cabinet::after{
  content:"";position:absolute;top:.5rem;bottom:1.1rem;width:6px;border-radius:3px;
  background:linear-gradient(90deg,#7a5c0c,var(--tmold) 45%,#fff2b8 55%,#8a6a10);
  box-shadow:0 0 12px -2px rgba(233,180,32,.5);
}
.cabinet::before{left:-1px}
.cabinet::after{right:-1px}

/* ---- hood: the lid over the marquee. The shadow it throws down the sign is
   what makes the marquee read as recessed rather than printed on. */
.cabinet-hood{
  display:block;position:relative;z-index:2;margin:0 -1.1rem;height:1.15rem;
  border-radius:12px 12px 2px 2px;
  background:linear-gradient(180deg,#3a3125,#1d1810 60%,#0b0906);
  box-shadow:0 6px 12px -4px rgba(0,0,0,.95),inset 0 1px 0 rgba(255,255,255,.12);
}

/* ---- head: vent | marquee | vent */
.cabinet-head{
  display:grid;grid-template-columns:5.5rem minmax(0,1fr) 5.5rem;gap:.5rem;
  padding:.5rem .5rem .6rem;
  background:linear-gradient(180deg,#100d09,#171208);
}
.cabinet-vent{
  display:block;border-radius:4px;opacity:.9;
  background:repeating-linear-gradient(180deg,#000 0 3px,#221b10 3px 7px);
  box-shadow:inset 0 0 0 1px rgba(0,0,0,.9),inset 0 0 12px rgba(0,0,0,.8);
}
.cabinet-marquee{
  position:relative;display:flex;align-items:center;justify-content:center;gap:1rem;
  /* held open, so the head's height does not move when the board arrives —
     anything that grows here pushes the glass down the page */
  min-height:4.3rem;
  padding:.9rem 1.4rem;border-radius:3px;border:1px solid rgba(255,194,14,.3);
  background:linear-gradient(180deg,rgba(255,194,14,.22),rgba(255,194,14,.05) 60%,
    rgba(0,0,0,.4)),#130f08;
  box-shadow:
    inset 0 0 40px rgba(255,194,14,.25),
    inset 0 3px 10px rgba(0,0,0,.7),          /* the hood's shadow across the top */
    0 0 40px -8px rgba(255,194,14,.5);
  overflow:hidden;
}
/* the fluorescent tube behind the acrylic */
.cabinet-marquee::before{
  content:"";position:absolute;inset:0 0 auto;height:40%;pointer-events:none;
  background:linear-gradient(180deg,rgba(255,255,255,.15),transparent);
}
.cabinet-bee{width:2.3rem;height:2.3rem;flex:none;color:var(--c-arcade);opacity:.92}
.cabinet-bee svg{width:100%;height:100%;display:block}

/* ---- what the sign says: the board, not the game's name. The name is the
   page's own heading a screen above, and a cabinet that repeats it spends its
   most visible surface on something already said. One line, three entries, no
   wrapping — names clip rather than push the scores out of line. */
.cabinet-mq{display:flex;align-items:baseline;justify-content:center;gap:1.5rem;
  min-width:0;overflow:hidden}
.cabinet-mq .mq-k{font:600 .68rem/1 var(--mono);text-transform:uppercase;
  letter-spacing:.2em;color:#c79a2a;flex:none}
.mq-row{display:flex;align-items:baseline;gap:.5rem;min-width:0}
.mq-rank{font:700 .82rem/1 var(--mono);color:#8d7442;flex:none;
  font-variant-numeric:tabular-nums}
/* First place gets the lamp. Nothing in the strip is brighter than a score,
   and the top score is what people are here to beat. */
.mq-row:nth-of-type(1) .mq-rank{color:var(--c-arcade)}
.mq-who{font:600 1.05rem/1 var(--cond);color:#e8d6a8;
  max-width:11ch;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.mq-pts{font:700 1.45rem/1 var(--cond);color:#ffe89c;flex:none;
  font-variant-numeric:tabular-nums;
  text-shadow:0 0 18px rgba(255,194,14,.55),0 1px 0 rgba(0,0,0,.7)}
.mq-row:nth-of-type(1) .mq-pts{font-size:1.75rem}
/* Capped at 2.3rem: at 2.7 the fallback was 5px taller than the board state,
   and the glass moved down the page depending on whether the API answered. */
.mq-name{font:700 clamp(1.5rem,3vw,2.3rem)/1 var(--cond);
  text-transform:uppercase;letter-spacing:.07em;color:#ffe89c;white-space:nowrap;
  text-shadow:0 0 20px rgba(255,194,14,.6),0 1px 0 rgba(0,0,0,.7)}
.mq-none{font:500 .92rem/1 var(--cond);color:#c9b98f;letter-spacing:.02em;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}

/* ---- monitor: the black plate the tube is set into, shoulders cut off the
   top corners the way a real bezel is, comb printed in the flanks. Bezel art
   is where an upright's artwork actually reads — side art only exists for
   someone standing beside the machine, and a web page is always front-on. */
.cabinet-monitor{
  position:relative;margin:0 .5rem;padding:1rem var(--cab-side) 1.1rem;
  background:linear-gradient(180deg,#0d0b07,var(--plate) 30%);
  clip-path:polygon(0 2.2rem,2.2rem 0,calc(100% - 2.2rem) 0,100% 2.2rem,100% 100%,0 100%);
  box-shadow:inset 0 0 0 1px rgba(0,0,0,.9);
}
.cabinet-art{position:absolute;top:0;bottom:0;width:var(--cab-side);overflow:hidden;
  color:var(--c-arcade);opacity:.14;pointer-events:none}
.cabinet-art.l{left:0}
.cabinet-art.r{right:0}
.cabinet-art svg{position:absolute;inset:0;width:100%;height:100%}

.cabinet .arcade{gap:0}
.cabinet .arcade-stage{gap:0}
/* HUD: printed on the bezel above the glass, not a card sitting on the page. */
.cabinet .arcade-hud{border:0;border-radius:0;background:transparent;
  padding:.1rem .2rem .55rem}
.cabinet .hud-stat .k{color:#8d7f5e}
.cabinet .hud-stat b{color:#ffe89c}
.cabinet .meter{background:rgba(255,255,255,.13)}
.cabinet .hud-mute{background:#15120c;border-color:rgba(255,194,14,.32);color:#c9b587}
.cabinet .hud-mute:hover{color:#ffe89c;border-color:var(--c-arcade)}

/* The glass. Both layers over it are pointer-events:none, so Start still takes
   the click and the canvas still takes the pointer. */
.cabinet .arcade-screen{
  border:0;border-radius:10px/14px;background:var(--cab-glass);
  box-shadow:inset 0 0 0 3px #000,inset 0 0 90px rgba(0,0,0,.95),
    0 0 0 1px rgba(255,255,255,.06),0 0 60px -14px rgba(120,180,255,.3);
}
.cabinet .arcade-screen::before,
.cabinet .arcade-screen::after{content:"";position:absolute;inset:0;pointer-events:none}
.cabinet .arcade-screen::before{
  background:repeating-linear-gradient(180deg,rgba(0,0,0,.24) 0 1px,transparent 1px 3px),
    radial-gradient(ellipse at center,transparent 52%,rgba(0,0,0,.6) 100%);
  mix-blend-mode:multiply;opacity:.8;
}
.cabinet .arcade-screen::after{
  background:linear-gradient(152deg,rgba(255,255,255,.10) 0,rgba(255,255,255,.02) 20%,
    transparent 40%);
}

/* ---- control panel. The tilt is the point — it is what says "you stand at
   this" rather than "you look at this" — but it stops at 26deg because the
   instruction card is real text that has to stay readable, not a texture.
   Inset by 2rem so that the flare lands flush with the body. rotateX under a
   perspective grows the panel's near edge by about 5% of its width — 32px a
   side at this size — and a panel laid out flush therefore renders *past* the
   cabinet: at 1280, where --cab-w hits its 76rem cap and the window has only
   25px of air either side, that was 7px of horizontal page overflow. Letting
   it flare out to the cabinet's own edge fixes the overflow and reads better
   than a panel breaking through the T-molding. */
.cabinet-panelwrap{position:relative;z-index:3;margin:-.2rem 0 0;perspective:1200px}
.cabinet-panel{
  transform:rotateX(26deg);transform-origin:top center;
  margin:0 2rem;padding:1.15rem 1.5rem 1.3rem;border-radius:2px 2px 6px 6px;
  background:linear-gradient(180deg,rgba(255,255,255,.14),transparent 14%),
    linear-gradient(180deg,#2b2317,#1a1409 60%,#100c06);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.16),0 -6px 14px -6px rgba(0,0,0,.9),
    0 18px 26px -14px rgba(0,0,0,.95);
  display:grid;grid-template-columns:auto minmax(0,1fr) auto;align-items:center;gap:1.6rem;
}
/* the front edge of the panel, seen because the panel is lying back */
.cabinet-lip{display:block;height:1.4rem;border-radius:0 0 6px 6px;
  background:linear-gradient(180deg,#241d12,#0c0906);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.08)}

.cabinet-sticks{display:flex;align-items:center;gap:1.4rem;flex:none}
.cabinet-stick{width:5rem;height:5rem;display:block}
.cabinet-stick svg{width:100%;height:100%;display:block}
/* Six buttons in the staggered two-row arc a real panel has, not a grid. */
.cabinet-btns{display:flex;flex-direction:column;gap:.5rem}
.cabinet-btnrow{display:flex;gap:.55rem}
.cabinet-btnrow:last-child{margin-left:1rem}
.cabinet-btn{
  width:1.95rem;height:1.95rem;border-radius:50%;
  background:radial-gradient(circle at 34% 28%,#fff3c4,var(--btn,#e0a800) 52%,#5e4300);
  box-shadow:0 0 0 2px rgba(0,0,0,.7),0 3px 4px rgba(0,0,0,.8),
    inset 0 -3px 4px rgba(0,0,0,.5);
}
.cabinet-btnrow .cabinet-btn:nth-child(2){--btn:#e07a4a}
.cabinet-btnrow .cabinet-btn:nth-child(3){--btn:#d9789a}
.cabinet-btnrow:last-child .cabinet-btn:nth-child(1){--btn:#7ba7e8}
.cabinet-btnrow:last-child .cabinet-btn:nth-child(2){--btn:#6fbf73}
.cabinet-btnrow:last-child .cabinet-btn:nth-child(3){--btn:#c98bd9}

.cabinet-start{display:flex;flex-direction:column;gap:.55rem;flex:none;text-align:center}
.cabinet-sbtn{
  display:flex;align-items:center;justify-content:center;
  width:3.6rem;height:1.3rem;border-radius:3px;
  font:600 .56rem/1 var(--mono);letter-spacing:.1em;color:#2a1d00;
  background:linear-gradient(180deg,#ffe89c,#e0a800 60%,#a97c00);
  box-shadow:0 0 0 2px rgba(0,0,0,.7),0 2px 3px rgba(0,0,0,.8);
}
.cabinet-sbtn.p2{background:linear-gradient(180deg,#cfe3ff,#7ba7e8 60%,#3f6cab);color:#0b1a2e}

/* the instruction card, silk-screened onto the panel between the controls */
.cabinet-card{min-width:0;padding:.6rem .85rem;border-radius:3px;
  background:rgba(0,0,0,.32);box-shadow:inset 0 0 0 1px rgba(255,194,14,.18)}
.cabinet-card .arcade-keys{margin:0;color:#c2b189}
.cabinet-card .arcade-keys kbd{background:#0e0b06;border-color:rgba(255,194,14,.38);
  border-bottom-color:rgba(255,194,14,.55);color:#ffe89c}
.cabinet-card .arcade-legend{margin-top:.5rem}
.cabinet-card .arcade-legend li{color:#a4926c}
.cabinet-card .arcade-legend b{color:#ffe89c}

/* ---- coin door and kick plate. Both are held to the shortest height that
   still reads: give this section the quarter of the machine it has on a real
   upright and it becomes a black void with one small door in the middle. */
.cabinet-door{display:flex;align-items:center;justify-content:center;
  padding:.85rem 0 .8rem;background:linear-gradient(180deg,#151109,#0d0a06)}
.cabinet-coindoor{
  display:grid;grid-template-columns:auto auto auto auto auto;align-items:center;
  gap:1.5rem;padding:.6rem 1.6rem;border-radius:3px;
  background:linear-gradient(180deg,#2a2216,#120e07);
  box-shadow:inset 0 0 0 1px rgba(233,180,32,.35),inset 0 2px 6px rgba(0,0,0,.8),
    0 2px 0 rgba(255,255,255,.04);
}
.cabinet-mech{display:flex;flex-direction:column;align-items:center;gap:.32rem}
.cabinet-mech span{font:600 .46rem/1 var(--mono);letter-spacing:.14em;color:#7b6c4a}
.cabinet-slot{width:.4rem;height:1.45rem;border-radius:2px;background:#000;
  box-shadow:inset 0 0 0 1px rgba(233,180,32,.45),0 0 9px -3px rgba(233,180,32,.6)}
/* the coin return cup, pressed into the door */
.cabinet-return{width:2.6rem;height:1.5rem;border-radius:2px 2px 8px 8px;
  background:linear-gradient(180deg,#000,#0d0a06);
  box-shadow:inset 0 3px 6px rgba(0,0,0,.95),inset 0 0 0 1px rgba(233,180,32,.22)}
.cabinet-plate{font:600 .6rem/1.5 var(--mono);text-transform:uppercase;
  letter-spacing:.16em;color:#ffd75a;text-align:center}
.cabinet-plate i{display:block;font-style:normal;font-size:.52rem;color:#7b6c4a;
  letter-spacing:.12em}
.cabinet-plate b{animation:cabinet-blink 1.5s steps(1,end) infinite}
@keyframes cabinet-blink{50%{opacity:.2}}

/* No levelers drawn. Front-on you see the kick plate meeting the floor, not a
   cabinet's feet — two blocks down there read as two stray rectangles. The
   floor shadow does the job they were there for. */
.cabinet-kick{position:relative;display:flex;align-items:center;justify-content:center;
  height:1.9rem;padding:0 1.6rem;border-radius:0 0 3px 3px;
  background:linear-gradient(180deg,#0a0806,#050403);
  box-shadow:inset 0 3px 8px -4px rgba(0,0,0,.9)}
.cabinet-kick::after{content:"";position:absolute;left:4%;right:4%;bottom:-1.6rem;
  height:1.8rem;pointer-events:none;
  background:radial-gradient(ellipse at center,rgba(0,0,0,.6),transparent 70%)}
.cabinet-badge{font:600 .58rem/1 var(--mono);text-transform:uppercase;
  letter-spacing:.24em;color:#5f5540}

/* The board stands beside the machine, back at the column's width: it is the
   one thing here you read with the game stopped. */
.cabinet+.arcade-board{margin:0 0 2.2rem}

:root[data-theme="light"] .cabinet{
  box-shadow:inset 0 1px 0 rgba(255,255,255,.07),0 30px 56px -26px rgba(43,37,29,.6);
}
}
@media (prefers-reduced-motion:reduce){.cabinet-plate b{animation:none}}

/* ------------------------------------------------------------- footer ---- */
.foot{border-top:1px solid var(--rule);margin-top:2rem;
  padding:1.6rem clamp(1rem,3vw,2rem);display:flex;gap:1rem;flex-wrap:wrap;
  justify-content:space-between;max-width:82rem;margin-inline:auto;
  font:500 .77rem/1 var(--mono);color:var(--soft)}
.foot-links{display:flex;gap:1.2rem}
.foot a{color:var(--soft);text-decoration:none}
.foot a:hover{color:var(--accent)}

/* --------------------------------------------------------------- home ---- */
main.is-home article{max-width:none}

/* An opener sets the page's name at display size and needs the full column
   for its grids, so the template's own heading block stands down. The h1 stays
   in the document for screen readers and for the outline — it is only the
   printed copy of it that would be a duplicate. */
main.is-opened article{max-width:none}
main.is-opened .page-head{border-bottom:0;padding:0;margin:0;position:static}
main.is-opened .page-head::after{display:none}
main.is-opened .subtitle{display:none}
main.is-opened .page-head h1{
  position:absolute;width:1px;height:1px;margin:-1px;padding:0;
  overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0;
}
/* the opener is full-bleed, so it must not start below a breadcrumb's margin */
main.is-opened .crumb{margin-bottom:.9rem}

.lockup{
  display:block;width:100%;height:auto;
  filter:drop-shadow(0 18px 42px rgba(0,0,0,.55));
}
:root[data-theme="light"] .lockup{filter:drop-shadow(0 14px 30px rgba(43,37,29,.28))}

/* -------------------------------------------------------------- opener ----
   The home page's first screen: the mark on the left, the claim on the right.
   Full-bleed out of the shell so the glow reaches the window edges, with the
   content pulled back to the shell's own width. */
.opener{
  position:relative;isolation:isolate;overflow:hidden;
  margin:0 calc(50% - 50vw) 0;padding:2.6rem calc(50vw - 50% + clamp(1rem,3vw,2rem)) 4.6rem;
  display:grid;grid-template-columns:minmax(0,1.05fr) minmax(0,.95fr);
  align-items:center;gap:2.4rem;
  background:
    radial-gradient(70% 100% at 26% 46%,var(--accent-wash),transparent 62%),
    radial-gradient(90% 80% at 88% 0%,color-mix(in srgb,var(--accent) 6%,transparent),transparent 60%);
  border-bottom:1px solid var(--rule);
}
.combs{position:absolute;inset:0;z-index:-1;pointer-events:none;opacity:.55}
.combs path{fill:none;stroke:var(--accent);stroke-width:2}
.opener-art{min-width:0}
/* A section that leads on a picture rather than the mark. Bordered rather than
   bled, so it reads as a screenshot of the game and not as site furniture. */
.opener.is-art .opener-img{
  display:block;width:100%;height:auto;
  border:1px solid var(--rule);box-shadow:0 22px 50px -26px #000;
}
:root[data-theme="light"] .opener.is-art .opener-img{
  box-shadow:0 16px 34px -22px rgba(43,37,29,.5);
}
/* The stats variant leads with the copy — there is no mark to look at first,
   so the headline takes the wider column and the rail sits beside it. */
.opener.is-stats{grid-template-columns:minmax(0,1.15fr) minmax(0,.85fr);
  padding-top:2.2rem;padding-bottom:3rem}
.opener.is-stats .opener-art{order:2}
.opener.is-stats .opener-copy{order:1;max-width:38rem}
.opener-copy{min-width:0;max-width:34rem}
.opener-copy>p{margin:0 0 1rem;max-width:none}
.opener-copy>p:last-child{margin-bottom:0}

.eyebrow{font:700 .78rem/1 var(--cond);text-transform:uppercase;
  letter-spacing:.16em;color:var(--accent);margin:0 0 .85rem !important}
.eyebrow em{font-style:italic;color:var(--text);opacity:.85}

.opener-head{
  font:700 clamp(2.4rem,1.6rem + 3.6vw,4rem)/.94 var(--cond);
  text-transform:uppercase;letter-spacing:.005em;text-wrap:balance;
  margin:0 0 1rem !important;
}
.opener-head .chrome{
  background:var(--chrome);-webkit-background-clip:text;background-clip:text;
  color:transparent;-webkit-text-fill-color:transparent;
  filter:drop-shadow(0 2px 0 var(--chrome-edge));
}
.opener-head .gold{
  background:linear-gradient(178deg,
    color-mix(in srgb,var(--accent) 55%,#fff) 0%,var(--accent) 42%,
    color-mix(in srgb,var(--accent) 62%,#000) 70%,var(--accent-hover) 100%);
  -webkit-background-clip:text;background-clip:text;
  color:transparent;-webkit-text-fill-color:transparent;
  filter:drop-shadow(0 2px 0 var(--chrome-edge));
}

.opener-cta{display:flex;align-items:center;gap:1.3rem;flex-wrap:wrap;
  margin:1.5rem 0 0 !important}
.opener-foot{font:500 .7rem/1.45 var(--mono);color:var(--soft);
  text-transform:uppercase;letter-spacing:.08em}
.opener-foot b{display:block;color:var(--muted);font-weight:500}

/* the skewed slab control, shared with the arcade's Play buttons */
.btn{
  display:inline-block;position:relative;
  padding:.75rem 1.9rem;border:0;cursor:pointer;text-decoration:none;
  font:700 .86rem/1 var(--cond);text-transform:uppercase;letter-spacing:.1em;
  color:#1a1200;
  background:linear-gradient(100deg,
    color-mix(in srgb,var(--accent) 80%,#000),var(--accent-hover) 60%,var(--accent));
  transform:skewX(var(--skew));
  box-shadow:0 0 0 2px var(--chrome-edge),0 12px 30px -14px var(--accent-glow);
  transition:filter .18s,transform .18s;
}
:root[data-theme="light"] .btn{color:#fffdf8}
.btn>span{display:block;transform:skewX(calc(var(--skew) * -1))}
.btn:hover{filter:brightness(1.09);transform:skewX(var(--skew)) translateY(-2px);color:#1a1200}
:root[data-theme="light"] .btn:hover{color:#fffdf8}
.btn.sm{padding:.5rem 1.15rem;font-size:.76rem}

/* ----------------------------------------------------------- band head ----
   Heading left, its explanation right. Two columns rather than a stacked
   heading and lede, so a section announces itself in one horizontal band. */
.band-head{
  display:flex;align-items:flex-end;justify-content:space-between;
  gap:2rem;flex-wrap:wrap;margin:3.4rem 0 1.6rem;
}
.band-head>div{min-width:0}
.band-h{
  font:700 clamp(1.7rem,1.2rem + 2vw,2.6rem)/1 var(--cond) !important;
  text-transform:uppercase;letter-spacing:.01em;
  margin:0 !important;padding:0 !important;border:0 !important;
  text-wrap:balance;
}
.band-h::before,.band-h::after{display:none !important}
.band-h .chrome{
  background:var(--chrome);-webkit-background-clip:text;background-clip:text;
  color:transparent;-webkit-text-fill-color:transparent;
  filter:drop-shadow(0 2px 0 var(--chrome-edge));
}
.band-h .gold{
  background:linear-gradient(178deg,
    color-mix(in srgb,var(--accent) 55%,#fff) 0%,var(--accent) 42%,
    color-mix(in srgb,var(--accent) 62%,#000) 70%,var(--accent-hover) 100%);
  -webkit-background-clip:text;background-clip:text;
  color:transparent;-webkit-text-fill-color:transparent;
  filter:drop-shadow(0 2px 0 var(--chrome-edge));
}
.band-note{margin:0 !important;color:var(--muted);max-width:42ch;font-size:.92rem}
main.is-home .hero-copy>p:first-of-type{font-size:1.1rem;color:var(--muted);max-width:58ch}
main.is-home .cards{grid-template-columns:repeat(auto-fit,minmax(17rem,1fr))}

/* ------------------------------------------------------------ responsive - */
@media (max-width:860px){
  .board-list{max-height:20rem}

  /* The opener kept its two columns all the way down, which on a phone left the
     claim wrapping every four words and pushed the stat rail off the side of
     the screen — clipped rather than scrolled, because .opener hides its own
     overflow, so the numbers were simply gone. One column, and the rail sits
     under the copy it annotates. */
  .opener,.opener.is-stats{grid-template-columns:minmax(0,1fr);gap:1.7rem}
  .opener{padding-top:2rem;padding-bottom:2.6rem}
  .opener-copy,.opener.is-stats .opener-copy{max-width:none}
  /* A long single word — "Factorio", "Abomination" — is wider than a 320px
     column at display size, and every one of these is a proper noun no hyphen
     dictionary will break. */
  .opener-head{overflow-wrap:anywhere}
  /* Stacked, the rail is three full-width slabs of its own. Let it pair up
     while there is room for two. */
  .astats{grid-template-columns:repeat(auto-fit,minmax(12rem,1fr))}
  /* The mark at the full width of a phone is a screen and a half of logo
     before a word of the page. */
  .opener:not(.is-stats):not(.is-art) .opener-art{max-width:20rem;margin-inline:auto}
}
@media (max-width:600px){
  /* No room for the bar beside the counters at this width; give it a row of
     its own between them and the buff tray. */
  .hud-meter{flex-basis:100%;order:2}
  /* The toggle is the one thing here you press rather than read, so it wants a
     thumb-sized target at the end of the counters row. */
  .hud-mute{width:2.2rem;height:2.2rem}
  .arcade-overlay h2{font-size:1.55rem}
  .ov-score b{font-size:2.2rem}
  .arcade-overlay{gap:.45rem;padding:1rem}
  /* Nothing here is a keyboard hint that helps on a phone. */
  .arcade-keys{font-size:.7rem}
  /* Two columns at this width puts every note on a second line. One up, so a
     pick-up's name and what it does stay on the same row. */
  .arcade-legend{grid-template-columns:minmax(0,1fr)}
  .arcade-legend li{font-size:.68rem}
}

/* Below this the top nav becomes a drawer: the panels flatten into an
   indented outline so every level stays reachable by scrolling.
   1140px, not 900: the row of section links is 811px wide on its own, and with
   the brand and the head tools beside it the masthead wanted about 1140. Below
   that it was overflowing the window rather than collapsing — at 1024, an iPad
   held sideways, by 115px. */
@media (max-width:1140px){
  .nav-toggle{display:inline-flex}
  .brand{margin-right:auto}
  /* Hung off the masthead rather than pinned to a guessed offset from the top
     of the window. Fixed at 3.6rem it slid under a masthead 4.4rem tall, and
     its own height ran past the bottom of the screen by however far it had
     been mispositioned — which put the last few links out of reach. */
  .topnav{
    position:absolute;top:100%;left:0;right:0;z-index:30;margin-right:0;
    background:var(--bg);border-bottom:1px solid var(--rule);
    padding:.9rem 1.25rem 1.4rem;max-height:calc(100dvh - var(--mast-h));
    overflow-y:auto;display:none;box-shadow:var(--shadow-lift);
  }
  .topnav.open{display:block}
  .tn-l0{display:block}
  .tn-l0>li>a{padding:.5rem 0;font-size:1rem}
  .caret{display:none}
  .tn-panel{
    position:static;opacity:1;visibility:visible;transform:none;
    min-width:0;padding:0 0 .5rem .85rem;margin:0;
    background:none;border:0;border-left:1px solid var(--rule);
    border-radius:0;box-shadow:none;
  }
  .tn-panel::before{display:none}
  .tn-panel a{padding:.34rem .4rem}
}

@media (max-width:900px){
  .shell{padding-inline:1rem}
  main{padding-top:1.5rem}

  /* Stretched to a quarter of its natural width the drops flatten into tick
     marks. On a narrow window the drip runs at 1:1 instead and the far end is
     cropped by .drip-wrap — a phone only ever sees the first two drops, which
     is the point of them. */
  .drip{width:1440px}

  /* Heading anchors sit in the left gutter and appear on hover, which is not a
     gesture a phone has. All they do here is hang past the edge of the page. */
  .anchor{display:none}
}

/* The masthead is a single row that cannot wrap, and at phone widths the brand,
   the tools and the drawer button together want more than the window has — the
   button was hanging 6px off the right edge of every page on the site. Tighten
   the gaps and let the burger stand for the word. */
@media (max-width:560px){
  :root{--mast-h:4rem}
  .masthead{gap:.55rem;padding-inline:.9rem}
  .brand{gap:.45rem;min-width:0}
  .brand-name{font-size:1rem;white-space:nowrap}
  .head-tools{padding-left:.5rem;gap:.3rem}
  .icon-btn{width:2.1rem;height:2.1rem}
  .theme-toggle{padding:0 .5rem;font-size:.62rem}
  .nav-toggle{flex:none;padding:.5rem .6rem;font-size:0;gap:0}
  .nav-toggle span{width:.95rem}
}

@media (max-width:600px){
  .status-row{grid-template-columns:auto 1fr auto;row-gap:.2rem}
  .st-detail{grid-column:2/-1;text-align:left}

  /* Side by side at this width a server name gets three words to a line. The
     rack stacks and goes back to being ruled top to bottom — and stacked, the
     rows line up on their glyphs rather than centring one under another. */
  .rack .status[data-cols]{grid-template-columns:minmax(0,1fr)}
  .rack .status[data-cols] .status-row{
    grid-template-columns:auto 1fr;justify-content:stretch;
    border-right:0;border-bottom:1px solid var(--rule);
  }
  .rack .status[data-cols] .status-row:last-child{border-bottom:0}

  /* The year rail costs more than it is worth on a phone. Put the year above
     its entries and pull the spine to the left edge. */
  .tl-key{padding-left:0;gap:.3rem .9rem}
  .timeline{padding-left:1.15rem}
  .timeline::before{left:0}
  .tl-year{margin-bottom:1.5rem}
  .tl-year::before{left:-1.45rem}
  .tl-y{position:static;width:auto;text-align:left;margin-bottom:.5rem}

  /* Side by side, the campaign name loses so much width to the system and the
     state pill that a two-word title wraps. Stack them instead. */
  .camp>a,.camp>div{grid-template-columns:minmax(0,1fr)}
  .camp-meta{justify-self:start;margin-top:.3rem}
}

/* ------------------------------------------------------------- motion ---- */
@media (prefers-reduced-motion:no-preference){
  html{scroll-behavior:smooth}
  /* Transform only, never opacity. A stalled animation — background tab, a
     paused compositor — would hold the from-state, and fading in from 0 would
     leave the page blank. Costing 8px of offset instead is harmless. */
  main>article{animation:rise .4s cubic-bezier(.2,.7,.3,1)}
  @keyframes rise{from{transform:translateY(8px)}to{transform:none}}
}
@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{animation-duration:.01ms!important;transition-duration:.01ms!important}
  html{scroll-behavior:auto}
}
