
/* ══════════════════════════════════════════════════════════════════════════
   NOCTURNE - the design system this site is drawn in.

   Tokens first, in one block, and every rule after this one takes its colour,
   type, spacing, radius and shadow from them. Retune the look HERE; nothing
   downstream is allowed a hex of its own.

   Nocturne is a DARK system, so the stylesheet no longer forks on
   `prefers-color-scheme`. The old light theme is gone rather than kept as a
   second skin: two themes is two designs to keep honest, and only one of them
   was ever designed. Rules that used to be dark-mode-only (the white plate
   under a transparent logo) are now simply rules.
   ═══════════════════════════════════════════════════════════════════════ */

/* Inter, self-hosted and subset to Latin. Deliberately NOT the design system's
   own `@import` from fonts.googleapis.com, for two reasons that both bite here:
   this site has to render opened straight off the Windows share with no
   internet, and - the stronger one - every page is already built so that
   READING it announces nothing to a third party. That is the entire point of
   the click-to-load facade in `EMBED_PROVIDERS`; a webfont fetched per page
   view from fonts.gstatic.com would undo it on every page at once, for a
   typeface. The files are committed under `scripts/site_fonts/`, so a build on
   a machine without Inter installed still produces the designed page.

   The `url()`s resolve against THIS FILE, not against the page, so one relative
   path serves every depth the site nests pages at.

   Japanese falls straight through the stack to the system's own CJK face: Inter
   has never carried those glyphs, and a catalogue of Japanese live discs asks
   for them on most pages. That is why the stack keeps a CJK name in it - losing
   it would leave half this catalogue to the browser's last-resort font. */
@font-face{font-family:Inter;font-style:normal;font-weight:400;font-display:swap;
  src:url(fonts/inter-400.woff2) format("woff2")}
@font-face{font-family:Inter;font-style:italic;font-weight:400;font-display:swap;
  src:url(fonts/inter-400i.woff2) format("woff2")}
@font-face{font-family:Inter;font-style:normal;font-weight:500;font-display:swap;
  src:url(fonts/inter-500.woff2) format("woff2")}
@font-face{font-family:Inter;font-style:normal;font-weight:600;font-display:swap;
  src:url(fonts/inter-600.woff2) format("woff2")}
@font-face{font-family:Inter;font-style:normal;font-weight:700;font-display:swap;
  src:url(fonts/inter-700.woff2) format("woff2")}
/* Newsreader (Production Type, SIL OFL 1.1), the FRONT PAGE's face and nobody
   else's: the owner left the landing's type to me and judged it (2026-10-09),
   and a newspaper serif under a blackletter nameplate is the oldest masthead
   pairing in print, so the front door reads as the front page of an archive.
   The catalogue pages keep Inter. Two VARIABLE files (weight 200-800 and the
   `opsz` axis, which the browser drives from the font size by itself) rather
   than four static cuts, so the 34px tagline and the 12px pin labels each get
   the optical size drawn for them. Self-hosted for Inter's reasons above. */
@font-face{font-family:Newsreader;font-style:normal;font-weight:200 800;font-display:swap;
  src:url(fonts/newsreader-var.woff2) format("woff2")}
@font-face{font-family:Newsreader;font-style:italic;font-weight:200 800;font-display:swap;
  src:url(fonts/newsreader-italic-var.woff2) format("woff2")}

:root{
  --color-bg:#161826;
  --color-surface:#232532;
  --color-text:#e9e9ed;
  --color-accent:#9184d9;
  --color-accent-2:#a7a1db;
  --color-divider:color-mix(in srgb, #e9e9ed 16%, transparent);

  /* Tonal ramps - generated in OKLCH on one shared lightness scale, so the same
     step of any role carries the same visual weight. On this dark ground the
     deep steps (700-900) are the tinted fills, 500 is the role's base, and the
     light steps (100-300) are text sitting on those fills. */
  --color-neutral-100:#f3f5fe; --color-neutral-200:#e4e7f5;
  --color-neutral-300:#cfd3e5; --color-neutral-400:#b2b6ca;
  --color-neutral-500:#9397ab; --color-neutral-600:#75798c;
  --color-neutral-700:#595d6c; --color-neutral-800:#3f424d;
  --color-neutral-900:#292b31;

  --color-accent-100:#f5f4ff; --color-accent-200:#e7e5fe;
  --color-accent-300:#d2cefd; --color-accent-400:#b5abfc;
  --color-accent-500:#968ae0; --color-accent-600:#796cbf;
  --color-accent-700:#5d5294; --color-accent-800:#423a6a;
  --color-accent-900:#2b2741;

  --font-heading:Inter,system-ui,"Noto Sans JP","Hiragino Kaku Gothic ProN",
    "Yu Gothic",Meiryo,sans-serif;
  --font-heading-weight:500;
  --font-body:Inter,system-ui,"Noto Sans JP","Hiragino Kaku Gothic ProN",
    "Yu Gothic",Meiryo,sans-serif;
  --font-mono:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
  /* The landing's serif, falling through to the SAME CJK stack as Inter: a
     Japanese title in a pin label reaches Noto Sans JP exactly as it does on
     every other page. */
  --font-landing:Newsreader,system-ui,"Noto Sans JP","Hiragino Kaku Gothic ProN",
    "Yu Gothic",Meiryo,sans-serif;

  /* Density 0.70x - this system is compact on purpose. */
  --space-1:2.8px; --space-2:5.6px; --space-3:8.4px;
  --space-4:11.2px; --space-6:16.8px; --space-8:22.4px;

  /* The system's ONE saturated field - a deep indigo used as presence at
     section scale (the deck's dividers, the landing's stat band), never as an
     interface colour. Everywhere else the grounds stay desaturated. */
  --color-section:#262a60;
  --color-section-glow:#353b80;
  --color-section-ghost:#4c5397;

  /* The reading measure. Nocturne is left-aligned and asymmetric - copy hugs
     the left edge and the air is left on the right - and this held prose to
     58ch rather than running it to the column.

     `none` since 2026-10-01, at the owner's call, with the column itself going
     full width: a fandom wiki runs its page across the whole screen, and a
     58ch paragraph inside a column that wide would only have moved the empty
     space he was asking to lose from the sides of the page into the middle of
     it. One token still; give it a length again to bring a measure back. */
  --measure:none;

  --radius-sm:4px; --radius-md:8px; --radius-lg:14px;

  /* Elevation on a dark ground is a hairline EDGE plus ambient darkness, never
     a stack of soft drops - which is why the cards below carry no border of
     their own any more: the shadow token already draws it. */
  --shadow-sm:0 0 0 1px #3f424d;
  --shadow-md:0 0 0 1px #595d6c,0 6px 18px rgba(0,0,0,.55);
  --shadow-lg:0 0 0 1px #9397ab,0 16px 40px rgba(0,0,0,.65);

  /* ── The names the rest of this stylesheet already speaks ────────────────
     Mapped onto the tokens above rather than replaced throughout: every layout
     rule in this file was argued for and measured against a real page, and a
     rename would have meant touching all of them to change none of them.
     `--card` and `--mono` are NEW - `EDIT_CSS` has always asked for both and
     nothing ever defined them, so the editor's panels were drawing on an
     unset variable and its textareas were falling back to a bare `monospace`. */
  --bg:var(--color-bg);
  --panel:var(--color-surface);
  --card:var(--color-surface);
  --ink:var(--color-text);
  --muted:color-mix(in srgb, var(--color-text) 55%, transparent);
  --line:var(--color-divider);
  --accent:var(--color-accent);
  --accent-soft:var(--color-accent-900);
  --edge:var(--color-neutral-700);
  --mono:var(--font-mono);
  --shadow:var(--shadow-sm);

  /* The graph's categorical colours. Regenerated in OKLCH onto ONE lightness
     (0.78) at a chroma below the accent's own 0.125, so the accent still leads
     any page they appear on and no kind shouts louder than another purely by
     being yellow. The HUES are the ones the catalogue has always taught -
     release lavender, band amber, person teal, venue blue, company rose - so a
     reader who has learned the legend has not had it moved. */
  --k-release:#b8aded; --k-band:#ddad79; --k-person:#7fc9ad;
  --k-venue:#8cbcee; --k-company:#eaa1a0;
  /* A name with no page is RED - the wiki red link - at the owner's call
     (2026-10-01); it was a grey #686870. oklch(0.68 0.19 27): a true red at
     twice the company rose's chroma, so the two never read as one colour, and
     at the lavender link's own lightness, so a page-less name carries the same
     weight as a link that works. 6.4:1 on the plate, 4.8:1 on a card. */
  --k-dead:#f75e54;
  /* The concerts graph's ground is a record on a turntable (owner, 2026-10-04):
     the vinyl and the platter under it. Near-black on purpose - every node
     colour above reads best on black - and tokens so the canvas reads them the
     way it reads everything else. The label takes `--accent-soft`. */
  --vinyl:#0a0b0f; --platter:#14151b;

  /* The plate under the content column. The ground draws behind the WHOLE
     document, so on a text page the backline runs straight under the words;
     this darkens it there and nowhere else. One knob.

     THE VALUE IS HIGH ON PURPOSE, and a low one does nothing - which is not
     obvious. Black at alpha scales the line AND the ground beneath it by the
     same (1-alpha), so the Weber contrast between them is UNCHANGED: .42 made
     the whole column dimmer and left the drawing exactly as legible through the
     text. Measured on the venues table at .60/.75/.88, only the last actually
     put the kit behind the words. What does the work is absolute luminance -
     near the floor the eye stops resolving the step at all.

     .85 -> .76 at the owner's call: he wanted the ground to read through a
     little. That is a TASTE dial and this is the range it works in - .76 keeps
     the drawing as faint texture, .68 has it competing with a table again, and
     .60 may as well not be here. The test guards the floor, not the taste. */
  --plate:rgba(0,0,0,.76);
}

/* Interaction states are THEMED, never the browser's. */
:focus{outline:none}
:focus-visible{outline:2px solid var(--color-accent);outline-offset:2px}
::selection{background:color-mix(in srgb, var(--color-accent) 30%, transparent)}

/* The image wrapper the system specifies for photographs. It is deliberately
   NOT applied to anything in the catalogue itself: `mix-blend-mode:lighten`
   drops everything darker than the page out of a picture, which is the effect
   Nocturne wants from a portrait shot on black - and is destructive on a disc
   sleeve, whose dark half would simply disappear. A catalogue whose whole job
   is showing the object cannot blend the object into the page. It stays
   available for a decorative photograph that was shot for it. */
.lighten{mix-blend-mode:lighten;background-color:transparent}
.text-muted{color:var(--muted)}

/* Every colour, face, radius and shadow this file spends is defined once in the
   NOCTURNE block above - including `--bg`/`--ink`/`--k-*` and the other names
   the rules below have always used. There is no light/dark fork any more. */
*,*::before,*::after{box-sizing:border-box}
body{margin:0;color:var(--ink);
  background:
    radial-gradient(1200px 720px at 82% -160px,
      color-mix(in srgb, var(--color-accent-900) 75%, transparent), transparent 60%),
    radial-gradient(1100px 800px at -10% 100%,
      color-mix(in srgb, black 30%, transparent), transparent 55%),
    var(--bg);
  background-repeat:no-repeat;
  font-family:var(--font-body);font-size:15px;line-height:1.55;font-weight:400;
  -webkit-font-smoothing:antialiased;
  /* better breaks, no orphan left on a line of its own */
  text-wrap:pretty}

/* - THE GROUND -
   A backline: head and 4x12, guitars on their stands, wedges, kit, keys and a
   second stack, on a floor line that fades out at both ends. The three amps
   show a plain front and no speaker cones (owner, 2026-10-01): on a real cab
   the cones sit behind the grille cloth, and drawn they read as portholes. Fixed to the foot
   of the viewport and masked away toward the top, so it reads as the room the
   catalogue is about rather than as an illustration sitting on a page. It
   scrolls with nothing and catches no clicks.

   IT RIDES ONE DATA URI IN THIS STYLESHEET, not markup in every page. As
   markup it is 4.7KB x ~285 pages = 1.3MB of identical bytes, all of which
   would also have to ride every public publish. The cost of the URI is that a
   custom property cannot resolve inside it - a data URI is its own document -
   so the single colour it spends is inlined as `--color-neutral-800`'s value.
   There is no light fork for that to fall out of step with; if the token is
   ever retuned, retune it here in the same change.

   `z-index:-1` is what lets it sit under the whole document without any other
   element opting into a stacking context. The alternative - the mockup's -
   is wrapping every page's content in a positioned div, which is markup on
   every page again, and a `z-index:0` layer would paint OVER unpositioned
   text rather than behind it. */
body::before{content:"";position:fixed;left:0;right:0;bottom:0;
  height:min(42vh,380px);pointer-events:none;z-index:-1;
  background:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1400 360' preserveAspectRatio='xMidYMax meet'%3E%3Cdefs%3E%3ClinearGradient id='bl-floor' x1='0' x2='1' y1='0' y2='0'%3E%3Cstop offset='0' stop-color='%233f424d' stop-opacity='0'%3E%3C/stop%3E%3Cstop offset='0.12' stop-color='%233f424d'%3E%3C/stop%3E%3Cstop offset='0.88' stop-color='%233f424d'%3E%3C/stop%3E%3Cstop offset='1' stop-color='%233f424d' stop-opacity='0'%3E%3C/stop%3E%3C/linearGradient%3E%3C/defs%3E%3Cg stroke='%233f424d' stroke-width='1.3' opacity='0.75' fill='none' stroke-linejoin='round' stroke-linecap='round'%3E%3Cline x1='20' y1='320' x2='1380' y2='320' stroke='url(%23bl-floor)'%3E%3C/line%3E%3Crect x='130' y='152' width='200' height='58' rx='3'%3E%3C/rect%3E%3Ccircle cx='165' cy='181' r='4'%3E%3C/circle%3E%3Ccircle cx='190' cy='181' r='4'%3E%3C/circle%3E%3Ccircle cx='215' cy='181' r='4'%3E%3C/circle%3E%3Ccircle cx='240' cy='181' r='4'%3E%3C/circle%3E%3Ccircle cx='265' cy='181' r='4'%3E%3C/circle%3E%3Ccircle cx='290' cy='181' r='4'%3E%3C/circle%3E%3Crect x='130' y='215' width='200' height='105' rx='3'%3E%3C/rect%3E%3Cpath d='M148 181 C 95 200 88 300 122 318 S 200 334 250 320'%3E%3C/path%3E%3Cpolygon points='250,320 330,320 318,290 268,290'%3E%3C/polygon%3E%3Ccircle cx='293' cy='305' r='8'%3E%3C/circle%3E%3Cline x1='430' y1='320' x2='430' y2='150'%3E%3C/line%3E%3Cpath d='M410 320 430 296 450 320'%3E%3C/path%3E%3Cline x1='430' y1='150' x2='500' y2='112'%3E%3C/line%3E%3Ccircle cx='506' cy='109' r='7'%3E%3C/circle%3E%3Cpolygon points='470,320 550,320 538,290 488,290'%3E%3C/polygon%3E%3Ccircle cx='513' cy='305' r='8'%3E%3C/circle%3E%3Cellipse cx='568' cy='168' rx='32' ry='4'%3E%3C/ellipse%3E%3Cellipse cx='568' cy='178' rx='32' ry='4'%3E%3C/ellipse%3E%3Cline x1='568' y1='182' x2='568' y2='320'%3E%3C/line%3E%3Cpath d='M550 320 568 298 586 320'%3E%3C/path%3E%3Crect x='585' y='232' width='66' height='36' rx='3'%3E%3C/rect%3E%3Cline x1='618' y1='268' x2='618' y2='320'%3E%3C/line%3E%3Cpath d='M600 320 618 298 636 320'%3E%3C/path%3E%3Crect x='655' y='185' width='58' height='48' rx='3'%3E%3C/rect%3E%3Crect x='722' y='185' width='58' height='48' rx='3'%3E%3C/rect%3E%3Ccircle cx='715' cy='268' r='52'%3E%3C/circle%3E%3Ccircle cx='715' cy='268' r='45'%3E%3C/circle%3E%3Ccircle cx='736' cy='286' r='7'%3E%3C/circle%3E%3Crect x='782' y='236' width='66' height='84' rx='3'%3E%3C/rect%3E%3Cellipse cx='800' cy='120' rx='40' ry='4'%3E%3C/ellipse%3E%3Cline x1='800' y1='124' x2='800' y2='236'%3E%3C/line%3E%3Cellipse cx='870' cy='150' rx='44' ry='4'%3E%3C/ellipse%3E%3Cline x1='870' y1='154' x2='870' y2='320'%3E%3C/line%3E%3Cpath d='M852 320 870 298 888 320'%3E%3C/path%3E%3Crect x='930' y='236' width='130' height='84' rx='3'%3E%3C/rect%3E%3Cline x1='930' y1='252' x2='1060' y2='252'%3E%3C/line%3E%3Ccircle cx='950' cy='244' r='3'%3E%3C/circle%3E%3Ccircle cx='968' cy='244' r='3'%3E%3C/circle%3E%3Ccircle cx='986' cy='244' r='3'%3E%3C/circle%3E%3Ccircle cx='1004' cy='244' r='3'%3E%3C/circle%3E%3Ccircle cx='1022' cy='244' r='3'%3E%3C/circle%3E%3Ccircle cx='1040' cy='244' r='3'%3E%3C/circle%3E%3Crect x='1110' y='118' width='190' height='50' rx='3'%3E%3C/rect%3E%3Ccircle cx='1135' cy='143' r='4'%3E%3C/circle%3E%3Ccircle cx='1155' cy='143' r='4'%3E%3C/circle%3E%3Ccircle cx='1175' cy='143' r='4'%3E%3C/circle%3E%3Ccircle cx='1195' cy='143' r='4'%3E%3C/circle%3E%3Ccircle cx='1215' cy='143' r='4'%3E%3C/circle%3E%3Ccircle cx='1235' cy='143' r='4'%3E%3C/circle%3E%3Ccircle cx='1255' cy='143' r='4'%3E%3C/circle%3E%3Ccircle cx='1275' cy='143' r='4'%3E%3C/circle%3E%3Crect x='1110' y='172' width='190' height='148' rx='3'%3E%3C/rect%3E%3C/g%3E%3C/svg%3E") center bottom/100% 100% no-repeat;
  -webkit-mask-image:linear-gradient(to top,#000 45%,transparent);
  mask-image:linear-gradient(to top,#000 45%,transparent)}
/* The graph owns the viewport and paints its own ground; a stage drawing
   behind a node map is noise, not atmosphere. */
body.fullpage::before{display:none}
a{color:var(--accent);text-decoration:none;text-underline-offset:3px}
a:hover{text-decoration:underline}
/* A link INSIDE running text is underlined, faintly, as well as coloured (WCAG 1.4.1,
   the 2026-10-08 accessibility pass): the accent against body text is 2.7:1 and against
   muted text 1.1:1, so colour alone did not mark it. Headers, menus, tiles, buttons and
   the on-this-page rail stay as they were; a hover makes the line solid. The whole rule
   has the specificity of a bare `a` (`:where`), so any rule that styles a link row of its
   own (the forum's lists, in ACCOUNT_CSS) simply wins. */
:where(main p,main li,main dd,main figcaption,.lead,footer) a:where(:not(.btn,.tile,.chip,.lb,nav *,.actions *,.catbar *,.pager *)){
  text-decoration:underline;text-decoration-thickness:1px;text-underline-offset:.2em;
  text-decoration-color:color-mix(in srgb, currentColor 40%, transparent)}
:where(main p,main li,main dd,main figcaption,.lead,footer) a:where(:not(.btn,.tile,.chip,.lb,nav *,.actions *,.catbar *,.pager *)):hover{
  text-decoration-color:currentColor}
/* Skip to the page's content, first in the tab order and shown only when focused. */
.skip{position:absolute;left:12px;top:-80px;z-index:200;padding:8px 14px;border-radius:var(--radius-md);
  background:var(--panel);color:var(--ink);border:2px solid var(--accent)}
.skip:focus{top:10px}
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;
  clip:rect(0 0 0 0);white-space:nowrap;border:0}
.tablewrap:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
/* Nothing animates for a reader who has asked for less motion (the graph and the
   lightbox already honour it in their own code). */
@media (prefers-reduced-motion:reduce){*,*::before,*::after{transition-duration:.01ms!important;
  animation-duration:.01ms!important;animation-iteration-count:1!important;scroll-behavior:auto!important}}
code{font:13px/1.4 var(--font-mono);background:var(--accent-soft);
  color:var(--color-accent-200);padding:1px 5px;border-radius:var(--radius-sm)}
.topbar{position:sticky;top:0;z-index:20;display:flex;gap:18px;align-items:center;flex-wrap:wrap;
  padding:13px 22px;background:var(--panel);border-bottom:0}
/* IN-PAGE LINKS LAND BELOW THE BAR. The bar is sticky, so a `#section` jump
   used to park the heading it was aiming at UNDERNEATH it (owner,
   2026-10-01). `scroll-padding-top` on the root is the one rule every such
   jump honours - the rail's links, a hash arriving from another page, and
   `scrollIntoView`. The height is the bar's own, MEASURED by `BAR_JS`,
   because it wraps: measured 2026-10-04, once the ornament under the mark
   made it 18px taller, 80px on one row, 122px on two, 103px on a phone down
   to 320, and more where the private copy's Edit and Refresh buttons push it
   to a third. These per-width values are only the fallback for a page with
   no script. */
:root{--bar-h:80px}
html{scroll-padding-top:calc(var(--bar-h) + 14px)}
@media (max-width:1240px){:root{--bar-h:122px}}
@media (max-width:640px){:root{--bar-h:103px}}
.ico{width:15px;height:15px;flex:none;display:block}
.searchwrap{position:relative;display:inline-flex;align-items:center}
.searchwrap .ico{position:absolute;left:11px;color:var(--muted);pointer-events:none}
.brand{font-family:var(--font-heading);font-weight:var(--font-heading-weight);
  font-size:18px;letter-spacing:-.015em;color:var(--ink)}
/* The brand is the drawn mark now, so `.brand` only has to be a flex row that
   keeps the artwork and any tagline on one baseline. The type rules above still
   stand because `.brand span` - the tagline - inherits them. */
.brand{display:inline-flex;align-items:center;flex:none}
/* The WORD is 34px - taller than the old Backline mark's 26px, because
   blackletter's x-height is under half its full height (the M's swash and the
   g's tail), so at 26px the lowercase was 11px. The mark is 54px because the
   ornament hangs under the word (`LOGO_VIEWBOX` is 1438 units tall to the
   word's 905): grow this and the banner's height by that ratio together, or
   the word changes size. */
.brand .logomark{height:54px}
.logomark{width:auto;display:block;overflow:visible}
.brand span{color:var(--muted);font-weight:400;margin-left:9px;font-size:10.5px;
  text-transform:uppercase;letter-spacing:.11em}
.topbar nav{display:flex;gap:14px;font-size:14px;align-items:center}
/* Two navs, one row on a wide screen. `browse` sits where the mockup draws it -
   straight after the brand - and `tools` is pushed to the far end by the auto
   margin rather than by an order rule, so the wide layout needs no reordering
   at all and is simply the document.
   
   THREE ROWS ARE MADE IMPOSSIBLE HERE, not by the breakpoint below. Measured,
   the bar wants 1170px to sit on one line; a breakpoint alone would mean any
   platform whose Inter metrics differ by a few percent silently wraps into a
   third row on some width nobody tested. So `browse` is the item that gives:
   `min-width:0` lets it shrink below its content and `overflow-x:auto` lets
   that content slide instead of pushing a sibling onto a new line, while
   `tools` never shrinks. The wrap can then only ever happen where the query
   below asks for it. */
.topbar .browse{flex:0 1 auto;min-width:0;overflow-x:auto;flex-wrap:nowrap;
  padding-bottom:2px;scrollbar-width:thin}
/* Or the links compress into each other rather than overflowing, and the
   scroll never arrives. */
.topbar .browse a{flex:none}
.topbar .browse a:focus-visible{outline-offset:-2px}
.topbar .tools{flex:none;margin-left:auto}
.topbar nav a{color:var(--ink);display:inline-flex;align-items:center;gap:6px}
/* The glyph is quieter than its label: at full ink a 15px filled Phosphor mark
   out-weighs the 14px word beside it and the bar reads as a row of icons with
   captions. It comes UP to the label on hover and on the current page, which is
   also the only way the accent state reads as one thing rather than a coloured
   word beside an ink icon. */
.topbar nav a .ico{opacity:.62}
.topbar nav a:hover .ico,.topbar nav a.on .ico{opacity:1}
.topbar nav a:hover{color:var(--accent);text-decoration:none}
.topbar nav a.on{color:var(--accent);font-weight:var(--font-heading-weight)}
/* The design system's button, taken as-is. Outlined on nothing - the primary
   action is an accent LINE, never a fill. It sits on an <a> here because the
   thing it does is navigate, and `.btn` was written to work on one. */
.btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;
  cursor:pointer;text-decoration:none;font-family:var(--font-heading);
  font-weight:var(--font-heading-weight);font-size:14px;line-height:1.2;
  color:var(--ink);background:transparent;border:1px solid transparent;
  padding:var(--space-2) calc(var(--space-3) * 1.2);border-radius:var(--radius-md);
  white-space:nowrap}
.btn-primary{color:var(--accent);border-color:var(--accent)}
.btn-primary:hover{background:color-mix(in srgb, var(--accent) 12%, transparent)}
.btn-primary:active{background:color-mix(in srgb, var(--accent) 22%, transparent)}
.btn-ghost{color:var(--accent);padding-inline:var(--space-1)}
.btn-ghost:hover{background:color-mix(in srgb, var(--accent) 10%, transparent)}
/* `.topbar nav a` is two classes deep and would otherwise out-specify
   `.btn-primary` and paint the button's own label back to ink.
   
   The bar's one action is WHITE on an accent outline, not accent-on-accent:
   it is the only control up there that does something rather than going
   somewhere, and the brighter label is what separates it from the five browse
   links beside it. The icon comes with it for free - every Phosphor glyph is
   inlined `fill="currentColor"`, so the label's colour is the icon's colour and
   the two cannot drift apart.
   
   `--color-neutral-100` and not a `#fff` of its own: the ramp's lightest step is
   what this system calls white, and the NOCTURNE block's rule is that nothing
   downstream gets a hex. Scoped to `.topbar` deliberately - `.btn-primary` is
   also the editor panel's save button, which is not this and should not move. */
.topbar nav a.btn-primary{color:var(--color-neutral-100)}
.topbar nav a.btn-primary:hover{color:var(--color-neutral-100);text-decoration:none}
/* Already there: the outline is the accent either way, so being ON that page
   is said with the pressed tint rather than by colouring what is coloured. */
.topbar nav a.btn-primary.on{background:color-mix(in srgb, var(--accent) 18%, transparent)}
/* Hidden until the JS finds a server to talk to - see `REFRESH_JS`. `hidden`
   alone loses to `display:flex` on the bar, hence the explicit rule. */
#refreshsite[hidden],#editsite[hidden]{display:none}
/* Outlined, never filled - the system's primary action is an accent line on
   nothing. The hover and pressed states are tints of the same accent rather
   than a second colour. */
#refreshsite{font:inherit;font-size:13.5px;padding:5px 11px;border:1px solid var(--accent);
  border-radius:var(--radius-md);background:transparent;color:var(--accent);
  cursor:pointer;white-space:nowrap}
#refreshsite:hover{background:color-mix(in srgb, var(--accent) 12%, transparent)}
#refreshsite:active{background:color-mix(in srgb, var(--accent) 22%, transparent)}
#refreshsite:disabled{color:var(--muted);cursor:default;border-color:var(--line);background:transparent}
/* `font:14px inherit` was an INVALID shorthand - `inherit` is not a family name,
   so the whole declaration was dropped and this box has been rendering in the
   browser's own UI font since the day it was written. Naming the face fixes it. */
.topbar input{font:14px/1.4 var(--font-body);padding:7px 11px 7px 32px;
  border:1px solid var(--color-neutral-600);border-radius:var(--radius-md);background:var(--bg);
  color:var(--ink);width:250px}
.topbar input::placeholder{color:var(--muted)}
/* - the browse menus -
   `position:fixed` so the strip's `overflow-x:auto` cannot clip them, placed
   from the trigger's rect by NAV_JS. Hidden until that script says otherwise,
   so a page with no script has no empty box in it. */
.navmenu{position:fixed;z-index:60;display:none;
  background:var(--panel);border-radius:var(--radius-md);padding:8px;
  box-shadow:var(--shadow-md);overflow-y:auto;overscroll-behavior:contain;
  max-width:min(92vw,680px)}
.navmenu.on{display:block}
.navmenu-rule{height:1px;margin:0 8px 8px;
  background:linear-gradient(to right,var(--kc),var(--color-neutral-900) 45%,transparent)}
.navmenu-list{display:grid;gap:1px 6px}
.navmenu[data-cols="2"] .navmenu-list{grid-template-columns:repeat(2,minmax(0,1fr))}
.navmenu[data-cols="3"] .navmenu-list{grid-template-columns:repeat(3,minmax(0,1fr))}
.navmenu a{display:flex;align-items:center;gap:8px;padding:5px 8px;
  border-radius:6px;color:var(--ink);font-size:13.5px;white-space:nowrap}
.navmenu a:hover{background:var(--color-neutral-900);color:var(--accent);
  text-decoration:none}
.navmenu a i{width:8px;height:8px;border-radius:50%;display:inline-block;flex:none}
/* `minmax(0,1fr)` lets the column be narrower than its content, so without this
   a long name simply overruns into the next one. */
.navmenu-name{min-width:0;overflow:hidden;text-overflow:ellipsis}
.navmenu-type{margin-left:auto;padding-left:14px;font-size:12px;color:var(--muted)}
/* THE PLATE FADES OUT at both ends over 48px - the treatment every freestanding
   rule on this site already uses - so the column reads as a lit surface rather
   than as a slab with two hard vertical edges cutting through the drawing.
   `footer` is a SIBLING of `.wrap`, not a child, so it carries the same plate or
   the column ends in a seam directly above the last paragraph.

   FULL WIDTH since 2026-10-01 (the owner, after a fandom wiki): the column used
   to stop at 1360px and leave the ground bare either side of it - 280px a side
   at 1920, 600 at 2560. It runs edge to edge now and the plate with it, so the
   48px fade lands on the screen's own edges and reads as a soft frame. The
   gutter grows with the screen so that, from about 1600px up, the first word
   of every line sits on the full plate rather than inside that fade; on a
   phone it is the 22px it always was.

   THE SCATTER rides the plate as its top layer - see `_SCATTER` for the
   instruments and why each is tilted. Plated surfaces only, deliberately: the
   home page's masthead keeps the open ground, the footer stays a quiet strip
   under its hairline, and the graph page has no `.wrap` at all. It scrolls
   WITH the page, never `fixed`: a fixed background forces a repaint of the
   whole column on every frame of scrolling, and the backline below it is
   already the part of the room that stays put. */
:root{--gutter:clamp(22px,3vw,64px)}
.wrap{padding:26px var(--gutter) 60px}
.wrap.plated,.platedblock{
  background:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='720' height='720' viewBox='0 0 720 720'%3E%3Cg opacity='0.09'%3E%3Cg transform='translate(79 31) rotate(33) scale(-2.41667 2.41667) translate(-12 -12)' fill='none' stroke='%23cfd3e5' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m2 2 8 8'/%3E%3Cpath d='m22 2-8 8'/%3E%3Cellipse cx='12' cy='9' rx='10' ry='5'/%3E%3Cpath d='M7 13.4v7.9'/%3E%3Cpath d='M12 14v8'/%3E%3Cpath d='M17 13.4v7.9'/%3E%3Cpath d='M2 9v8a10 5 0 0 0 20 0V9'/%3E%3C/g%3E%3Cg transform='translate(79 751) rotate(33) scale(-2.41667 2.41667) translate(-12 -12)' fill='none' stroke='%23cfd3e5' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m2 2 8 8'/%3E%3Cpath d='m22 2-8 8'/%3E%3Cellipse cx='12' cy='9' rx='10' ry='5'/%3E%3Cpath d='M7 13.4v7.9'/%3E%3Cpath d='M12 14v8'/%3E%3Cpath d='M17 13.4v7.9'/%3E%3Cpath d='M2 9v8a10 5 0 0 0 20 0V9'/%3E%3C/g%3E%3Cg transform='translate(451 47) rotate(25) scale(0.210938 0.210938) translate(-128 -128)' fill='%23cfd3e5'%3E%3Cpath d='M212.92,17.69a8,8,0,0,0-6.86-1.45l-128,32A8,8,0,0,0,72,56V166.08A36,36,0,1,0,88,196V110.25l112-28v51.83A36,36,0,1,0,216,164V24A8,8,0,0,0,212.92,17.69ZM52,216a20,20,0,1,1,20-20A20,20,0,0,1,52,216ZM88,93.75V62.25l112-28v31.5ZM180,184a20,20,0,1,1,20-20A20,20,0,0,1,180,184Z'/%3E%3C/g%3E%3Cg transform='translate(451 767) rotate(25) scale(0.210938 0.210938) translate(-128 -128)' fill='%23cfd3e5'%3E%3Cpath d='M212.92,17.69a8,8,0,0,0-6.86-1.45l-128,32A8,8,0,0,0,72,56V166.08A36,36,0,1,0,88,196V110.25l112-28v51.83A36,36,0,1,0,216,164V24A8,8,0,0,0,212.92,17.69ZM52,216a20,20,0,1,1,20-20A20,20,0,0,1,52,216ZM88,93.75V62.25l112-28v31.5ZM180,184a20,20,0,1,1,20-20A20,20,0,0,1,180,184Z'/%3E%3C/g%3E%3Cg transform='translate(633 70) rotate(21) scale(-2.25 2.25) translate(-12 -12)' fill='none' stroke='%23cfd3e5' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect width='20' height='16' x='2' y='4' rx='2'/%3E%3Cpath d='M6 8h4'/%3E%3Cpath d='M14 8h.01'/%3E%3Cpath d='M18 8h.01'/%3E%3Cpath d='M2 12h20'/%3E%3Cpath d='M6 12v4'/%3E%3Cpath d='M10 12v4'/%3E%3Cpath d='M14 12v4'/%3E%3Cpath d='M18 12v4'/%3E%3C/g%3E%3Cg transform='translate(187 169) rotate(-38) scale(2.58333 2.58333) translate(-12 -12)' fill='none' stroke='%23cfd3e5' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M16 12a4.16 4.16 0 0 1 -5.62 3.89a3.78 3.78 0 0 1 -2.38 -3.39a3.42 3.42 0 0 1 2.34 -3.38l3.79 -1.42a2.89 2.89 0 0 0 1.87 -2.7a2 2 0 1 0 -4 0v14a2 2 0 1 1 -4 0'/%3E%3C/g%3E%3Cg transform='translate(451 217) rotate(25) scale(-2.58333 2.58333) translate(-12 -12)' fill='none' stroke='%23cfd3e5' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M10 13v4'/%3E%3Cpath d='M14 13v4'/%3E%3Cpath d='M18 13v4'/%3E%3Cpath d='M2 13h20'/%3E%3Cpath d='M22 11.5A3.5 3.5 0 0018.5 8a3.52 3.52 0 01-3.173-2A7 7 0 002 9v10a2 2 0 002 2h16a2 2 0 002-2z'/%3E%3Cpath d='M6 13v4'/%3E%3C/g%3E%3Cg transform='translate(-3 339) rotate(80) scale(-0.210938 0.210938) translate(-128 -128)' fill='%23cfd3e5'%3E%3Cpath d='M249.66,46.34l-40-40a8,8,0,0,0-11.31,11.32L200.69,20,140.52,80.16C117.73,68.3,92.21,69.29,76.75,84.74a42.27,42.27,0,0,0-9.39,14.37A8.24,8.24,0,0,1,59.81,104c-14.59.49-27.26,5.72-36.65,15.11C11.08,131.22,6,148.6,8.74,168.07,11.4,186.7,21.07,205.15,36,220s33.34,24.56,52,27.22A71.13,71.13,0,0,0,98.1,248c15.32,0,28.83-5.23,38.76-15.16,9.39-9.39,14.62-22.06,15.11-36.65a8.24,8.24,0,0,1,4.92-7.55,42.12,42.12,0,0,0,14.37-9.39c15.45-15.46,16.44-41,4.58-63.77L236,55.31l2.34,2.34a8,8,0,1,0,11.32-11.31ZM160,167.93a26.12,26.12,0,0,1-8.95,5.83,24.24,24.24,0,0,0-15,21.89c-.36,10.46-4,19.41-10.43,25.88-8.44,8.43-21,11.95-35.36,9.89C75,229.25,59.73,221.19,47.27,208.73S26.75,181,24.58,165.81c-2-14.37,1.46-26.92,9.89-35.36C40.94,124,49.89,120.37,60.35,120h0a24.22,24.22,0,0,0,21.89-15,26.12,26.12,0,0,1,5.83-9c5.49-5.49,13-8.13,21.38-8.13a49.38,49.38,0,0,1,19.13,4.19L108.5,112.19a32,32,0,1,0,35.31,35.31l20.08-20.08C170.41,142.71,169.47,158.41,160,167.93Zm-10.4-61.48a72.9,72.9,0,0,1,5.93,6.75l-15.42,15.42a32.22,32.22,0,0,0-12.68-12.68l15.42-15.43A73,73,0,0,1,149.55,106.45ZM112,128a16,16,0,0,1,16,16h0a16,16,0,1,1-16-16Zm48.85-32.85a86.94,86.94,0,0,0-6.68-6L176,67.31,188.69,80l-21.83,21.82A86.94,86.94,0,0,0,160.86,95.14ZM200,68.68,187.32,56,212,31.31,224.69,44ZM93.66,194.33a8,8,0,0,1-11.31,11.32l-32-32a8,8,0,0,1,11.32-11.31Z'/%3E%3C/g%3E%3Cg transform='translate(717 339) rotate(80) scale(-0.210938 0.210938) translate(-128 -128)' fill='%23cfd3e5'%3E%3Cpath d='M249.66,46.34l-40-40a8,8,0,0,0-11.31,11.32L200.69,20,140.52,80.16C117.73,68.3,92.21,69.29,76.75,84.74a42.27,42.27,0,0,0-9.39,14.37A8.24,8.24,0,0,1,59.81,104c-14.59.49-27.26,5.72-36.65,15.11C11.08,131.22,6,148.6,8.74,168.07,11.4,186.7,21.07,205.15,36,220s33.34,24.56,52,27.22A71.13,71.13,0,0,0,98.1,248c15.32,0,28.83-5.23,38.76-15.16,9.39-9.39,14.62-22.06,15.11-36.65a8.24,8.24,0,0,1,4.92-7.55,42.12,42.12,0,0,0,14.37-9.39c15.45-15.46,16.44-41,4.58-63.77L236,55.31l2.34,2.34a8,8,0,1,0,11.32-11.31ZM160,167.93a26.12,26.12,0,0,1-8.95,5.83,24.24,24.24,0,0,0-15,21.89c-.36,10.46-4,19.41-10.43,25.88-8.44,8.43-21,11.95-35.36,9.89C75,229.25,59.73,221.19,47.27,208.73S26.75,181,24.58,165.81c-2-14.37,1.46-26.92,9.89-35.36C40.94,124,49.89,120.37,60.35,120h0a24.22,24.22,0,0,0,21.89-15,26.12,26.12,0,0,1,5.83-9c5.49-5.49,13-8.13,21.38-8.13a49.38,49.38,0,0,1,19.13,4.19L108.5,112.19a32,32,0,1,0,35.31,35.31l20.08-20.08C170.41,142.71,169.47,158.41,160,167.93Zm-10.4-61.48a72.9,72.9,0,0,1,5.93,6.75l-15.42,15.42a32.22,32.22,0,0,0-12.68-12.68l15.42-15.43A73,73,0,0,1,149.55,106.45ZM112,128a16,16,0,0,1,16,16h0a16,16,0,1,1-16-16Zm48.85-32.85a86.94,86.94,0,0,0-6.68-6L176,67.31,188.69,80l-21.83,21.82A86.94,86.94,0,0,0,160.86,95.14ZM200,68.68,187.32,56,212,31.31,224.69,44ZM93.66,194.33a8,8,0,0,1-11.31,11.32l-32-32a8,8,0,0,1,11.32-11.31Z'/%3E%3C/g%3E%3Cg transform='translate(331 374) rotate(-25) scale(0.210938 0.210938) translate(-128 -128)' fill='%23cfd3e5'%3E%3Cpath d='M212.92,17.69a8,8,0,0,0-6.86-1.45l-128,32A8,8,0,0,0,72,56V166.08A36,36,0,1,0,88,196V110.25l112-28v51.83A36,36,0,1,0,216,164V24A8,8,0,0,0,212.92,17.69ZM52,216a20,20,0,1,1,20-20A20,20,0,0,1,52,216ZM88,93.75V62.25l112-28v31.5ZM180,184a20,20,0,1,1,20-20A20,20,0,0,1,180,184Z'/%3E%3C/g%3E%3Cg transform='translate(171 392) rotate(22) scale(-0.242188 0.242188) translate(-128 -128)' fill='%23cfd3e5'%3E%3Cpath d='M168,16A72.07,72.07,0,0,0,96,88a73.29,73.29,0,0,0,.63,9.42L27.12,192.22A15.93,15.93,0,0,0,28.71,213L43,227.29a15.93,15.93,0,0,0,20.78,1.59l94.81-69.53A73.29,73.29,0,0,0,168,160a72,72,0,1,0,0-144Zm56,72a55.72,55.72,0,0,1-11.16,33.52L134.49,43.16A56,56,0,0,1,224,88ZM54.32,216,40,201.68,102.14,117A72.37,72.37,0,0,0,139,153.86ZM112,88a55.67,55.67,0,0,1,11.16-33.51l78.34,78.34A56,56,0,0,1,112,88Zm-2.35,58.34a8,8,0,0,1,0,11.31l-8,8a8,8,0,1,1-11.31-11.31l8-8A8,8,0,0,1,109.67,146.33Z'/%3E%3C/g%3E%3Cg transform='translate(546 426) rotate(-33) scale(-2.75 2.75) translate(-12 -12)' fill='none' stroke='%23cfd3e5' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect width='20' height='16' x='2' y='4' rx='2'/%3E%3Cpath d='M6 8h4'/%3E%3Cpath d='M14 8h.01'/%3E%3Cpath d='M18 8h.01'/%3E%3Cpath d='M2 12h20'/%3E%3Cpath d='M6 12v4'/%3E%3Cpath d='M10 12v4'/%3E%3Cpath d='M14 12v4'/%3E%3Cpath d='M18 12v4'/%3E%3C/g%3E%3Cg transform='translate(377 534) rotate(-23) scale(2.41667 2.41667) translate(-12 -12)' fill='none' stroke='%23cfd3e5' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m2 2 8 8'/%3E%3Cpath d='m22 2-8 8'/%3E%3Cellipse cx='12' cy='9' rx='10' ry='5'/%3E%3Cpath d='M7 13.4v7.9'/%3E%3Cpath d='M12 14v8'/%3E%3Cpath d='M17 13.4v7.9'/%3E%3Cpath d='M2 9v8a10 5 0 0 0 20 0V9'/%3E%3C/g%3E%3Cg transform='translate(212 554) rotate(81) scale(0.210938 0.210938) translate(-128 -128)' fill='%23cfd3e5'%3E%3Cpath d='M249.66,46.34l-40-40a8,8,0,0,0-11.31,11.32L200.69,20,140.52,80.16C117.73,68.3,92.21,69.29,76.75,84.74a42.27,42.27,0,0,0-9.39,14.37A8.24,8.24,0,0,1,59.81,104c-14.59.49-27.26,5.72-36.65,15.11C11.08,131.22,6,148.6,8.74,168.07,11.4,186.7,21.07,205.15,36,220s33.34,24.56,52,27.22A71.13,71.13,0,0,0,98.1,248c15.32,0,28.83-5.23,38.76-15.16,9.39-9.39,14.62-22.06,15.11-36.65a8.24,8.24,0,0,1,4.92-7.55,42.12,42.12,0,0,0,14.37-9.39c15.45-15.46,16.44-41,4.58-63.77L236,55.31l2.34,2.34a8,8,0,1,0,11.32-11.31ZM160,167.93a26.12,26.12,0,0,1-8.95,5.83,24.24,24.24,0,0,0-15,21.89c-.36,10.46-4,19.41-10.43,25.88-8.44,8.43-21,11.95-35.36,9.89C75,229.25,59.73,221.19,47.27,208.73S26.75,181,24.58,165.81c-2-14.37,1.46-26.92,9.89-35.36C40.94,124,49.89,120.37,60.35,120h0a24.22,24.22,0,0,0,21.89-15,26.12,26.12,0,0,1,5.83-9c5.49-5.49,13-8.13,21.38-8.13a49.38,49.38,0,0,1,19.13,4.19L108.5,112.19a32,32,0,1,0,35.31,35.31l20.08-20.08C170.41,142.71,169.47,158.41,160,167.93Zm-10.4-61.48a72.9,72.9,0,0,1,5.93,6.75l-15.42,15.42a32.22,32.22,0,0,0-12.68-12.68l15.42-15.43A73,73,0,0,1,149.55,106.45ZM112,128a16,16,0,0,1,16,16h0a16,16,0,1,1-16-16Zm48.85-32.85a86.94,86.94,0,0,0-6.68-6L176,67.31,188.69,80l-21.83,21.82A86.94,86.94,0,0,0,160.86,95.14ZM200,68.68,187.32,56,212,31.31,224.69,44ZM93.66,194.33a8,8,0,0,1-11.31,11.32l-32-32a8,8,0,0,1,11.32-11.31Z'/%3E%3C/g%3E%3Cg transform='translate(537 602) rotate(-34) scale(2.58333 2.58333) translate(-12 -12)' fill='none' stroke='%23cfd3e5' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M16 12a4.16 4.16 0 0 1 -5.62 3.89a3.78 3.78 0 0 1 -2.38 -3.39a3.42 3.42 0 0 1 2.34 -3.38l3.79 -1.42a2.89 2.89 0 0 0 1.87 -2.7a2 2 0 1 0 -4 0v14a2 2 0 1 1 -4 0'/%3E%3C/g%3E%3Cg transform='translate(9 603) rotate(28) scale(-2.58333 2.58333) translate(-12 -12)' fill='none' stroke='%23cfd3e5' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M10 13v4'/%3E%3Cpath d='M14 13v4'/%3E%3Cpath d='M18 13v4'/%3E%3Cpath d='M2 13h20'/%3E%3Cpath d='M22 11.5A3.5 3.5 0 0018.5 8a3.52 3.52 0 01-3.173-2A7 7 0 002 9v10a2 2 0 002 2h16a2 2 0 002-2z'/%3E%3Cpath d='M6 13v4'/%3E%3C/g%3E%3Cg transform='translate(729 603) rotate(28) scale(-2.58333 2.58333) translate(-12 -12)' fill='none' stroke='%23cfd3e5' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M10 13v4'/%3E%3Cpath d='M14 13v4'/%3E%3Cpath d='M18 13v4'/%3E%3Cpath d='M2 13h20'/%3E%3Cpath d='M22 11.5A3.5 3.5 0 0018.5 8a3.52 3.52 0 01-3.173-2A7 7 0 002 9v10a2 2 0 002 2h16a2 2 0 002-2z'/%3E%3Cpath d='M6 13v4'/%3E%3C/g%3E%3Cg transform='translate(313 -36) rotate(-11) scale(0.210938 0.210938) translate(-128 -128)' fill='%23cfd3e5'%3E%3Cpath d='M168,16A72.07,72.07,0,0,0,96,88a73.29,73.29,0,0,0,.63,9.42L27.12,192.22A15.93,15.93,0,0,0,28.71,213L43,227.29a15.93,15.93,0,0,0,20.78,1.59l94.81-69.53A73.29,73.29,0,0,0,168,160a72,72,0,1,0,0-144Zm56,72a55.72,55.72,0,0,1-11.16,33.52L134.49,43.16A56,56,0,0,1,224,88ZM54.32,216,40,201.68,102.14,117A72.37,72.37,0,0,0,139,153.86ZM112,88a55.67,55.67,0,0,1,11.16-33.51l78.34,78.34A56,56,0,0,1,112,88Zm-2.35,58.34a8,8,0,0,1,0,11.31l-8,8a8,8,0,1,1-11.31-11.31l8-8A8,8,0,0,1,109.67,146.33Z'/%3E%3C/g%3E%3Cg transform='translate(313 684) rotate(-11) scale(0.210938 0.210938) translate(-128 -128)' fill='%23cfd3e5'%3E%3Cpath d='M168,16A72.07,72.07,0,0,0,96,88a73.29,73.29,0,0,0,.63,9.42L27.12,192.22A15.93,15.93,0,0,0,28.71,213L43,227.29a15.93,15.93,0,0,0,20.78,1.59l94.81-69.53A73.29,73.29,0,0,0,168,160a72,72,0,1,0,0-144Zm56,72a55.72,55.72,0,0,1-11.16,33.52L134.49,43.16A56,56,0,0,1,224,88ZM54.32,216,40,201.68,102.14,117A72.37,72.37,0,0,0,139,153.86ZM112,88a55.67,55.67,0,0,1,11.16-33.51l78.34,78.34A56,56,0,0,1,112,88Zm-2.35,58.34a8,8,0,0,1,0,11.31l-8,8a8,8,0,1,1-11.31-11.31l8-8A8,8,0,0,1,109.67,146.33Z'/%3E%3C/g%3E%3Cg transform='translate(223 37) rotate(-27) scale(0.125 0.125) translate(-128 -128)' fill='%23cfd3e5'%3E%3Cpath d='M212.92,17.69a8,8,0,0,0-6.86-1.45l-128,32A8,8,0,0,0,72,56V166.08A36,36,0,1,0,88,196V110.25l112-28v51.83A36,36,0,1,0,216,164V24A8,8,0,0,0,212.92,17.69ZM52,216a20,20,0,1,1,20-20A20,20,0,0,1,52,216ZM88,93.75V62.25l112-28v31.5ZM180,184a20,20,0,1,1,20-20A20,20,0,0,1,180,184Z'/%3E%3C/g%3E%3Cg transform='translate(368 123) rotate(34) scale(0.125 0.125) translate(-128 -128)' fill='%23cfd3e5'%3E%3Cpath d='M212.92,17.69a8,8,0,0,0-6.86-1.45l-128,32A8,8,0,0,0,72,56V166.08A36,36,0,1,0,88,196V110.25l112-28v51.83A36,36,0,1,0,216,164V24A8,8,0,0,0,212.92,17.69ZM52,216a20,20,0,1,1,20-20A20,20,0,0,1,52,216ZM88,93.75V62.25l112-28v31.5ZM180,184a20,20,0,1,1,20-20A20,20,0,0,1,180,184Z'/%3E%3C/g%3E%3Cg transform='translate(578 173) rotate(-39) scale(0.125 0.125) translate(-128 -128)' fill='%23cfd3e5'%3E%3Cpath d='M212.92,17.69a8,8,0,0,0-6.86-1.45l-128,32A8,8,0,0,0,72,56V166.08A36,36,0,1,0,88,196V110.25l112-28v51.83A36,36,0,1,0,216,164V24A8,8,0,0,0,212.92,17.69ZM52,216a20,20,0,1,1,20-20A20,20,0,0,1,52,216ZM88,93.75V62.25l112-28v31.5ZM180,184a20,20,0,1,1,20-20A20,20,0,0,1,180,184Z'/%3E%3C/g%3E%3Cg transform='translate(354 248) rotate(-24) scale(-1.08333 1.08333) translate(-12 -12)' fill='none' stroke='%23cfd3e5' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M16 18.5c2 -2.5 4 -6.5 4 -10.5c0 -2.946 -2.084 -4.157 -4.204 -4.654c-.864 -.23 -2.13 -.346 -3.796 -.346c-1.667 0 -2.932 .115 -3.796 .346c-2.12 .497 -4.204 1.708 -4.204 4.654c0 3.312 2 8 4 10.5c.297 .37 .618 .731 .963 1.081l.354 .347a3.9 3.9 0 0 0 5.364 0a14.05 14.05 0 0 0 1.319 -1.428'/%3E%3C/g%3E%3Cg transform='translate(128 292) rotate(99) scale(1.08333 1.08333) translate(-12 -12)' fill='none' stroke='%23cfd3e5' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M16 18.5c2 -2.5 4 -6.5 4 -10.5c0 -2.946 -2.084 -4.157 -4.204 -4.654c-.864 -.23 -2.13 -.346 -3.796 -.346c-1.667 0 -2.932 .115 -3.796 .346c-2.12 .497 -4.204 1.708 -4.204 4.654c0 3.312 2 8 4 10.5c.297 .37 .618 .731 .963 1.081l.354 .347a3.9 3.9 0 0 0 5.364 0a14.05 14.05 0 0 0 1.319 -1.428'/%3E%3C/g%3E%3Cg transform='translate(653 433) rotate(-98) scale(-1.25 1.25) translate(-12 -12)' fill='none' stroke='%23cfd3e5' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M16 18.5c2 -2.5 4 -6.5 4 -10.5c0 -2.946 -2.084 -4.157 -4.204 -4.654c-.864 -.23 -2.13 -.346 -3.796 -.346c-1.667 0 -2.932 .115 -3.796 .346c-2.12 .497 -4.204 1.708 -4.204 4.654c0 3.312 2 8 4 10.5c.297 .37 .618 .731 .963 1.081l.354 .347a3.9 3.9 0 0 0 5.364 0a14.05 14.05 0 0 0 1.319 -1.428'/%3E%3C/g%3E%3Cg transform='translate(253 450) rotate(99) scale(-1.16667 1.16667) translate(-12 -12)' fill='none' stroke='%23cfd3e5' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M16 18.5c2 -2.5 4 -6.5 4 -10.5c0 -2.946 -2.084 -4.157 -4.204 -4.654c-.864 -.23 -2.13 -.346 -3.796 -.346c-1.667 0 -2.932 .115 -3.796 .346c-2.12 .497 -4.204 1.708 -4.204 4.654c0 3.312 2 8 4 10.5c.297 .37 .618 .731 .963 1.081l.354 .347a3.9 3.9 0 0 0 5.364 0a14.05 14.05 0 0 0 1.319 -1.428'/%3E%3C/g%3E%3Cg transform='translate(49 495) rotate(35) scale(0.125 0.125) translate(-128 -128)' fill='%23cfd3e5'%3E%3Cpath d='M212.92,17.69a8,8,0,0,0-6.86-1.45l-128,32A8,8,0,0,0,72,56V166.08A36,36,0,1,0,88,196V110.25l112-28v51.83A36,36,0,1,0,216,164V24A8,8,0,0,0,212.92,17.69ZM52,216a20,20,0,1,1,20-20A20,20,0,0,1,52,216ZM88,93.75V62.25l112-28v31.5ZM180,184a20,20,0,1,1,20-20A20,20,0,0,1,180,184Z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E") 0 0/720px 720px repeat,
    linear-gradient(to right,transparent,var(--plate) 48px,var(--plate) calc(100% - 48px),transparent)}
/* The home page plates a BLOCK rather than the column, because its banner
   paints its own black ground and a plate under it would only show at the
   edges. The block bleeds back out through
   `.wrap`'s side padding so its plate is the same width as every other page's,
   and down through the bottom padding so it meets the footer's plate with no
   seam. `padding-top` is what stops the plate's top edge landing exactly on the
   heading's cap; the heading then drops its own collapsing top margin. */
.platedblock{margin:0 calc(-1 * var(--gutter)) -60px;padding:22px var(--gutter) 60px}
.platedblock>h2:first-child{margin-top:0}
/* Four children in ONE document order that is already correct on a phone -
   title, infobox, prose, the rest - and explicit grid placement that rebuilds
   the two-column desktop look on top of it.
   
   The order is the DOM's, deliberately: no `order`, no `display:contents`. An
   earlier cut reordered a collapsed grid with those two, which works but fails
   INVISIBLY on any browser that does not implement them - the aside quietly
   stays where it sits in the markup, which is last, which is the exact bug this
   is fixing. Placement can degrade; document order cannot. If every rule below
   is dropped on the floor, a phone still reads title -> photo -> article.
   
   `row-gap:0` because the cards and headings carry their own margins.

   GRID ROWS ARE SHARED ACROSS COLUMNS, and that is the whole difficulty. With
   `.infocol` sitting in row 1 by itself, row 1 became as TALL AS THE INFOBOX -
   and `article`, starting at row 2, began below it. Measured on a band page: a
   602px infobox, a 70px title, and 532px of nothing under the heading before a
   word of prose. True from the day this grid was written, and only obvious once
   press shots made the infobox taller than a landscape Commons photo had.

   Two columns cannot flow independently while a grid decides their rows, so the
   right-hand one is ONE item: `<aside>` is nested inside `.infocol` rather than
   being a fourth child. The grid then has three items and nothing to get wrong -
   the title is row 1, the article is row 2 directly under it, and `.infocol`
   spans both so its height never inflates the title's row. Every other
   arrangement was tried and measured: with `aside` a sibling in row 3, the
   article's height distributes ACROSS the rows it spans and drags the
   connections card to the bottom of a five-thousand-pixel page.

   The phone pays for it, and this is the trade: `display:block` drops the grid
   and the document order stands, so the connections and elsewhere cards now sit
   between the photo and the prose instead of after it. The rule that mattered
   still holds - title, then photo, then article - and nothing here is a
   `order`/`display:contents` reordering that could fail invisibly. */
/* `auto 1fr`: the title row is as tall as the title. With the default rows, a right-hand
   column taller than a short article (BanG Dream!: a title, one release and a connections
   card) spread its extra height over BOTH rows it spans, and opened a gap of a couple of
   hundred pixels between the title and the first line of the article (seen 2026-10-13). */
.page{display:grid;grid-template-columns:186px minmax(0,1fr) 330px;grid-template-rows:auto 1fr;
  column-gap:34px;row-gap:0;align-items:start}
.pagehead{grid-column:2;grid-row:1;min-width:0}
.page>.infocol{grid-column:3;grid-row:1/span 2;min-width:0}
.page>article{grid-column:2;grid-row:2;min-width:0}
/* The rail is LAST in the document and first on the screen. Placement puts it
   in column 1; document order keeps the page readable with every rule below
   dropped on the floor, where a table of contents belongs after the thing it
   indexes rather than in front of it. */
.page>.toccol{grid-column:1;grid-row:1/span 2;min-width:0;
  position:sticky;top:calc(var(--bar-h) + 12px);align-self:start}
.toccol h2{font-size:11px;text-transform:uppercase;letter-spacing:.1em;color:var(--muted);
  border:0;background:none;margin:0 0 12px;padding:0}
.toclist{display:flex;flex-direction:column;
  border-left:1px solid var(--line)}
.toclist a{color:var(--muted);font-size:13px;line-height:1.35;padding:6px 0 6px 13px;
  margin-left:-1px;border-left:1px solid transparent}
.toclist a:hover{color:var(--ink);text-decoration:none}
.toclist a.on{color:var(--ink);border-left-color:var(--accent)}
/* Below this the three columns cannot all be honest widths, and the rail is the
   one whose job the scrollbar already does. */
@media (max-width:1100px){
  .page{grid-template-columns:minmax(0,1fr) 330px}
  .page>.toccol{display:none}
  .pagehead{grid-column:1}
  .page>article{grid-column:1}
  .page>.infocol{grid-column:2}
}
/* One column on a phone - and the right-hand column DISSOLVES here rather than
   stacking as a lump. Desktop needs `.infocol` to be a single grid item, because
   grid rows are shared across columns and two columns cannot otherwise flow
   independently; the phone needs the opposite, with the prose immediately after
   the photo and the cards after the prose. `display:contents` is exactly that:
   the wrapper stops generating a box, its children become flex items of `.page`
   alongside the title and the article, and `order` interleaves them.

   THIS IS THE ONE PLACE `order` AND `display:contents` ARE USED, and the reason
   the old rule against them does not apply is the FALLBACK. They were rejected
   before because a browser without them left the aside last, which was the exact
   bug being fixed. Here a browser without them renders title, photo, cards,
   prose - merely the second-best order, never a broken page - and every browser
   shipped since about 2018 has both. Two renderings of the page were the
   alternative and would mean duplicate ids and a screen reader saying
   everything twice. */
@media (max-width:900px){
  /* `align-items:stretch` UNDOES the grid rule's `start`, and it is what stops
     a release page scrolling sideways on a phone. On the grid, `start` is right
     - grid rows are shared between columns and a stretched item would grow to
     the tallest row. On the flex COLUMN the same declaration means the cross
     axis, so every child sized itself to its own content instead of to the
     page: measured at 390px, `<article>` came out 512px wide and took the whole
     document with it, one horizontal scrollbar on every release page. It
     predates the design pass (the same page measured 471px before it) and is
     invisible unless the document width is compared against the viewport's -
     the page LOOKS right, it just slides. */
  .page{display:flex;flex-direction:column;align-items:stretch}
  .page>.toccol{display:none}
  .pagehead{order:0}
  .page>.infocol{display:contents}
  .page>.infocol>.factbox{order:1}
  .page>article{order:2}
  .page>.infocol>aside{order:3}
}
/* A phone spent ~130px on the header before a word of the page: the brand
   tagline pushed the brand onto its own line and `nav` - which holds the three
   links AND the 180px search box on one non-wrapping row - took a second. The
   tagline is the one thing on it that says nothing a reader needs twice, so it
   goes; the search box then shares a row with the links instead of forcing a
   third. Nothing is removed, only the restatement. */
/* THE BROWSE STRIP TAKES ITS OWN ROW. Seven destinations, the brand, the search
   field and the Graph button want 1170px between them (measured), so below that
   the header is two rows: brand and tools on top, the strip under it. Two, and
   never three - see the shrink rules above.
   
   The tagline goes at the SAME width rather than at the phone query where it
   used to, and that is what keeps the two-row promise honest: it is the one
   element on the bar whose width the owner can change from the site's own copy
   editor, and with a tagline set the top row wants ~755px, which would wrap
   again on a tablet. It says nothing a reader needs twice - the argument that
   removed it from the phone in the first place - so it now earns its place only
   on a screen wide enough for the single-row bar the mockup draws.
   
   1240 AND NOT 1170, THE MEASURED FIGURE: a window is not its layout width. A
   classic vertical scrollbar takes ~15px out of it, and this site's pages are
   long enough that every one of them has that scrollbar - measured, a 1181px
   frame laid out at 1166 and wrapped. The margin is the scrollbar plus room for
   a platform whose Inter metrics run a few percent wide, and a 1280 laptop -
   the width this has to look right at - still clears it by 40px.
   
   `order` is used in exactly one other place in this stylesheet, and the
   argument is the same one: judge it by the FALLBACK. A browser without it
   renders brand, browse, tools - three lines in the document's own reading
   order, which is merely taller, never broken. Every browser shipped since
   about 2015 has it. */
@media (max-width:1240px){
  .brand span{display:none}
  .topbar .browse{order:3;flex:1 1 100%}
  .topbar .tools{order:2}
}
@media (max-width:640px){
  .topbar{gap:10px 12px;padding:8px 14px}
  /* `min-width:0` on BOTH, and it is not decoration. A flex item's automatic
     minimum is its min-content width, and an `<input>` reports one derived from
     its `size` attribute - 20 characters, about 200px - which it will not go
     under however the flex is written. Letting the box fill the row without
     that costs 6px of horizontal page scroll on a 375px phone: the first
     overflow this site has ever had, and invisible unless it is measured. */
  .topbar nav{gap:13px;font-size:13.5px;min-width:0}
  /* A FLEX BASIS, not `auto`, and this is the whole two-rows-not-three fix.
     Line breaking uses each item's hypothetical size - its basis - and shrinks
     only WITHIN a line afterwards, so `flex:1 1 auto` measured `tools` at its
     content width (the search placeholder plus the Graph button, ~335px),
     found it would not sit beside the brand in 362px, and wrapped it onto a
     line of its own before any shrinking was considered. A 140px basis fits
     beside the brand at 320px, and the grow puts every spare pixel back into
     the field. Measured: the header stays 96px and the search box comes out
     WIDER than it was before this row existed, because Home and All pages
     moved off it. */
  .topbar .tools{flex:1 1 140px}
  /* The field is inside its icon wrapper now, so the wrapper is the flex item
     that has to give - the input's own rule below is what it gives to. */
  .searchwrap{flex:1 1 96px;min-width:0}
  .topbar input{width:auto;flex:1 1 96px;min-width:0}
  /* THE ONE HORIZONTAL SCROLL THIS SITE ALLOWS, and it is contained to the
     strip rather than being the page: a row of links is a thing you expect to
     slide, and seven of them with their glyphs are wider than 390px. There is
     deliberately no fade mask over the right edge - a mask that is always on
     dims the last link even when every link already fits, which is a decoration
     that lies about the state it was drawn to report. The platform's own
     overlay scrollbar says it instead, and costs no layout. */
  .topbar .browse{-webkit-overflow-scrolling:touch}
}
/* Under this width the search field is the thing that loses, and it loses to a
   button whose label is the least load-bearing text on the bar: `Graph view`
   sits next to its own glyph, on the one destination whose page announces
   itself the moment it opens. Measured at 320px, carrying the label left the
   field 25px wide - narrower than it was before the browse row existed, which
   would make this change a regression on the smallest phones for the sake of
   two words.
   
   The label is CLIPPED, never `display:none`: an icon-only control still has to
   say its name, and a hidden label is the only version of that which cannot go
   stale against the visible one. */
@media (max-width:480px){
  .topbar .tools .btn-primary .lbl,.topbar .tools .acct .lbl,.topbar .tools a[data-nav] .lbl{position:absolute;
    width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}
  /* With an account the bar carries three more glyphs (Forum, account, bell), and every 14px
     gap between them came out of the search field: 8px gives back 20 of the 28px the Forum
     link cost it at 390px (measured 2026-10-06). */
  .topbar .tools{gap:8px}
  .topbar .tools .btn-primary{padding-inline:var(--space-3)}
}
h1,h2,h3,h4,h5,h6{font-family:var(--font-heading);font-weight:var(--font-heading-weight);
  letter-spacing:-.015em;line-height:1.12}
h1{font-size:clamp(29px,3.4vw,38px);line-height:1.1;margin:.18em 0 .1em;
  letter-spacing:-.022em;margin-left:-.06em}
/* The rule under a section heading FADES OUT at both ends over 48px rather than
   stopping square - the system's one signature, and the reason it is painted as
   a background strip instead of a border: a border cannot hold a gradient. */
h2{font-size:25px;margin:1.9em 0 .55em;padding:0;border:0;background:none}
h3{font-size:19px;margin:1.5em 0 .4em}
/* A kind's heading carries the same glyph its link in the browse bar does, so
   `Venues` reads as one thing in both places. `.ico` is `display:block` for the
   nav's sake, so the heading is a flex row rather than the icon being coaxed
   inline. Muted, and sized to the heading rather than to the bar: at ink weight
   a filled mark this size competes with the word it is labelling. */
h2.kindhead{display:flex;align-items:center;gap:10px}
h2.kindhead .ico{width:20px;height:20px;color:var(--muted)}
h4{font-size:16px;margin:1.2em 0 .3em;color:var(--muted)}
.kicker{position:relative;display:block;color:var(--accent);font-size:13px;
  text-transform:uppercase;letter-spacing:.06em;font-feature-settings:"tnum" 1}
/* `.ico` is `display:block` for the nav's sake, which put the glyph on its own
   line above the word. Inside a kicker it flows with the text instead. */
.kicker .ico{display:inline-block;width:14px;height:14px;vertical-align:-2px;margin-right:6px}
/* An index page runs the full width with everything else (2026-10-01); it was
   held to 940px. */
/* An ISO date is one word to a reader and four to a line breaker: without this
   `2006-02-07` wraps after the hyphen and the column reads as two dates. */
.catpage td.nw{white-space:nowrap}
/* A MIN-WIDTH, so `.tablewrap`'s scroll is actually reached. Without it the
   table honours `width:100%` and squeezes five columns into 390px, which does
   not overflow - it just wraps every title onto six lines and reads as a fault.
   Sliding a table sideways is the site's existing answer for the disc table,
   and it is the one that keeps every column rather than hiding two. */
.catpage table{min-width:620px}
/* Sortable index tables (`SORT_JS`): the script moves a header's words into a button,
   so a column sorts by click or keyboard; the arrow shows on the sorted column only.
   Without the script the header is the plain text it always was. */
.sorth{font:inherit;color:inherit;letter-spacing:inherit;text-transform:inherit;
  background:none;border:0;padding:0;cursor:pointer;display:inline-flex;align-items:center;gap:5px}
.sorth:hover,th[aria-sort=ascending],th[aria-sort=descending]{color:var(--ink)}
.sorth:focus-visible{outline:2px solid var(--accent);outline-offset:2px;border-radius:2px}
th[aria-sort=ascending] .sortmark::after{content:"\25B2";font-size:8px}
th[aria-sort=descending] .sortmark::after{content:"\25BC";font-size:8px}
.catpage .catbar{display:flex;flex-wrap:wrap;align-items:center;gap:10px 18px;margin:6px 0 18px}
.catpage .catbar .filterbox{flex:1 1 280px;margin:0}
.lead{color:var(--muted);margin:.2em 0 1.4em;font-size:17px;max-width:var(--measure)}
article img{max-width:100%;border-radius:var(--radius-md)}
article ul{padding-left:20px}
article li{margin:.18em 0}
article>p{max-width:var(--measure)}
blockquote{margin:1em 0;padding:.4em 1em;border-left:2px solid var(--accent);
  color:var(--muted);max-width:var(--measure);text-indent:-.474em}
/* Under this width the page gutter is narrower than the hung mark's own
   advance, so the quote would press into the viewport edge - it seats flush
   instead, and the hang returns with the wider gutter. */
@media (max-width:720px){blockquote{text-indent:0}}
/* The fact row: a solid-outlined strip of two or three facts under the lede.
   SOLID, and that is the system's own distinction rather than a preference -
   its rules fade out at both ends, but "box outlines, in-control separators and
   short accent marks stay solid" (`foundations/layout.html`). The labels take
   the h6 treatment the system gives a small caps label, and the values sit on
   tabular figures so a runtime and a disc count line up down a column.
   
   `auto-fit` rather than a fixed count: the row has two cells as often as
   three, and a fixed three would leave a visible empty column on every release
   that never named a venue. */
.factrow{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));
  gap:14px 24px;margin:1.5em 0;padding:14px 18px;border:1px solid var(--line);
  border-radius:var(--radius-md);max-width:var(--measure)}
.factrow dt{font-size:11px;letter-spacing:.09em;text-transform:uppercase;
  color:var(--muted);margin:0 0 3px}
.factrow dd{margin:0;font-size:15px;font-feature-settings:"tnum" 1}
@media (max-width:480px){.factrow{padding:12px 14px;gap:12px 16px}}

/* ── The release page's own components ──────────────────────────────────────
   Nocturne draws a release as a sectioned article: a breadcrumb trail and a
   row of tags above the title, then label/value grids and lists between the
   tables. Everything here takes its colour, space and radius from the token
   block at the top of the stylesheet, and every one of these blocks is
   dropped by its renderer when the record holds nothing to put in it - an
   empty bordered box reads as a component that failed, which is the rule
   `.factrow` was already written to. */

/* The trail. `Releases / Artist / This release` - the last crumb is the page
   you are on and is deliberately NOT a link, so the trail can never offer a
   reader the page they are already reading. */
.crumbs{display:flex;flex-wrap:wrap;align-items:center;gap:7px;font-size:12px;
  color:var(--muted);margin:0 0 14px}
.crumbs a{color:inherit}
.crumbs a:hover{color:var(--ink)}

/* Tag variants. `.badge` is already Nocturne's `.tag` down to the padding and
   the radius, so these are its two missing skins rather than a second
   component spelled differently - the system's `tag-neutral` and
   `tag-outline`. */
.badge.neutral{background:var(--color-neutral-800);color:var(--color-neutral-100)}
.badge.outline{background:transparent;border:1px solid var(--accent);color:var(--accent)}
.tagrow{display:flex;flex-wrap:wrap;gap:7px;margin:0 0 11px}

/* Two columns of label/value on desktop and one on a phone. `auto-fit` rather
   than a hard two, so a record holding three spec lines does not leave a dead
   column beside them. */
.speclist{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));
  gap:0 34px;margin:.9em 0;max-width:var(--measure)}
.speclist>div{display:grid;grid-template-columns:minmax(88px,auto) minmax(0,1fr);
  gap:9px 12px;align-content:start;font-size:14px}
.speclist dt,.crewgrid dt{font-size:12.5px;color:var(--muted);margin:0}
.speclist dd,.crewgrid dd{margin:0;min-width:0;overflow-wrap:anywhere}

/* The crew grid is one column of role → people, not two: a role label runs to
   `Sound Director / Drum Technician` and its value to five names and their
   companies, and neither survives being squeezed into half a measure. */
.crewgrid{display:grid;grid-template-columns:minmax(120px,168px) minmax(0,1fr);
  gap:10px 14px;margin:.9em 0;font-size:14px;max-width:var(--measure)}

/* A tracklist heading is a row spanning the table, because it labels the rows
   under it rather than being one of them - the booklet's own `特典映像` break
   between the concert and the bonus footage. */
.trk td.pos{color:color-mix(in srgb, var(--ink) 55%, transparent);width:34px}
.trk td.note{color:var(--muted);font-size:13px}
.trk td.reading{color:var(--muted)}
.trk tr.sub td{padding-top:16px;color:var(--muted);font-size:13px;font-style:italic}
.trkcap{font-size:13px;color:var(--muted);margin:1.4em 0 .2em}

/* The footer of tags. Its rule fades at both ends like every freestanding rule
   in the system. */
.cats{display:flex;flex-wrap:wrap;align-items:center;gap:7px;margin-top:2.6em;
  padding-top:16px;background:linear-gradient(to right,transparent,var(--line) 48px,
  var(--line) calc(100% - 48px),transparent) no-repeat top/100% 1px}
.cats>span.lbl{font-size:11px;letter-spacing:.08em;text-transform:uppercase;
  color:var(--muted);margin-right:5px}
.cats a:hover{text-decoration:none;box-shadow:0 0 0 1px var(--accent)}
figcaption{font-size:11px;margin-top:var(--space-1);color:var(--muted)}
.tablewrap{overflow-x:auto}
table{border-collapse:collapse;width:100%;font-size:14px;margin:.6em 0;
  font-feature-settings:"tnum" 1}
th,td{text-align:left;padding:7px 10px;border-bottom:0;vertical-align:top}
th{color:var(--muted);font-weight:500;font-size:11px;letter-spacing:.08em;
  text-transform:uppercase}
/* Row-level strips, so one rule fades across the whole row rather than each
   cell fading to nothing at its own edges. */
thead tr{background:linear-gradient(to right,transparent,var(--line) 48px,var(--line) calc(100% - 48px),transparent) no-repeat bottom/100% 1px}
tbody tr{background:linear-gradient(to right,transparent,
  color-mix(in srgb, var(--ink) 8%, transparent) 48px,
  color-mix(in srgb, var(--ink) 8%, transparent) calc(100% - 48px),transparent)
  no-repeat bottom/100% 1px}
.card{background:var(--panel);border:0;border-radius:var(--radius-md);box-shadow:var(--shadow-sm)}
/* EVERY card in the right-hand column, not just the ones in `<aside>`. The
   infobox was the odd one out and had no padding at all - its heading and its
   rows sat hard against the border while every other card inset them by 18px -
   because the only rule was `aside .card` and the infobox is a sibling of
   `<aside>`, not a child. `.infocol` covers both now that the aside is nested
   inside it. (The graph page's `.gpanel.card` is outside `.infocol` and carries
   its own padding, so it is untouched.) */
.infocol .card{padding:16px 18px;margin-bottom:18px}
/* Its title is the first thing in the box, so the generic `h2` top margin would
   now open a 35px hole above it: with no padding on the card that margin
   collapsed out through the top edge, and adding padding is exactly what stops
   it collapsing. */
.infocol>.factbox>h2{font-size:18px;line-height:1.22;letter-spacing:-.01em;
  margin:0 0 11px;padding:0;background:none}
.infocol .kicker{font-size:10px;letter-spacing:.11em;margin:0 0 5px}
.infocol>.factbox>.kicker+h2{margin-top:0}
.infocol>.factbox>h2:first-child{margin-top:0}
aside h2{font-size:11px;text-transform:uppercase;letter-spacing:.1em;color:var(--muted);
  border:0;background:none;margin:0 0 10px;padding:0}
.infobox dl{display:grid;grid-template-columns:minmax(88px,auto) minmax(0,1fr);gap:6px 12px;
  margin:0;font-size:14px;font-feature-settings:"tnum" 1}
.infobox dt{color:var(--muted)}
.infobox dd{margin:0;overflow-wrap:anywhere}
.infobox img{width:100%;border-radius:var(--radius-md);margin-bottom:6px}
/* A LOGO is transparent where a photograph is opaque, and a label's portrait is
   now its logo where Wikidata has no picture of the building. Warner Music
   Japan's wordmark is solid black, which on the dark panel is an empty box -
   the picture vanishes for anyone whose OS is in dark mode, which is the
   default on most phones. So dark mode plates the image.

   It is deliberately unconditional rather than gated on "is this a logo",
   because it costs nothing to be wrong: this rule has no `object-fit` and no
   fixed height above it, so an opaque photograph covers its own background
   completely and never letterboxes. The plate can only ever show through
   transparency - which is exactly the set of pictures that need it.

   ★ AND THAT IS ONLY TRUE AT ZERO PADDING. A 6px pad was tried and it framed
   every photograph on the site in white, because padding sits inside the
   element and outside the image: the background then shows where the picture
   cannot cover it. The argument for making this unconditional is exactly the
   argument for keeping it flush. */
.infobox img{background:#fff}
.infobox .imgnote{color:var(--muted);font-size:11.5px;line-height:1.45;margin:0 0 12px}
.fence{margin-top:12px;font-size:12px;color:var(--muted);border-top:0;padding-top:9px;
  background:linear-gradient(to right,transparent,var(--line) 48px,var(--line) calc(100% - 48px),transparent) no-repeat top/100% 1px}
.dead{color:var(--k-dead);border-bottom:1px dotted color-mix(in srgb, var(--k-dead) 55%, transparent);
  cursor:help}
a.wiki.credited{text-decoration-style:dashed}
.badge{display:inline-block;font-size:11px;letter-spacing:.05em;text-transform:uppercase;
  font-feature-settings:"tnum" 1;
  padding:3px 9px;border-radius:calc(var(--radius-md) * .75);background:var(--color-accent-800);
  color:var(--color-accent-100);vertical-align:middle}
.badge.stub{background:transparent;border:1px solid var(--line);color:var(--muted)}
.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(250px,1fr));gap:14px;margin:14px 0 6px}
/* `The object` and `Translated pages` are a FILMSTRIP at every width: one
   row of thumbnails at a common height in a box that scrolls sideways, and a
   click opens the lightbox, which steps through the same set (it groups by
   this `.grid`) and swipes on a phone.

   It was a column grid first, then a wrapped justified gallery. A column
   grid makes every row as tall as its tallest member, so on SEXL-79 a
   portrait front cover beside landscape spreads left 552px of dead space. The
   wrapped gallery fixed that with a common HEIGHT and each picture's own
   width, which the strip keeps. But it still laid every scan out bare, and
   on a phone (one full-width scan per row) an eleven-scan release ran to
   3,200px. The owner called both sloppy (2026-10-02, phone first, then
   desktop). The strip is an index you click into, and the lightbox is where
   a scan is actually read.

   `overscroll-behavior` stops a swipe that reaches the end of the strip from
   carrying on as the browser's back gesture. There is no right padding
   because the fade below supplies the trailing edge, and `scroll-padding`
   matches the left padding so a snapped thumbnail keeps its margin instead
   of sitting flush against the box. */
.grid.objects{display:flex;gap:10px;overflow-x:auto;overscroll-behavior-x:contain;
  scroll-snap-type:x proximity;scroll-padding-left:12px;padding:12px 0 12px 12px;
  background:var(--panel);border-radius:var(--radius-md);box-shadow:var(--shadow-sm);
  scrollbar-width:thin;scrollbar-color:var(--line) transparent}
/* Each thumbnail at its own width, capped at 85% of the strip so a panorama
   (an unfolded obi) still leaves the next one peeking in. `contain`
   letterboxes the capped one rather than squashing it. */
.grid.objects>a.lb{flex:0 0 auto;height:180px;max-width:85%;display:block;
  line-height:0;scroll-snap-align:start}
.grid.objects>a.lb>img{height:100%;width:auto;max-width:100%;object-fit:contain;
  border-radius:calc(var(--radius-md) * .5)}
/* THE FADE IS THE "THIS SCROLLS" SIGNAL. A thumbnail cut off at the edge
   cannot be relied on to give it: measured, a CD jacket and its back panel at
   150px filled a phone's strip exactly, with nothing peeking at all. Sticky
   to the right edge while there is more to see, and at the end it lands in
   its own place AFTER the last thumbnail, fading into the box's own colour,
   so the last picture is never dimmed. `pointer-events:none` so a click on
   the thumbnail under it still opens that one. */
.grid.objects::after{content:"";flex:0 0 28px;margin-left:-10px;
  position:sticky;right:0;pointer-events:none;
  background:linear-gradient(to right,transparent,var(--panel))}
@media (max-width:519px){
  .grid.objects{gap:8px;scroll-padding-left:10px;padding:10px 0 10px 10px}
  .grid.objects>a.lb{height:128px}
  .grid.objects::after{margin-left:-8px}
}
/* Arrow buttons for a MOUSE, added by `STRIP_JS` (a finger or a trackpad
   scrolls the strip by itself, but a mouse wheel only scrolls the page). The
   script wraps the strip in `.objstrip` so the buttons can sit over the box
   without scrolling along with it. The same disc as the lightbox's own
   arrows, so a click on one is not read as a click on a picture. */
.objstrip{position:relative}
.stripnav{position:absolute;top:50%;transform:translateY(-50%);z-index:1;
  width:38px;height:54px;border:1px solid var(--line);border-radius:var(--radius-md);
  background:color-mix(in srgb, var(--color-bg) 82%, transparent);
  color:var(--color-neutral-200);font:400 28px/1 var(--font-body);cursor:pointer;
  display:flex;align-items:center;justify-content:center;padding:0}
.stripnav:hover{border-color:var(--accent);color:var(--accent)}
.stripnav[hidden]{display:none}
.stripprev{left:10px} .stripnext{right:10px}
.tile{display:block;padding:14px 16px;background:var(--panel);border:0;
  border-radius:var(--radius-md);color:inherit;box-shadow:var(--shadow-sm)}
.tile:hover{box-shadow:0 0 0 1px var(--accent);text-decoration:none}
.tile b{display:block;font-family:var(--font-heading);font-weight:var(--font-heading-weight);
  font-size:16px;line-height:1.2}
/* A tile with a sleeve puts the art above the text and keeps every card the
   same height, so a grid of releases reads as a shelf rather than as a ragged
   list. The FIXED BOX is what keeps the shelf - see `.tileart` for why the box
   is 2:3, and why the art is contained inside it rather than cropped to it. */
.tile.hasart{padding:0;overflow:hidden;display:flex;flex-direction:column}
/* A tile with no sleeve must not be STRETCHED to a sleeve's height: the grid
   row is as tall as its art, and a text card filling that leaves a big empty
   white panel that reads as a page that failed to load. It sizes to its own
   content instead and sits at the top of the row. Art tiles still stretch to
   each other, so a shelf stays a shelf. */
.tile:not(.hasart){align-self:start}
/* 2:3 because every cover in this tree is a Plex poster and Plex serves those
   at 1000x1500, so the box is the art's own shape and nothing is cropped. It
   was 3/4, which is wider, so `cover` scaled each poster to the box's width
   and lost a ninth of its height - ~83px off the top and ~83px off the bottom
   of every one of them. Invisible on art that is all photograph; fatal on the
   Andrew W.K sleeve, whose title sits flush against the top edge and whose
   dates sit flush against the bottom, and which arrived beheaded at both ends.
   `contain` rather than `cover` so the same thing cannot come back: a `cover:`
   pointed by hand at a square digipak or a flatbed scan is letterboxed on the
   panel colour instead of having its own lettering sliced off. The box fixes
   the height either way, so the shelf survives the odd ratio. */
.tile .tileart{width:100%;aspect-ratio:2/3;object-fit:contain;display:block;
  background:var(--color-neutral-900);border-bottom:1px solid var(--line)}
.tile .tiletext{display:block;padding:12px 14px}
.tile .meta{display:block;color:var(--muted);font-size:13px;margin-top:5px}
/* A bare mono string, not a `<code>` pill. The pill carries a filled accent
   ground, and thirty-seven of them in a grid of sleeves made the catalogue
   numbers the loudest thing on the page - above the artwork they label. */
.tile .catno{display:inline-block;margin-top:6px;font:12px/1.4 var(--mono);
  color:var(--color-accent-300);letter-spacing:.02em}
/* What this release is joined to, in the legend's own colours - the one thing
   a tile can say that the sleeve in it cannot. */
.tile .conn{display:flex;align-items:center;gap:5px;margin-top:9px}
.tile .conn i{width:8px;height:8px;border-radius:50%;display:inline-block;flex:none}
.tile .conn i.dead{border:1.4px dashed var(--k-dead)}
.tile .conn span{margin-left:2px;font-size:12px;color:var(--muted);
  font-feature-settings:"tnum" 1}
/* FOUR ACROSS ON A PHONE (owner, 2026-10-09). At the 250px minimum above, a
   phone fits ONE sleeve per row, so the home page was a scroll of full-width
   posters about two to a screen. The home page's grids carry `.homeshelf` (the
   account pages' collection grids reuse `.grid` and `.tile` and keep their own
   size), and on a phone a shelf is four columns: the sleeve and up to two lines
   of title. The date, format, catno and connection dots go - at ~80px they
   would wrap into a column of fragments, and the release page carries all of
   it. A tile with no sleeve stretches to the row and centres its title, so it
   reads as a plain sleeve and the shelf stays even. */
@media (max-width:640px){
  .grid.homeshelf{grid-template-columns:repeat(4,minmax(0,1fr));gap:12px 8px}
  .grid.homeshelf .tile .tiletext{padding:6px 7px 8px}
  .grid.homeshelf .tile b{font-size:11px;line-height:1.25;display:-webkit-box;
    -webkit-box-orient:vertical;-webkit-line-clamp:2;overflow:hidden;overflow-wrap:break-word}
  .grid.homeshelf .tile :is(.meta,.catno,.conn,.badge){display:none}
  .grid.homeshelf .tile:not(.hasart){align-self:stretch;display:flex;align-items:center;padding:0}
  .grid.homeshelf .tile:not(.hasart) .tiletext{min-width:0}  /* or a flex item grows to its longest word */
  .grid.homeshelf .tile:not(.hasart) b{-webkit-line-clamp:7}
}
.chips{display:flex;flex-wrap:wrap;gap:7px;margin:10px 0 4px}
.chip{display:inline-flex;align-items:center;gap:7px;padding:4px 11px;border:1px solid var(--line);
  border-radius:calc(var(--radius-md) * .75);font-size:13px;background:transparent;color:inherit}
.chip:hover{border-color:var(--accent);color:var(--accent);text-decoration:none;
  background:color-mix(in srgb, var(--accent) 10%, transparent)}
.chip i{width:9px;height:9px;border-radius:50%;display:inline-block}
.chip.member i{background:var(--accent)}
.chip i.credited{background:color-mix(in srgb, var(--k-person) 45%, transparent);
  border:1.2px dashed var(--k-person);box-sizing:border-box}
.chip.off{color:var(--k-dead);border-style:dashed;cursor:default;
  border-color:color-mix(in srgb, var(--k-dead) 40%, transparent)}
/* - THE HOME PAGE -
   `.statband` used to live here: a full-bleed band on `--color-section`, the
   site's one saturated colour, carrying seven counts at up to 48px. It spent
   the loudest thing the palette has, and 34px of vertical either side of it,
   on numbers that are context rather than news - and it pushed the first
   sleeve below the fold. The counts are all still here, in one sentence. */
.statline{margin:0 0 18px;font-size:15px;color:var(--muted);
  font-feature-settings:"tnum" 1;max-width:var(--measure)}
.statline b{color:var(--ink);font-weight:var(--font-heading-weight)}
/* The edge count is the one number that is about the GRAPH rather than about
   the shelf, and the graph is what the link under it opens. */
.statline b.conn{color:var(--color-accent-300)}

/* THE BANNER (owner, 2026-10-03): one black bar, the MegaFan wordmark centred
   in it, and a light film grain over the lot for an aged feel. The wordmark IS
   the h1; `line-height:0` so the heading box is the artwork's own height rather
   than a line box built around it. The bar bleeds out through `.wrap`'s side
   padding (`.platedblock`'s move) so it runs edge to edge, and through the top
   padding when it opens the page, so it sits flush under the nav bar. On the
   household build the section tabs come first and it keeps its margin.
   The grain is a 240px tile of fractal noise, NOT a filter on the bar: a tile is
   rasterised once and repeated, where a filter over a full-width box recomputes
   every pixel on every resize. `isolation` keeps it from mixing with anything
   behind the bar. Its opacity is the taste dial - .09 reads as grain on black
   without lifting the bar to grey (picked by eye against .07-.18). The worn ink
   on the letters is the mark's own filter (`_wear_filter`).
   THE AGED PRINT IS ONE RULE for two wearers (2026-10-09): the banner and the
   front page's logo panel (`.aged`) share the ground, the grain and the dust,
   because two copies would be two taste dials that drift, and each picture
   rides this stylesheet exactly once whoever wears it. */
h1.sitebar,.aged{position:relative;isolation:isolate;background:#000}
h1.sitebar::after,.aged::after{content:"";position:absolute;inset:0;pointer-events:none;
  background:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='240' height='240'%3E%3Cfilter id='g' x='0' y='0' width='100%25' height='100%25'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.8' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='240' height='240' filter='url(%23g)'/%3E%3C/svg%3E") 0 0/240px 240px repeat;opacity:.09}
/* The dust (`specks_svg`), at full strength - the specks carry their own alpha. */
h1.sitebar::before,.aged::before{content:"";position:absolute;inset:0;pointer-events:none;
  background:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='640' height='320'%3E%3Cfilter id='f' x='0' y='0' width='100%25' height='100%25'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.3' numOctaves='2' seed='9' stitchTiles='stitch'/%3E%3CfeColorMatrix type='matrix' values='0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 14 0 0 0 -11.6'/%3E%3C/filter%3E%3Crect width='640' height='320' filter='url(%23f)'/%3E%3Cg fill='white'%3E%3Ccircle cx='207.3' cy='48.3' r='1.6' fill-opacity='0.44'/%3E%3Cellipse cx='60.2' cy='186.5' rx='4.1' ry='1.8' transform='rotate(22 60.2 186.5)' fill-opacity='0.52'/%3E%3Ccircle cx='277.5' cy='22.4' r='0.7' fill-opacity='0.63'/%3E%3Ccircle cx='79.2' cy='71.4' r='1.5' fill-opacity='0.92'/%3E%3Ccircle cx='374.7' cy='15.9' r='0.8' fill-opacity='0.71'/%3E%3Ccircle cx='185.3' cy='46.2' r='0.7' fill-opacity='0.57'/%3E%3Ccircle cx='115.7' cy='186.1' r='1.5' fill-opacity='0.6'/%3E%3Ccircle cx='455.8' cy='180.6' r='1.5' fill-opacity='0.67'/%3E%3Ccircle cx='273.7' cy='100.5' r='1.4' fill-opacity='0.65'/%3E%3Ccircle cx='159.0' cy='57.5' r='2.1' fill-opacity='0.45'/%3E%3Ccircle cx='336.1' cy='280.0' r='1.9' fill-opacity='0.56'/%3E%3Ccircle cx='75.6' cy='133.8' r='2.0' fill-opacity='0.48'/%3E%3Ccircle cx='269.9' cy='307.8' r='0.7' fill-opacity='0.71'/%3E%3Ccircle cx='217.7' cy='112.1' r='1.2' fill-opacity='0.84'/%3E%3Ccircle cx='537.6' cy='302.3' r='1.1' fill-opacity='0.77'/%3E%3Ccircle cx='467.9' cy='99.1' r='1.4' fill-opacity='0.77'/%3E%3Ccircle cx='182.1' cy='123.5' r='1.7' fill-opacity='0.41'/%3E%3Ccircle cx='227.5' cy='195.5' r='1.1' fill-opacity='0.52'/%3E%3Ccircle cx='82.8' cy='79.2' r='0.9' fill-opacity='0.88'/%3E%3Ccircle cx='106.5' cy='128.5' r='0.8' fill-opacity='0.48'/%3E%3Ccircle cx='553.0' cy='89.1' r='1.0' fill-opacity='0.6'/%3E%3Ccircle cx='612.9' cy='48.3' r='0.7' fill-opacity='0.53'/%3E%3Ccircle cx='7.7' cy='265.9' r='0.7' fill-opacity='0.56'/%3E%3Ccircle cx='268.1' cy='118.2' r='1.3' fill-opacity='0.92'/%3E%3Ccircle cx='549.9' cy='304.1' r='1.6' fill-opacity='0.81'/%3E%3Cellipse cx='575.7' cy='249.6' rx='3.8' ry='1.7' transform='rotate(100 575.7 249.6)' fill-opacity='0.84'/%3E%3Ccircle cx='254.8' cy='126.1' r='1.1' fill-opacity='0.62'/%3E%3Ccircle cx='43.1' cy='66.8' r='0.7' fill-opacity='0.59'/%3E%3Ccircle cx='65.5' cy='181.4' r='1.2' fill-opacity='0.92'/%3E%3Ccircle cx='16.3' cy='279.8' r='1.5' fill-opacity='0.48'/%3E%3Cpath d='M575.1 188.6Q580.3 184.5 585.7 174.6' fill='none' stroke='white' stroke-width='.7' stroke-opacity='.5' stroke-linecap='round'/%3E%3Cpath d='M309.0 107.3Q315.5 110.7 319.2 114.3' fill='none' stroke='white' stroke-width='.7' stroke-opacity='.5' stroke-linecap='round'/%3E%3C/g%3E%3C/svg%3E") 0 0/640px 320px repeat}
h1.sitebar{display:flex;justify-content:center;
  align-items:center;line-height:0;margin:0 calc(-1 * var(--gutter));
  padding:clamp(26px,4.2vw,50px) var(--gutter);color:var(--ink)}
.wrap>h1.sitebar:first-child{margin-top:-26px}
h1.sitebar .logomark{height:clamp(102px,15.1vw,197px);max-width:100%}

/* The two index actions. Plain accent links with a glyph each, where they were
   bordered chips - a chip reads as a filter you can toggle on, and both of
   these navigate. */
.actions{display:flex;flex-wrap:wrap;gap:22px;margin:0 0 6px;font-size:14px}
.actions a{display:inline-flex;align-items:center;gap:8px;color:var(--color-accent-300)}
/* Random release (`RANDOM_JS`): beside the Releases filter, and among the home page's links. */
.randomrel{display:inline-flex;align-items:center;gap:8px;font-size:14px;color:var(--color-accent-300)}
.homeactions{margin:16px 0 6px}
/* The timeline's three views (wave 3): a row of tabs that are plain links. */
.tltabs{display:flex;flex-wrap:wrap;gap:6px;margin:4px 0 10px}
.tltabs a{padding:5px 13px;border:1px solid var(--line);border-radius:var(--radius-md);font-size:14px;color:var(--muted)}
.tltabs a:hover{color:var(--ink);text-decoration:none}
.tltabs a[aria-current]{color:var(--ink);border-color:var(--accent);background:color-mix(in srgb, var(--accent) 10%, transparent)}
/* The timeline graph (`timeline_graph`): everything placed by inline px from the build. */
.tlgraph{overflow-x:auto;overflow-y:hidden;margin:6px 0 30px;padding:4px 0 10px}
.tlgraph:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
.tltrack{position:relative}
.tlaxis{position:absolute;left:0;right:0;height:2px;background:color-mix(in srgb, var(--ink) 30%, transparent)}
.tltick{position:absolute;width:1px;height:8px;background:color-mix(in srgb, var(--ink) 30%, transparent)}
.tltick.major{height:14px;background:color-mix(in srgb, var(--ink) 55%, transparent)}
.tlyear{position:absolute;transform:translateX(-50%);font-size:11px;color:var(--muted);font-feature-settings:"tnum" 1}
.tlyear.major{color:var(--ink);font-weight:var(--font-heading-weight)}
.tlstem{position:absolute;width:1px;background:color-mix(in srgb, var(--accent) 55%, transparent)}
.tldot{position:absolute;width:9px;height:9px;margin:-4px 0 0 -4px;border-radius:50%;background:var(--accent);
  box-shadow:0 0 0 3px var(--bg)}
.tlcards{list-style:none;margin:0;padding:0}
.tltrack .tlcard{position:absolute}
.tlcard{width:112px;height:166px}
.tlcard a{display:flex;flex-direction:column;height:100%;box-sizing:border-box;overflow:hidden;border:1px solid var(--line);
  border-radius:var(--radius-md);background:var(--panel);color:var(--ink);text-decoration:none}
.tlcard a:hover,.tlcard a:focus-visible{border-color:var(--accent);text-decoration:none}
.tlcard img,.tlcard .tlnoart{display:block;width:100%;height:78px;flex:none;object-fit:cover;object-position:center 20%}
.tlcard .tlnoart{background:color-mix(in srgb, var(--accent) 14%, var(--panel))}
.tlcard .tla{font-size:11px;line-height:1.35;font-weight:var(--font-heading-weight);padding:5px 7px 0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.tlcard .tlt{font-size:11.5px;line-height:1.25;padding:1px 7px 0;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
.tlcard .tld{font-size:10.5px;line-height:1.35;color:var(--muted);padding:2px 7px 6px;margin-top:auto;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.tlloose{display:flex;flex-wrap:wrap;gap:12px;margin:10px 0 24px}

/* A section heading's rule starts AT the accent and fades out rightward, where
   every freestanding rule on the site fades at both ends. The difference is the
   point: this one is anchored to the heading above it rather than floating
   between two sections. */
.secrule{height:1px;margin:9px 0 0;
  background:linear-gradient(to right,var(--accent),var(--color-neutral-900) 38%,transparent)}
/* Muted and plain, where a release page's `.badge` is an accent pill: beside a
   rule that already starts at full accent, a second accent object 11px away
   reads as a pair of buttons rather than as a heading and its count. */
.secount{font-size:14px;color:var(--muted);font-weight:400;
  font-feature-settings:"tnum" 1}
h2 .secount{letter-spacing:0}

/* The unclaimed note. The gaps ARE part of the picture here - they draw as
   hollow dashed nodes everywhere they appear - so the page says how many there
   are instead of quietly rendering a shorter list. */
.unclaimed{margin:30px 0 0;padding:18px 0 0;display:flex;align-items:center;gap:15px;
  flex-wrap:wrap;
  background:linear-gradient(to right,transparent,var(--line) 48px,var(--line) calc(100% - 48px),transparent) no-repeat top/100% 1px}
.unclaimed svg{flex:none}
.unclaimed p{flex:1;min-width:260px;margin:0;font-size:14px;line-height:1.5;color:var(--muted)}
.unclaimed b{color:var(--ink);font-weight:var(--font-heading-weight)}
.unclaimed a{font-size:14px;white-space:nowrap}
.embed{width:100%;height:auto;display:block;margin:2px 0 4px;overflow:visible}
/* `[hidden]` has to BEAT `display:block` - the `#refreshsite[hidden]` lesson,
   third time. The served ring is hidden the moment the live card takes over. */
.embed[hidden]{display:none}
/* `touch-action:none` or a drag on a phone scrolls the page instead of moving
   the node, the same reason `#g` carries it. */
.clive{width:100%;display:block;margin:2px auto 4px;touch-action:none}
.embed .e{stroke:var(--edge);stroke-width:1.1;stroke-linecap:round;opacity:.85}
.embed .n{stroke:var(--panel);stroke-width:1.4}
.embed .n.release{fill:var(--k-release)} .embed .n.band{fill:var(--k-band)}
.embed .n.person{fill:var(--k-person)} .embed .n.company{fill:var(--k-company)}
/* A venue is drawn as a RING and a page-less name as a dashed one - the two
   node states you can read at 8px without consulting the legend. */
.embed .n.venue{fill:none;stroke:var(--k-venue);stroke-width:2}
.embed .n.dead{fill:none;stroke:var(--k-dead);stroke-width:1.5;stroke-dasharray:3 3}
.embed .n.credited{fill:color-mix(in srgb, var(--k-person) 45%, transparent);stroke:var(--k-person);
  stroke-width:1.5;stroke-dasharray:3 2}
.embed .e.dead{stroke:var(--edge);stroke-dasharray:4 4;opacity:.6}
.embed .n.focus{fill:var(--accent);stroke:var(--accent);stroke-width:5;
  stroke-opacity:.22;paint-order:stroke}
.embed .l{font-size:10.5px;fill:var(--muted)}
.embed .focuslabel{font-size:11px;fill:var(--ink);font-weight:500}
.embed .more{font-size:11px;fill:var(--accent)}
.embed a{cursor:pointer}
.embed a:hover .n{stroke:var(--accent);stroke-width:2}
.embed a:hover .l{fill:var(--accent)}

/* - the connections card - */
.conncard h2{display:flex;align-items:baseline;gap:8px}
.ccount{margin-left:auto;font-size:10.5px;letter-spacing:.04em;text-transform:none;
  color:var(--muted);font-weight:400}
/* Hidden until the script that makes them work has run - `#refreshsite`'s rule,
   for its reason: a control that cannot do anything is not put on the page. */
.cfilter[hidden]{display:none}
.cfilter{display:flex;flex-wrap:wrap;gap:5px;margin:0 0 10px}
.cchip{display:inline-flex;align-items:center;gap:5px;font:inherit;font-size:11px;
  padding:3px 8px;border:1px solid var(--line);border-radius:calc(var(--radius-md) * .75);
  background:transparent;color:var(--muted);cursor:pointer;line-height:1.4}
.cchip:hover{border-color:var(--accent);color:var(--accent)}
.cchip[aria-pressed=true]{border-color:var(--accent);color:var(--color-accent-100);
  background:color-mix(in srgb, var(--accent) 22%, transparent)}
.clegend{display:flex;flex-wrap:wrap;gap:4px 12px;margin:2px 0 0;font-size:10.5px;
  color:var(--muted)}
.clg{display:inline-flex;align-items:center;gap:5px}
.ckey{width:8px;height:8px;border-radius:50%;display:inline-block;flex:none}
.ckey.release{background:var(--k-release)} .ckey.band{background:var(--k-band)}
.ckey.person{background:var(--k-person)} .ckey.company{background:var(--k-company)}
.ckey.venue{border:1.5px solid var(--k-venue)}
.ckey.dead{border:1.5px dashed var(--k-dead)}
.ckey.credited{background:color-mix(in srgb, var(--k-person) 45%, transparent);
  border:1.5px dashed var(--k-person)}
.cfoot{margin:12px 0 0;font-size:12px;text-align:right}
/* Centred (owner, 2026-10-01), and BALANCED: at full width the note is two
   lines, and centring a full line over a short one reads as a ragged fault.
   `balance` evens them; a browser without it only centres. */
footer{padding:26px var(--gutter) 50px;color:var(--muted);font-size:13px;
  text-align:center;text-wrap:balance;
  border-top:0;background:linear-gradient(to right,transparent,var(--line) 48px,var(--line) calc(100% - 48px),transparent) no-repeat top/100% 1px,
  linear-gradient(to right,transparent,var(--plate) 48px,var(--plate) calc(100% - 48px),transparent)}
/* A search field's width, not the column's. The page runs edge to edge now
   (2026-10-01), and a filter box spread across all of it is a field nobody
   would type a word into - the reason the index page used to stop at 940px,
   kept for the one element it was really about. */
.filterbox{width:100%;max-width:640px;font:15px/1.4 var(--font-body);padding:10px 13px;border:1px solid var(--color-neutral-600);
  border-radius:var(--radius-md);background:var(--panel);color:var(--ink);margin:6px 0 18px}
.filterbox::placeholder{color:var(--muted)}
.hit{display:none}
/* The graph owns the viewport, so the page is a flex column and `#gwrap` takes
   whatever the header leaves - there is no hardcoded header height left to be
   wrong about. It WAS `position:fixed;inset:52px 0 0 0`, and 52px is the height
   of a header that fits on one line: on a phone the same header wraps to ~130px
   and the top 78px of the canvas sat behind it. `dvh` because a phone's URL bar
   makes `vh` overshoot, which would push the bottom sheet's handle off-screen -
   `vh` first so a browser that has never heard of `dvh` still gets a height. */
body.fullpage{height:100vh;height:100dvh;display:flex;flex-direction:column;overflow:hidden}
body.fullpage #gwrap{position:relative;flex:1;min-height:0}
#g{position:relative;width:100%;height:100%;display:block;cursor:grab;touch-action:none}
/* The backdrop, under the graph and catching no pointer: the record on its
   platter (#gbg, drawn by the script in WORLD space so it pans and zooms with
   the graph), the corners falling away into shadow (#gfall), and the light that
   follows a pin (#gglow). `position:relative` on #g is what keeps the graph
   above all three and the panel above the graph: positioned boxes paint in
   document order, and these come first. */
#gbg,#gfall,#gglow{position:absolute;inset:0;width:100%;height:100%;pointer-events:none}
#gfall{background:radial-gradient(ellipse 82% 78% at var(--fx,50%) 50%,transparent 42%,rgb(0 0 0 / .55) 100%)}
#gglow{opacity:0;background:radial-gradient(var(--gw,400px) var(--gh,300px) at var(--gx,50%) var(--gy,50%),
  color-mix(in srgb,var(--accent) 20%,transparent),color-mix(in srgb,var(--accent) 6%,transparent) 48%,transparent)}
#g.drag{cursor:grabbing}
.gpanel{position:absolute;top:16px;left:16px;width:250px;max-height:calc(100% - 32px);overflow:auto;
  padding:14px 16px;font-size:13.5px}
.gsheet{display:none}
.gpanel h2{font-size:11px;text-transform:uppercase;letter-spacing:.1em;color:var(--muted);
  border:0;background:none;margin:0 0 9px;padding:0}
.gpanel label{display:flex;align-items:center;gap:8px;margin:5px 0;cursor:pointer}
.gpanel input[type=search]{width:100%;font:14px/1.4 var(--font-body);padding:7px 10px;
  border:1px solid var(--color-neutral-600);border-radius:var(--radius-md);background:var(--bg);
  color:var(--ink);margin-bottom:10px}
.gpanel input[type=search]::placeholder{color:var(--muted)}
.gpanel .btn{display:inline-block;padding:5px 11px;border:1px solid var(--line);
  border-radius:var(--radius-md);margin:3px 4px 0 0;cursor:pointer;font-size:13px;
  font-family:var(--font-heading);font-weight:var(--font-heading-weight);
  color:var(--ink);background:transparent}
.gpanel .btn:hover{background:color-mix(in srgb, var(--ink) 7%, transparent)}
.gpanel .btn:disabled{opacity:.45;cursor:default;background:transparent}
.gpanel .btn.on{border-color:var(--accent);color:var(--color-accent-300);
  background:color-mix(in srgb, var(--accent) 12%, transparent)}
.gpanel .hint{color:var(--muted);margin-top:10px;font-size:12px;line-height:1.5}
.swatch{width:11px;height:11px;border-radius:50%;display:inline-block}
.gfocus{display:flex;flex-wrap:wrap;align-items:center;gap:6px;margin-top:8px}
.gfocus .hint{margin:0 2px 0 6px}
#focusbtn.on{background:var(--accent);color:var(--bg);border-color:var(--accent)}
/* The route finder's answer: one sentence per name along the route. */
.groute input[type=search]{margin-bottom:6px}
.rsays{margin-top:8px;font-size:13px;line-height:1.55}
.rsays p{margin:0 0 6px}
.rsays .rhead{color:var(--muted);font-size:12px}
.rsays ol{margin:0;padding-left:18px}
.rsays li{margin:0 0 6px}
.rsays .hint{margin-top:6px}
/* Who works with whom (PLAN.connections.md §6): teams, and the folded rest of
   a long "Often credited with" table. */
ul.teams{padding-left:18px}
ul.teams li{margin:0 0 10px}
details.owmore{margin-top:8px}
details.owmore summary{cursor:pointer;color:var(--color-accent-300)}
.catpage .catbar .kindf{flex:0 0 auto;width:auto}
.swatch.credited{background:color-mix(in srgb, var(--k-person) 45%, transparent);
  border:1.5px dashed var(--k-person);box-sizing:border-box}
/* Phone: a bottom sheet. A 250px panel floating over a 390px viewport covers
   two thirds of the graph, and the graph draws behind it. It PEEKS rather than
   hiding - a control nobody can see is a control nobody finds - and the handle
   IS the toggle, so there is no floating button to collide with the sheet it
   opens. 900px matches `fit()`'s own breakpoint, so the camera reserves room
   for the sheet exactly when the sheet is there. */
@media (max-width:900px){
  .gpanel{top:auto;left:0;right:0;bottom:0;width:auto;max-height:64%;
    border-radius:14px 14px 0 0;border-bottom:0;padding:0 15px 15px;overflow:hidden;
    transform:translateY(calc(100% - 46px));transition:transform .2s ease}
  .gpanel.open{transform:none;overflow:auto}
  .gsheet{display:flex;align-items:center;gap:8px;width:100%;height:46px;padding:0;
    border:0;background:var(--panel);color:var(--ink);
    font:var(--font-heading-weight) 14px/1 var(--font-heading);
    cursor:pointer;position:sticky;top:0;z-index:1}
  /* The caret is a character in the MARKUP and not a CSS `content:` escape: the
     stylesheet is a separate file with no charset of its own, and the button
     already had to carry a label anyway. */
  .gsheet .caret{margin-left:auto;color:var(--muted);font-size:11px;transition:transform .2s ease}
  .gpanel.open .gsheet .caret{transform:rotate(180deg)}
}

.embeds{display:grid;gap:14px;margin:16px 0 4px}
/* A LONE player stops at 880px. The column runs edge to edge since
   2026-10-01, and a 16:9 video poster scaled to all of it came out ~1220x690
   at 1920 - one embed taller than the screen's first fold, ahead of everything
   the page is actually about. Two or more still share the full row. */
.embeds:not(.multi){max-width:880px}
.embeds.multi{grid-template-columns:repeat(auto-fit,minmax(300px,1fr))}
.player{margin:0;border:0;border-radius:var(--radius-md);background:var(--panel);
  box-shadow:var(--shadow-sm);overflow:hidden}
.player .stage{position:relative;width:100%;background:var(--accent-soft);border:0;padding:0;
  display:flex;align-items:center;justify-content:center;cursor:pointer;color:var(--accent)}
.player .stage:hover{filter:brightness(1.12)}
.player .stage svg{width:52px;height:52px;filter:drop-shadow(0 2px 8px rgba(0,0,0,.55))}
/* `cover` on purpose: YouTube's hqdefault is 4:3 with black bars baked in, so a
   16/9 stage that CONTAINED it would frame the bars instead of cropping them. */
.player .stage img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
.player .stage .play{position:relative;display:flex}
.player iframe{display:block;width:100%;border:0}
.player figcaption{display:flex;gap:10px;align-items:baseline;flex-wrap:wrap;
  padding:9px 13px;border-top:1px solid var(--line);font-size:14px}
.player figcaption b{font-weight:600}
.player figcaption .note{color:var(--muted)}
.player figcaption .out{margin-left:auto;font-size:13px;white-space:nowrap}
.player .prov{font-size:11px;text-transform:uppercase;letter-spacing:.08em;color:var(--muted)}

/* The editor panel. Inserted before <main> rather than floated over it: the
   graph page's bottom sheet already showed what a fixed overlay costs on a
   phone, and this one has form fields in it, which a keyboard would fight. */
.editpanel{border:0;border-radius:var(--radius-md);background:var(--card);
  box-shadow:var(--shadow-sm);padding:18px;margin:0 0 22px;max-width:100%}
.editpanel h3{margin:22px 0 10px;font-size:11px;letter-spacing:.1em;
  text-transform:uppercase;color:var(--muted);opacity:1}
.editpanel h3:first-child{margin-top:0}
.ehead{display:flex;flex-wrap:wrap;gap:10px;align-items:baseline;
  border-bottom:1px solid var(--line);padding-bottom:10px;margin-bottom:6px}
.ehead .efile{font:12px/1.4 var(--mono,monospace);opacity:.6}
.ehead button{margin-left:auto}
.enote{min-height:1.2em;margin:6px 0;color:var(--accent,#b45309);font-size:.9rem}
.erow{display:block;margin:0 0 14px}
.elabel{display:block;font-weight:600}
.epriv{font-weight:400;font-style:normal;opacity:.6;font-size:.82rem;margin-left:8px}
.ehint{display:block;opacity:.6;margin:1px 0 4px}
/* min-width:0 is load-bearing on a flex row holding an <input>: an input reports
   a min-content width off its `size` attribute and will not shrink under it,
   which is what cost this site its only horizontal scroll once already. */
.eline{display:flex;gap:8px;min-width:0}
.eline input{flex:1 1 auto;min-width:0}
.editpanel input,.editpanel textarea{font:inherit;padding:7px 9px;
  border:1px solid var(--line);border-radius:var(--radius-md);background:var(--bg);
  color:inherit;width:100%;box-sizing:border-box;caret-color:var(--accent)}
.editpanel input:hover,.editpanel textarea:hover{
  border-color:color-mix(in srgb, var(--ink) 45%, transparent)}
.editpanel input:focus-visible,.editpanel textarea:focus-visible{
  border-color:var(--accent);outline-offset:0}
.editpanel textarea{display:block;margin:6px 0;resize:vertical;
  font:14px/1.55 var(--mono,ui-monospace,monospace)}
.editpanel textarea:disabled{opacity:.55}
.editpanel button{font-family:var(--font-heading);font-weight:var(--font-heading-weight);
  font-size:14px;padding:6px 13px;border:1px solid var(--line);
  border-radius:var(--radius-md);background:transparent;color:inherit;cursor:pointer}
.editpanel button:hover:not(:disabled){background:color-mix(in srgb, var(--ink) 7%, transparent)}
.editpanel button:active:not(:disabled){background:color-mix(in srgb, var(--ink) 14%, transparent)}
.ezone{margin:0 0 20px}
.ezone h4{margin:0 0 4px;font-size:.98rem}
.etag{font-style:normal;font-weight:400;font-size:.78rem;opacity:.65;
  margin-left:8px;border:1px solid var(--line);
  border-radius:calc(var(--radius-md) * .75);padding:1px 8px}
.eclaim{float:right;margin-left:10px}
.ewhy{opacity:.7;font-size:.9rem;margin:2px 0 6px}
/* One player. Stacked, so it works at 390px without a second layout - the
   provider and the link share a row because a select is narrow and a pasted
   URL is long. */
.eplayer{border:1px solid var(--line);border-radius:var(--radius-md);padding:10px;margin:0 0 10px}
.eplayer input{margin:0 0 6px}
.eplayer .eline{margin:0 0 6px}
.eplayer select{font:inherit;padding:7px 9px;border:1px solid var(--line);
  border-radius:var(--radius-md);background:var(--bg);color:inherit;flex:0 0 auto}
.eplayer .eline input{flex:1 1 auto;min-width:0;margin:0}
.eremove{font-size:.85rem;opacity:.75}
.eplayers .ezone{margin-bottom:22px}
.ephoto label{display:block;margin:10px 0}
.ephoto .elbl{display:block;font-weight:600;margin-bottom:3px}
.ephoto input{width:100%;box-sizing:border-box}
.ephoto .ehint{display:block;color:var(--muted);font-size:12px;margin-top:3px}
#editsite{margin-left:6px}


/* The gallery grid and the lightbox that opens any picture on the site.
   One block because they are one feature: the grid is where most of the
   clickable pictures are, and the overlay is what a click does. */

/* A fixed-height thumb with `contain`, NOT `cover`. A tile crops, and the
   first two entries in this catalogue are a scale floor plan and a lighting
   rig plot - cropping the edges off a drawing throws away the part that is
   dimensioned. Letterboxing against the card is honest and keeps a row of
   mixed portrait/landscape pictures level. */
.gallery figure{margin:0;display:flex;flex-direction:column;gap:8px}
.gallery .lb{display:block;line-height:0;background:var(--panel);
  border:0;box-shadow:var(--shadow-sm);border-radius:var(--radius-md);overflow:hidden}
.gallery .lb:hover{box-shadow:0 0 0 1px var(--accent)}
/* 230px, not 200: at the ~378px cell a two-column article gives, a 3:2
   drawing then fills the cell edge to edge instead of sitting in a band of
   letterboxing, and a portrait one still has room to be recognisable. */
.gallery img{width:100%;height:230px;object-fit:contain;display:block;border-radius:0}
.gallery figcaption{font-size:13.5px;line-height:1.5}
.gallery figcaption b{font-weight:400;display:block}
.gallery figcaption .meta{color:var(--muted);font-size:12.5px}

/* An anchor is inline, so wrapping the infobox photograph in one would hang a
   line-box descender under it and open a gap the photo never had. */
.infobox .lb{display:block;line-height:0}

/* `[hidden]` has to BEAT `display:flex` - the `#refreshsite[hidden]` lesson,
   which is the same rule with a different selector. */
.lightbox[hidden]{display:none}
/* Nearly opaque, deliberately. At .88 the page still read through it - and
   what read through hardest was the gallery's OWN captions, sitting directly
   behind the overlay's copy of the caption for the picture you just opened. */
/* `overflow:hidden` because the overlay is a window onto a STRIP three
   overlays wide - the previous picture, this one, the next - and the two
   either side must be clipped, not widen the page under a phone's finger. */
.lightbox{position:fixed;inset:0;z-index:60;overflow:hidden;
  background:color-mix(in srgb, var(--color-bg) 97%, transparent)}
/* `left:-100%` parks the strip with the middle slide in view at transform
   NONE, so resting needs no pixel offset and a phone turned on its side
   while the box is open stays centred. `flex:1 1 0` gives three exact thirds,
   where `33.333%` would leave every slide a sliver short of the window. */
.lbtrack{position:absolute;top:0;bottom:0;left:-100%;width:300%;display:flex;
  will-change:transform}
.lbslide{flex:1 1 0;min-width:0;display:flex;align-items:center;
  justify-content:center;padding:30px 24px}
.lightbox figure{margin:0;display:flex;flex-direction:column;gap:12px;
  max-width:100%;max-height:100%}
/* A white ground under the picture: a transparent PNG of a black line drawing
   would otherwise be invisible against the overlay. */
.lightbox img{display:block;margin:0 auto;max-width:100%;
  max-height:calc(100vh - 150px);object-fit:contain;border-radius:var(--radius-md);background:#fff}
/* Click-to-zoom (`LIGHTBOX_JS`' `zoom`), offered to a mouse only: `zoomable`
   is set only where the pointer is fine. Zoomed, the whole overlay says
   zoom-out, because a click anywhere returns the picture to size; the close
   button and the arrows keep their own pointer. */
.lightbox.zoomable .lbslide img{cursor:zoom-in}
.lightbox.zoomed,.lightbox.zoomed .lbslide img{cursor:zoom-out}
/* At rest the close button and the arrows sit on the overlay's own dark
   ground. Zoomed, the picture runs under them, and over a scan of white paper
   a see-through button vanishes, so they get a ground of their own. */
.lightbox.zoomed .lbclose,.lightbox.zoomed .lbnav{
  background:color-mix(in srgb, var(--color-bg) 82%, transparent)}
.lightbox figcaption{margin:0 auto;max-width:72ch;text-align:center;
  color:var(--color-neutral-100);font-size:14px;line-height:1.55}
.lightbox figcaption b{display:block;font-family:var(--font-heading);
  font-weight:var(--font-heading-weight)}
.lightbox figcaption span{color:var(--color-neutral-400);font-size:12.5px}
/* Its own disc, or it lands on the topbar's search box and reads as part of
   the page underneath rather than as this overlay's control. */
.lbclose{position:absolute;top:14px;right:16px;width:40px;height:40px;
  border:1px solid var(--line);border-radius:var(--radius-md);background:transparent;
  color:var(--color-neutral-200);font:400 24px/1 var(--font-body);cursor:pointer;
  display:flex;align-items:center;justify-content:center;padding:0}
.lbclose:hover{background:color-mix(in srgb, var(--ink) 12%, transparent);
  border-color:var(--accent);color:var(--accent)}
/* The two arrows. Same disc as the close button for the same reason - a bare
   glyph over a scan reads as part of the picture. Vertically centred on the
   viewport rather than on the figure, so they do not move as pictures of
   different heights come and go. */
.lbnav{position:absolute;top:50%;transform:translateY(-50%);
  width:44px;height:64px;border:1px solid var(--line);
  border-radius:var(--radius-md);background:transparent;
  color:var(--color-neutral-200);font:400 30px/1 var(--font-body);cursor:pointer;
  display:flex;align-items:center;justify-content:center;padding:0}
.lbnav:hover{background:color-mix(in srgb, var(--ink) 12%, transparent);
  border-color:var(--accent);color:var(--accent)}
.lbprev{left:16px} .lbnext{right:16px}
.lbnav[hidden]{display:none}
.lbcount{display:block;margin-top:2px;color:var(--color-neutral-500);
  font-size:12px;font-feature-settings:"tnum" 1}
.lbcount[hidden]{display:none}
@media (max-width:640px){
  /* At phone width a 44px arrow either side eats the picture, so they sit
     under it on the caption's line instead of over it. */
  .lbnav{top:auto;bottom:14px;transform:none;width:52px;height:40px}
  .lbprev{left:12px} .lbnext{right:12px}
}
@media (max-width:640px){
  .lbslide{padding:18px 12px}
  .lightbox img{max-height:calc(100vh - 170px)}
}

/* The door on the release page: a cover, a count, a link. A `.tile` was the
   obvious reuse and is wrong here - `.tile .tileart` pins a 2:3 box because
   every cover in that grid is a 1000x1500 Plex poster, and a booklet cover is
   whatever shape the booklet is (PCXE-50110's is 128x167mm, near 3:4). The box
   would letterbox it for the sake of a shelf this link is not part of. */
.bookletlink{display:flex;gap:16px;align-items:center;padding:14px 16px;
  background:var(--panel);border-radius:var(--radius-md);color:inherit;
  box-shadow:var(--shadow-sm);margin:14px 0 6px}
.bookletlink:hover{box-shadow:0 0 0 1px var(--accent);text-decoration:none}
.bookletlink img{height:132px;width:auto;max-width:42%;display:block;
  border-radius:calc(var(--radius-md) * .5)}
.bookletlink b{display:block;font-family:var(--font-heading);
  font-weight:var(--font-heading-weight);font-size:17px;line-height:1.25}
.bookletlink .meta{display:block;color:var(--muted);font-size:13px;margin-top:5px}
@media (max-width:420px){.bookletlink{flex-direction:column;align-items:flex-start}
  .bookletlink img{max-width:100%;height:auto}}

/* A CONTACT SHEET, which is a different job from `The object`'s justified
   gallery and so does not reuse `.grid.objects`. There, every item is a surface
   a reader may want to check a catalogue number against, and a common 210px
   height is a floor. Here the grid is an INDEX to a book that is READ in the
   lightbox - you pick an opening out of it and step from there - so what earns
   its space is seeing many at once.

   THE CELL IS A FIXED 3:2 BOX and that is not decoration. The openings really
   are all one shape - a spread of a bound book is 3:2 here - but the COVERS
   are not: a front cover is a single portrait page, so a booklet is 61
   landscapes with two portraits in it. Measured on the first render, the
   portrait front cover set the first row's height and left ~400px of void
   across the four landscape cells beside it, which is the exact failure
   `.grid.objects`' comment describes and the reason that section is justified
   flex rather than a column grid. The fix here is the other one, because a
   contact sheet wants a REGULAR grid: pin the box to the openings' own shape
   and let `contain` letterbox the two covers inside it. Two cells lose some
   room; every row comes out level, which is what makes the sheet scannable. */
.grid.booklet{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));
  gap:18px 14px;margin:16px 0 6px}
.grid.booklet figure{margin:0}
.grid.booklet a.lb{display:block;line-height:0;aspect-ratio:3/2;
  background:var(--color-neutral-900);border-radius:calc(var(--radius-md) * .5)}
.grid.booklet img{width:100%;height:100%;display:block;object-fit:contain;
  border-radius:calc(var(--radius-md) * .5)}
.grid.booklet figcaption{font-size:12.5px;line-height:1.45;margin-top:7px}
/* The page number is the one thing on this sheet that is a CITATION, so it is
   set in the mono face the catalogue numbers already use. */
.grid.booklet figcaption b{display:block;font:12px/1.4 var(--mono);
  color:var(--color-accent-300);letter-spacing:.02em}
.grid.booklet .note{display:block;color:var(--muted);margin-top:2px}
.grid.booklet a.lb[data-meta]{margin-top:0}
.grid.booklet figcaption a.lb{display:inline-block;line-height:1.45;margin-top:5px;
  font-size:12px;color:var(--color-accent-300);border-bottom:1px solid transparent}
.grid.booklet figcaption a.lb:hover{border-bottom-color:currentColor;
  text-decoration:none}
/* TWO UP ON A PHONE. A booklet is a grid and not a `.grid.objects` filmstrip
   because it is READ IN ORDER and has 60-odd openings: a strip that long hides
   where you are, while a grid lets you see the book. The sheet is an index you
   tap INTO the lightbox, so what it owes you is scannability: measured, one column puts
   this booklet at 18,682px on a 390px screen and two puts it at 6,481px -
   better than half, because two columns halve the ROWS and shorten each cell
   as well. That is the difference between flicking through an index and
   scrolling past 63 of something. The picture you actually look at is full-screen
   either way, because that is what the lightbox is for. */
@media (max-width:519px){
  .grid.booklet{grid-template-columns:1fr 1fr;gap:14px 10px}
  .grid.booklet figcaption{font-size:11.5px}
}

/* The release page's collection slot (PLAN.accounts-forum.md §26-27). It is EMPTY in the
   HTML and filled by the account script, so a page that never runs it shows nothing, and an
   anonymous reader sees only the count. */
.own{display:flex;flex-wrap:wrap;align-items:center;gap:8px 14px;margin:-.6em 0 1.4em;
  font-size:14px;color:var(--muted)}
.own:empty{display:none}
.own .btn{font-size:13px}
.own .ownyes{color:var(--ink)}
.own .ownerr{color:var(--k-dead)}
.own button[disabled]{opacity:.6;cursor:progress}
.own .owndiscuss{font-size:14px}
/* Follow, under an artist page's name (wave 3). */
.follow{display:flex;flex-wrap:wrap;align-items:center;gap:8px 14px;margin:.2em 0 1em}
.follow:empty{display:none}
.follow .btn{font-size:13px}
.follow .ownby{color:var(--muted);font-size:14px}
.follow .ownerr{color:var(--k-dead)}
.follow button[disabled]{opacity:.6;cursor:progress}
.suggestfix{margin:2.4em 0 0;font-size:14px;color:var(--muted)}
/* A member's picture (wave 2) is a disc cover: cropped to a circle near the top, where the
   band's name and faces usually are. */
.topbar .acct img.acctpic{width:20px;height:20px;border-radius:50%;object-fit:cover;object-position:center 20%;flex:none}
/* The account pages, which wear this stylesheet through the chrome the app copies. Every
   rule is under `main.app`, the app's own content element, so no wiki page can pick one up;
   the controls take the design system's tokens rather than the browser's white boxes. */
main.app form{margin:10px 0 18px}
main.app label{display:inline-flex;flex-direction:column;gap:4px;margin:0 10px 8px 0;
  font-size:14px;color:var(--muted)}
main.app label:has(input[type=checkbox]){flex-direction:row;align-items:center;gap:8px}
main.app input:not([type=checkbox]),main.app textarea,main.app select{font:inherit;font-size:15px;color:var(--ink);
  background:var(--panel);border:1px solid var(--color-neutral-600);border-radius:var(--radius-md);
  padding:7px 10px;min-width:16em;max-width:100%}
main.app textarea{min-height:5em;width:min(36em,100%)}
/* `color-scheme` darkens the list a <select> opens, which is the browser's own and takes
   none of the rules above: without it the options are light text on a white list. */
main.app select{color-scheme:dark;cursor:pointer}
main.app input:focus-visible,main.app textarea:focus-visible,main.app select:focus-visible{outline:2px solid var(--accent);outline-offset:1px}
/* Report user: the profile's button is quiet (a reader looking for it finds it; nobody
   else should be drawn to it), and its form page stacks the reason over the notes. */
main.app a.reportuser{color:var(--muted);border-color:var(--line)}
main.app a.reportuser:hover{color:var(--ink);border-color:var(--muted);text-decoration:none}
main.app .reportform label{display:flex;width:min(36em,100%)}
main.app .reportform select,main.app .reportform textarea{width:100%;min-width:0;box-sizing:border-box}
main.app .reportform textarea{min-height:8em}
main.app button{font-family:var(--font-heading);font-weight:var(--font-heading-weight);font-size:14px;
  color:var(--accent);background:transparent;border:1px solid var(--accent);
  border-radius:var(--radius-md);padding:7px 14px;cursor:pointer;vertical-align:bottom}
main.app button:hover{background:color-mix(in srgb, var(--accent) 12%, transparent)}
/* The forum's link rows are lists of things to open, not running text: no underline. */
main.app .forumbar a,main.app .forumcats a,main.app .notes a{text-decoration:none}
.colrow{border-top:1px solid var(--line);padding:12px 0}
.colrow h3{margin:0 0 2px}
.colrow p{margin:2px 0}
/* Two forms side by side (the note, then Remove). An inline-flex box sits on its FIRST
   baseline, which for the note form is its label's text, so without `bottom` the Remove
   button floats up beside the label instead of beside the field. */
.colrow form{display:inline-flex;flex-wrap:wrap;gap:8px;align-items:end;margin:8px 12px 0 0;
  vertical-align:bottom}
.colrow form label{margin:0}
.colrow input[name=note]{width:min(28em,100%)}
.pager{margin:16px 0;color:var(--muted)}
/* The header's Forum link: after "All pages" while the browse strip has room, and an icon
   beside the account link once it would scroll off (see `layout`; measured, it fits to 720px). */
.topbar .tools .toolforum{display:none}
/* Icon-only wherever it shows, not only under 480px like its neighbours: between 641 and
   760px those still carry their labels, and a labelled Forum made the bar three rows. */
.topbar .tools .toolforum .lbl{position:absolute;width:1px;height:1px;padding:0;margin:-1px;
  overflow:hidden;clip-path:inset(50%);white-space:nowrap}
@media (max-width:760px){
  .topbar .browse .navforum{display:none}
  .topbar .tools .toolforum{display:inline-flex}
  .topbar .tools .toolforum,.topbar .tools .acct{min-width:24px;min-height:24px;
    justify-content:center;align-items:center}
  /* With an account showing, the tools cluster is too wide for one row here: measured
     2026-10-06, a 16-character username alone made the bar THREE rows from 641 to ~705px, and
     the Forum icon from there to 760. Icon-only, as on a phone, and only when the account link
     is there (`:has`), so a build without accounts keeps its Graph label. Names stay for
     screen readers: the labels are clipped, never removed. */
  .topbar .tools .acct .lbl,.topbar .tools:has(.acct) .btn-primary .lbl{position:absolute;width:1px;
    height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}
}
/* The header's account menu, drawn by ACCOUNT_JS (the owner, 2026-10-08): the username opens
   the member's public profile, and this hangs under it with the account's own pages. The
   bell's panel in shape; one layer above it, since the two sit side by side. */
.acctmenu{position:fixed;z-index:62;display:none;flex-direction:column;min-width:210px;
  max-width:calc(100vw - 16px);background:var(--panel);border:1px solid var(--line);
  border-radius:var(--radius-md);box-shadow:var(--shadow-md);overflow-y:auto;overscroll-behavior:contain}
.acctmenu.on{display:flex}
.acctmenu:focus{outline:none}
.accthead{padding:10px 14px;border-bottom:1px solid var(--line);font-family:var(--font-heading);
  font-weight:var(--font-heading-weight);font-size:14px;color:var(--ink)}
.acctmenu ul{list-style:none;margin:0;padding:4px 0}
.acctmenu a,.acctmenu button{display:block;width:100%;box-sizing:border-box;padding:8px 14px;
  font:inherit;font-size:14px;line-height:1.35;color:var(--ink);text-align:left;
  background:none;border:0;border-radius:0;cursor:pointer}
.acctmenu a:hover,.acctmenu a:focus-visible,.acctmenu button:hover,.acctmenu button:focus-visible{
  background:var(--color-neutral-900);color:var(--color-accent-300);text-decoration:none;outline:none}
.acctmenu a:focus-visible,.acctmenu button:focus-visible{outline:2px solid var(--accent);outline-offset:-2px}
.acctout{margin:0;padding:4px 0;border-top:1px solid var(--line)}
.topbar .acct[aria-expanded="true"]{color:var(--accent)}
/* The header's notification bell (§43). `hidden` alone loses to `.topbar nav a`'s display. */
.topbar nav a[hidden]{display:none}
.topbar .bell{position:relative}
.topbar .bell .n:empty{display:none}
.topbar .bell .n{position:absolute;top:-8px;right:-10px;min-width:16px;height:16px;padding:0 4px;
  border-radius:8px;background:var(--accent);color:var(--bg);font-size:10.5px;font-weight:700;
  line-height:16px;text-align:center}
.topbar .bell[aria-expanded="true"]{color:var(--accent)}
/* The bell's dropdown, drawn by ACCOUNT_JS: fixed and appended to <body> like the browse
   menus (`.navmenu`), so nothing in the bar clips it, and placed under the bell from its rect.
   Its own class names throughout (`bell…`): a page's `.notes li.new` is the app's markup. */
.bellmenu{position:fixed;z-index:61;display:none;flex-direction:column;width:min(380px,calc(100vw - 16px));
  background:var(--panel);border:1px solid var(--line);border-radius:var(--radius-md);
  box-shadow:var(--shadow-md);overflow:hidden}
.bellmenu.on{display:flex}
.bellmenu:focus{outline:none}
.bellhead{padding:10px 14px;border-bottom:1px solid var(--line);font-family:var(--font-heading);
  font-weight:var(--font-heading-weight);font-size:14px;color:var(--ink)}
.belllist{flex:1 1 auto;min-height:0;overflow-y:auto;overscroll-behavior:contain}
.belllist ul{list-style:none;margin:0;padding:4px 0}
.bellitem{position:relative;display:grid;gap:2px;padding:9px 14px 9px 24px;color:var(--ink);
  font-size:14px;line-height:1.35}
a.bellitem:hover,a.bellitem:focus-visible{background:var(--color-neutral-900);text-decoration:none}
a.bellitem:hover .belltext{color:var(--accent)}
.bellnew .bellitem::before{content:"";position:absolute;left:10px;top:15px;width:7px;height:7px;
  border-radius:50%;background:var(--accent)}
.bellex{color:var(--muted);font-size:13px;overflow:hidden;display:-webkit-box;
  -webkit-line-clamp:2;-webkit-box-orient:vertical}
.belltime{color:var(--muted);font-size:12px}
.bellnote{margin:0;padding:18px 14px;color:var(--muted);font-size:14px}
.bellall{display:block;padding:10px 14px;border-top:1px solid var(--line);font-size:14px;text-align:center}
.bellall:hover{background:var(--color-neutral-900);text-decoration:none}
/* The forum (Phase 4, §35-42): app pages only, so every rule is under `main.app`. */
main.app .forumbar{font-size:14px;color:var(--muted)}
main.app .forumcats{list-style:none;padding:0;margin:0}
main.app .forumcats li,main.app .notes li,main.app .recent li{border-top:1px solid var(--line);padding:11px 0;
  display:grid;gap:2px}
main.app .forumcats span,main.app .forumcats small,main.app .meta,main.app time,main.app .recent small{
  color:var(--muted);font-size:14px}
main.app table.threads{width:100%}
main.app .post{border-top:1px solid var(--line);padding:14px 0}
/* `.phead` and `.ptools`, never <header>/<footer>: the wiki styles every <footer> as the
   page's own footer band. */
main.app .post .phead{display:flex;flex-wrap:wrap;align-items:center;gap:4px 10px;margin-bottom:6px}
main.app .post .who{font-weight:var(--font-heading-weight)}
main.app .post.gone{color:var(--muted);padding:8px 0}
main.app .post .pnum{color:var(--muted)}
main.app .pbody{overflow-wrap:anywhere;max-width:48em}
main.app .pbody blockquote{margin:.6em 0;padding:.1em 0 .1em 14px;border-left:3px solid var(--line);color:var(--muted)}
main.app .pbody code{font-size:.92em;background:var(--panel);border-radius:4px;padding:1px 5px}
/* A spoiler hides in plain sight and opens on hover or focus (it is tab-reachable): no script. */
main.app .spoiler{background:var(--muted);color:transparent;border-radius:3px;cursor:pointer}
main.app .spoiler:not(:hover):not(:focus) *{color:transparent}
main.app .spoiler:hover,main.app .spoiler:focus{background:transparent;color:inherit}
main.app .ptools{display:flex;flex-wrap:wrap;align-items:center;gap:6px 14px;font-size:14px;margin-top:8px}
main.app .ptools form{margin:0}
main.app .ptools button{padding:3px 10px;font-size:13px}
main.app .ptools summary{cursor:pointer;color:var(--muted)}
main.app .ptools details[open]{flex-basis:100%}
main.app .av{width:28px;height:28px;border-radius:50%;flex:none;display:inline-flex;align-items:center;
  justify-content:center;font-weight:700;font-size:14px;color:var(--bg);background:var(--muted)}
main.app .av1{background:var(--k-band)}main.app .av2{background:var(--k-person)}
main.app .av3{background:var(--k-venue)}main.app .av4{background:var(--k-company)}
main.app .av5{background:var(--k-release)}main.app .av6{background:var(--accent)}
main.app .av7{background:var(--k-dead)}
main.app .compose label{display:flex;max-width:48em}
main.app .compose input,main.app .compose textarea{width:100%;min-width:0}
main.app .compose textarea{min-height:11em}
main.app .help{font-size:13px;color:var(--muted);max-width:48em}
main.app img.av{object-fit:cover;object-position:center 20%}
main.app .profhead{display:flex;align-items:center;gap:16px}
main.app .avbig{width:72px;height:72px;border-radius:50%;object-fit:cover;object-position:center 20%;flex:none}
main.app .about{color:var(--muted)}
main.app .avpicker{display:grid;grid-template-columns:repeat(auto-fill,minmax(104px,1fr));gap:14px;margin:6px 0 14px;max-width:48em}
main.app .avpicker label{display:flex;flex-direction:column;align-items:center;gap:6px;margin:0;text-align:center;cursor:pointer;
  font-size:12px;line-height:1.3}
main.app .avpicker img,main.app .avpicker .avnone{width:72px;height:72px;border-radius:50%;object-fit:cover;object-position:center 20%;
  border:2px solid transparent}
main.app .avpicker .avnone{display:block;background:var(--panel);border-color:var(--line)}
/* The collection as covers or as a shelf (wave 3): a two-way switch, then rows of spines
   standing on planks. A row is --spine-h of spine and 20px of gap; the plank is painted into
   that gap by the background, so it sits under every row however many wrap. 600px since
   2026-10-12 (owner: at 240px a DVD spine was ~18px wide and its printing unreadable); a
   spine picture is stored 900px tall (`scripts/spine_crops.py` HEIGHT), so this is 1.5x of
   that on a dense screen. 440px on a phone, where 600 would be a whole screen per row. */
main.app .viewtoggle{display:flex;gap:6px;margin:2px 0 14px}
main.app .viewtoggle a{padding:4px 12px;border:1px solid var(--line);border-radius:var(--radius-md);
  font-size:13px;color:var(--muted);text-decoration:none}
main.app .viewtoggle a[aria-current]{color:var(--ink);border-color:var(--accent)}
main.app .shelf{--spine-h:600px;display:flex;flex-wrap:wrap;align-items:flex-end;column-gap:4px;row-gap:20px;
  padding:0 12px 20px;margin:0 0 1.4em;background:repeating-linear-gradient(to bottom,transparent 0,
  transparent var(--spine-h),#4a3626 var(--spine-h),#5a4330 calc(var(--spine-h) + 4px),
  #3a2a1c calc(var(--spine-h) + 12px),transparent calc(var(--spine-h) + 12px),transparent calc(var(--spine-h) + 20px))}
@media (max-width:700px){main.app .shelf{--spine-h:440px}}
main.app .spine{display:block;height:var(--spine-h);flex:none;border-radius:2px;box-shadow:2px 0 4px rgba(0,0,0,.45);
  transition:transform .15s ease;text-decoration:none}
main.app .spine:hover,main.app .spine:focus-visible{transform:translateY(-8px)}
main.app .spine img{display:block;height:var(--spine-h);width:auto;border-radius:2px}
main.app .spine.plain{width:calc(var(--spine-h) / 13.5);background:hsl(var(--hue) 32% 26%);color:#ecebf0;overflow:hidden;
  border:1px solid hsl(var(--hue) 30% 36%)}
main.app .spine.plain.thin{width:calc(var(--spine-h) / 15.5)}
main.app .spine.plain span{display:block;writing-mode:vertical-rl;max-height:calc(var(--spine-h) - 16px);margin:8px auto;font-size:16px;
  line-height:1.15;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-family:var(--font-heading)}
main.app .avpicker label:has(input:checked) img,main.app .avpicker label:has(input:checked) .avnone{border-color:var(--accent)}
/* A wiki card is the wiki's own tile laid on its side: a post is not a shelf, so the cover
   is a thumbnail beside the title rather than a full 2:3 sleeve. */
main.app .wikicard{max-width:28em;margin:.6em 0}
main.app .wikicard .tile.hasart{flex-direction:row;align-items:center}
main.app .wikicard .tile .tileart{width:64px;flex:none;aspect-ratio:2/3}
main.app .wikicard .tile .tiletext{padding:10px 14px}
main.app .notes{list-style:none;padding:0}
main.app .notes li.new{box-shadow:inset 3px 0 0 var(--accent);padding-left:12px}
main.app .recent{list-style:none;padding:0}
/* `.found`, never `.hit`: the header's own search box hides its `.hit` rows. */
main.app .found{border-top:1px solid var(--line);padding:10px 0}
main.app .found h3{margin:0 0 4px}
main.app .prefs label{display:flex}
main.app table.modlog td{vertical-align:top;font-size:14px}

/* ══ THE FRONT DOOR (`landing_page`, PLAN.landing-page.md; owner 2026-10-09) ══
   One composition on one vertical axis, in three bands: the globe, the mark
   and the timeline link side by side; the wiki's two lines; and the caption
   over the strip of covers at the foot, so the first screen ends on moving
   sleeves. No header (the owner's call): the footer line stays, being the
   licence obligation and not chrome.

   ONE HORIZONTAL LINE, ONE VERTICAL AXIS - his ask was "centered and aligned
   evenly and properly", and the mockup's globe sat low and its timeline high.
   `align-items:center` puts the globe's centre, the mark's centre and the
   timeline link's centre on one line; the two side columns are the SAME width
   (`--lside`, the globe's whole box), so the middle column's centre is the
   page's centre; and the copy and the caption span the whole row and centre
   themselves, so a wide globe column could never push them off the axis.

   THE GLOBE'S BOX IS SIZED BY ITS LABELS, not by taste (review of W2's preview,
   2026-10-09: the pin labels were drawn OVER the globe). `landing_globe.js`
   keeps every label inside `#lglobe` and assumes a label is at most 150px wide
   (`placeLabels`' `labelW` default), clamping a right label's anchor to the
   box's width minus 150 - so a margin narrower than ~155px pulls the label in
   over the disc, whatever the CSS says. Hence `--llabel` IS that 150 (a test
   reads it out of the script) and the margin is DERIVED from it, label plus
   20px for the leader's elbow, so the two cannot drift.
   That fixed 2 x 170px is what decides the layout. Three across needs two
   globe boxes and the mark side by side: at 1440px a 220px disc's box is
   560px, leaving 304px for the mark between them, and below ~1400px a
   readable mark (and the plan's 220px floor for the disc) no longer fits at
   all - at 980px the two boxes alone would be 1120px. So three across starts
   at 1400px, the mark takes what the two boxes leave (up to its full 176px),
   and under that the globe and the timeline drop below the copy as a centred
   pair. The plan's `22vw` disc (317px at 1440) would have left the mark 64px
   tall; it is 15vw here, floored at 220, which reaches 330 at 2200px. */
main.landing{min-height:100vh;min-height:100svh;display:flex;flex-direction:column;
  font-family:var(--font-landing);
  /* Pin labels run out beside the disc and may pass the column; clipped here
     rather than letting one widen the page into a sideways scroll. `clip`, not
     `hidden`: it makes no scroll container, so nothing inside can be scrolled
     to by accident either. */
  overflow-x:clip}
.ltop{flex:1;display:grid;padding:clamp(28px,6vh,72px) 0 0;
  --llabel:150px;--lmargin:calc(var(--llabel) + 20px);--ldisc:clamp(220px,15vw,330px);
  --lside:calc(var(--ldisc) + 2 * var(--lmargin));
  grid-template-columns:var(--lside) minmax(0,560px) var(--lside);justify-content:center;
  grid-template-areas:"globe hero time" "copy copy copy";
  align-items:center;align-content:space-evenly;
  row-gap:clamp(28px,5vh,64px);column-gap:0}
/* The globe and the timeline share a wrapper so the narrower layouts can lay
   them out as one centred pair; three across it steps out of the way. */
.lpair{display:contents}
/* Everything that should paint OVER the mark's ground is positioned: the
   ground is a positioned box, and positioned boxes paint above plain ones.
   The globe and the timeline are also LIFTED (`z-index:1`): the ground's fade
   reaches far past the mark (at 1440px its left edge is over the globe's
   right-hand labels, at 1024px its lower fade is over the pair), and tree
   order alone kept them above it only for as long as nobody moved the hero
   later in the markup. Asked for by the review of the 2026-10-09 preview; the
   grey labels it saw there were caught mid-fade by a full-page screenshot's
   resize, not covered (measured: hiding the ground changed no label pixel),
   so this is the guarantee rather than the fix. */
.lglobecell,.ltime,.lcopy,.shelfstrip{position:relative}
.lglobecell,.ltime{z-index:1}

/* The mark's panel. LAYOUT SEES ONLY THE MARK: the black ground is a separate
   box behind it, larger than it on every side, so the fade can travel far past
   the wordmark without pushing the globe and the timeline outwards. The ground
   is masked with an ellipse that reaches transparent AT its own edges
   (`closest-side`), so the black, its grain and its dust dissolve into the page
   together with no edge anywhere, and it is sized so the wordmark sits inside
   the solid core. The plan's first value (`ellipse 72% 70%`) left the panel's
   edge at ~70% opacity - a visible rectangle, the one thing the owner asked it
   not to be - so the radii are the box's own and the box is what grows. The
   ground's size is the taste dial. Its solid core is 36%, not the 58% it
   shipped with: over the photograph (2026-10-10) a 58% core drew a black
   lozenge round the mark; at 36% it reads as a soft shadow under the
   wordmark, still dark enough behind the letters for them to read. */
.lhero{grid-area:hero;justify-self:center;position:relative;isolation:isolate;
  /* What the two globe boxes leave of the width (less 16px for a classic
     scrollbar, which `100vw` counts), at most the mark's full 176px. */
  --lmark-h:clamp(96px,calc((100vw - 16px - 2 * var(--lside)) / 2.2454),176px);--lmark-w:calc(var(--lmark-h) * 2.2454);
  --lpx:min(calc(var(--lmark-w) * .62), calc((100vw - var(--lmark-w)) / 2));
  --lpy:calc(var(--lmark-h) * .78)}
.lground{position:absolute;z-index:-1;inset:calc(-1 * var(--lpy)) calc(-1 * var(--lpx));
  pointer-events:none;
  -webkit-mask-image:radial-gradient(closest-side,#000 36%,transparent 100%);
  mask-image:radial-gradient(closest-side,#000 36%,transparent 100%)}
.lmark{margin:0;line-height:0;color:var(--ink)}
.lmark .logomark{height:var(--lmark-h);max-width:100%}
/* The mark is the shelves' link. No underline and no box of its own, so
   nothing draws an edge across the fade; the hover is the landing's other
   links' accent, and keyboard focus gets the globe's ring, which is wanted. */
.lmarklink{display:inline-block;color:inherit;text-decoration:none;border-radius:var(--radius-md)}
.lmarklink:hover,.lmarklink:focus-visible{text-decoration:none;color:var(--color-accent-300)}
.lmarklink:focus-visible{outline:2px solid var(--accent);outline-offset:6px}

/* The small globe (§14's contract; `LANDING_JS` draws into it). The link box
   is WIDER than the disc by `--lmargin` each side, the room the pin labels run
   out into, and the canvas is centred in it. No transition on the canvas: the
   script repaints it every frame. */
.lglobecell{grid-area:globe;justify-self:center}
.lglobe{position:relative;display:block;color:var(--ink);text-decoration:none;
  width:calc(var(--ldisc) + 2 * var(--lmargin));height:var(--ldisc)}
.lglobe:hover,.lglobe:focus-visible{text-decoration:none}
.lglobe:focus-visible{outline:2px solid var(--accent);outline-offset:6px;border-radius:var(--radius-md)}
.lglobe-earth{position:absolute;top:0;left:calc(50% - var(--ldisc) / 2);
  width:var(--ldisc);height:var(--ldisc);display:block;
  /* THE GLOBE WITH NO SCRIPT (review, 2026-10-09: the link was an empty
     canvas). An ocean disc in the texture's own sea blue, rgb(101,153,193) -
     the script's `OCEAN` - deepening toward the limb, then a thin fading
     atmosphere and a soft glow. The script paints over it, so this is what a
     reader without script, or before the texture lands, sees. Its geometry is
     the script's: the Earth's radius is 46% of the canvas (`RFRAC` .46, which
     is 92% of `closest-side`), and the atmosphere ring runs out to the
     canvas's edge, so the CSS disc and the painted one are the same circle. */
  border-radius:50%;
  background:radial-gradient(circle closest-side,rgb(101 153 193) 0%,rgb(92 142 183) 45%,
    rgb(64 108 148) 79%,rgb(38 72 104) 91.5%,rgb(110 165 215 / .32) 92.5%,rgb(110 165 215 / 0) 100%);
  box-shadow:0 0 calc(var(--ldisc) * .12) rgb(110 165 215 / .16)}
.lleads,.lpins{position:absolute;inset:0;list-style:none;margin:0;padding:0;pointer-events:none}
.lleads{overflow:visible}
/* Full ink, never a dimmed grey (review, 2026-10-09: the labels read as dim):
   the script fades each one in to opacity 1, so any dimming here would be a
   second, permanent fade on top of it. */
.lpins li{position:absolute;max-width:var(--llabel);font:italic 500 12px/1.3 var(--font-landing);
  color:var(--ink);text-wrap:balance}
.lpins li.right{transform:translateY(-50%)}
.lpins li.left{transform:translate(-100%,-50%);text-align:right}
.lglobe-caption{display:none}

/* View the Timeline: the drawn icon over its label, centred on the band's line. */
.ltime{grid-area:time;justify-self:center;display:flex;flex-direction:column;align-items:center;
  gap:12px;padding:10px;color:var(--ink);text-decoration:none;text-align:center}
.ltime:hover,.ltime:focus-visible{text-decoration:none}
.lticon .ico{width:clamp(64px,6vw,92px);height:auto;color:var(--color-accent-300)}
.ltlabel{font:italic 600 clamp(18px,1.6vw,24px)/1.2 var(--font-landing)}
.ltime:hover .ltlabel,.ltime:focus-visible .ltlabel{color:var(--color-accent-300)}

/* The wiki's two lines, on the page's axis (they span the whole row). */
.lcopy{grid-area:copy;justify-self:center;max-width:46rem;text-align:center}
.ltag{margin:0;font:500 clamp(23px,2.6vw,38px)/1.15 var(--font-landing);color:var(--ink);
  text-wrap:balance}
.lblurb{margin:.75em auto 0;max-width:36em;font:400 clamp(15px,1.25vw,19px)/1.55 var(--font-landing);
  color:var(--color-neutral-300);text-wrap:balance}
.lscreen{margin:1em 0 0;font:italic 500 15px/1.3 var(--font-landing)}
.lscreen a{color:var(--color-accent-300)}

/* The strip: one link, the caption over a reel of covers, full bleed (the main
   column has no side padding of its own). */
.shelfstrip{display:block;margin-top:clamp(28px,5vh,64px);padding-bottom:clamp(18px,3.5vh,36px);
  color:var(--ink);text-decoration:none;--lcover:clamp(120px,18vmin,200px)}
.shelfstrip:hover,.shelfstrip:focus-visible{text-decoration:none}
.shelfstrip:focus-visible{outline:2px solid var(--accent);outline-offset:-2px}
.lcap{display:flex;flex-wrap:wrap;justify-content:center;align-items:baseline;gap:.15em .8em;
  padding:0 16px 14px;font:500 clamp(16px,1.4vw,21px)/1.3 var(--font-landing);text-align:center}
.lcount{color:var(--muted);font-style:italic}
.shelfstrip:hover .lcapt,.shelfstrip:focus-visible .lcapt{color:var(--color-accent-300)}
.lreel{display:block;overflow:hidden}
/* Half the track per loop: the track is the row twice, and each copy carries
   its trailing gap as padding, so -50% lands the second copy exactly where the
   first began. A `gap` between the two copies would leave the loop 7px short
   and the strip would twitch once a cycle. */
.shelfstrip .track{display:flex;width:max-content;will-change:transform;
  animation:lmarquee var(--dur,120s) linear infinite}
.lcovers{display:flex;align-items:flex-end;gap:14px;padding-right:14px}
.lcovers img{display:block;height:var(--lcover);width:auto;border-radius:var(--radius-sm);
  box-shadow:0 6px 18px rgb(0 0 0 / .45)}
.lnoart{display:flex;flex-direction:column;justify-content:flex-end;gap:4px;flex:none;
  height:var(--lcover);width:calc(var(--lcover) * .71);padding:10px;box-sizing:border-box;
  border:1px solid var(--line);border-radius:var(--radius-sm);
  background:color-mix(in srgb, var(--accent) 14%, var(--panel));
  font:italic 500 13px/1.25 var(--font-landing);overflow:hidden}
.lnoart span{color:var(--muted);font-size:12px}
@keyframes lmarquee{from{transform:translateX(0)}to{transform:translateX(-50%)}}

/* The footer line, slimmer than a catalogue page's. */
body.landingpage footer{padding:16px var(--gutter) 26px;font-size:12px}
/* THE LANDING'S GROUND IS A PHOTOGRAPH (owner, 2026-10-10): a festival crowd
   and stage at night in blue haze, `scripts/site_assets/landing-bg.jpg`,
   published content-named by `copy_landing_bg`. It takes the BACKLINE'S OWN
   LAYER (`body::before`, fixed, `z-index:-1`), so the drawing is replaced on
   this page rather than stacked over the photo (the amps between the caption
   and the sleeves read as clutter on the 2026-10-09 preview), and no other
   page is touched: the rule is keyed on `body.landingpage`, so only this page
   ever fetches the picture. A fixed pseudo-element and NOT
   `background-attachment:fixed`, which iOS Safari ignores. Two knobs, both here:
   `--lphoto-pos`: the picture is PORTRAIT and `cover` on a landscape window
   shows only a band of it, so this picks the band - low enough to keep the
   stage (right of middle) and the top of the crowd rather than the empty haze
   above them. A phone is portrait and sees most of the frame either way.
   `--lphoto-scrim`: the page ground over the photo, light at the top and
   heavier at the foot where the caption and the strip sit, so the Newsreader
   copy and the pin labels read without burying the picture. The mark's own
   fade (`.lground`) dissolves its black into this.
   The layer hangs from the TOP at the LARGE viewport height (`100lvh`, with
   `100vh` for browsers without it), never `bottom:0`: a phone's address bar
   collapses as you scroll, and a layer pinned to both edges is resized with
   it, so `cover` re-cropped the picture and it slid with your thumb until you
   let go (owner, 2026-10-10). A constant box the size of the bar-hidden
   viewport is cropped once and stays put. */
body.landingpage{--lphoto-pos:60% 62%;
  --lphoto-scrim:linear-gradient(to bottom,color-mix(in srgb, var(--bg) 28%, transparent) 0%,
    color-mix(in srgb, var(--bg) 38%, transparent) 50%,color-mix(in srgb, var(--bg) 66%, transparent) 100%)}
body.landingpage::before{top:0;bottom:auto;height:100vh;height:100lvh;-webkit-mask-image:none;mask-image:none;
  background:var(--lphoto-scrim),url("landing-bg.b643b26050c57bf7.jpg") var(--lphoto-pos)/cover no-repeat}

/* Under 1400px: the mark and the copy across the width, then the globe and
   the timeline side by side as one centred pair (the box with its label room,
   the gap, the link), then the strip. The pair WRAPS where it no longer fits
   one line (about 780px), leaving the timeline under the globe on the axis,
   rather than letting either push the page sideways. */
@media (max-width:1399px){
  .ltop{grid-template-columns:minmax(0,1fr);grid-template-areas:"hero" "copy" "pair";
    padding-inline:var(--gutter);--ldisc:clamp(220px,22vw,280px)}
  .lpair{grid-area:pair;justify-self:center;display:flex;flex-wrap:wrap;
    justify-content:center;align-items:center;gap:16px clamp(24px,4vw,64px)}
  /* With a row to itself the mark is no longer what the boxes leave over. */
  .lhero{--lmark-h:clamp(104px,14vw,160px)}
}
/* Under 700px, one column in reading order. The globe drops its labels for one
   caption under the disc (the script writes it), since leader lines have no room
   beside a disc this size. */
@media (max-width:699px){
  .ltop{padding-inline:16px}
  .lpair{flex-direction:column;gap:clamp(28px,5vh,64px)}
  .lglobe{--ldisc:min(70vw,300px);--lmargin:0px;margin-bottom:3.2em}
  .lpins,.lleads{display:none}
  .lglobe-caption{display:block;position:absolute;left:0;right:0;top:calc(100% + 10px);
    text-align:center;font:italic 500 13px/1.3 var(--font-landing);color:var(--color-neutral-200)}
}
/* No spin and no drift. The strip becomes a row to scroll by hand, and its
   second copy goes, since it only ever existed to close the loop. */
@media (prefers-reduced-motion:reduce){
  .shelfstrip .track{animation:none}
  .shelfstrip .lreel{overflow-x:auto}
  .shelfstrip .lcovers+.lcovers{display:none}
}

/* ══ THE GLOBE PAGE (`globe_page`, PLAN.landing-page.md §9 and §14) ══
   The graph page's one-screen shell, undone: this page SCROLLS, because its
   CONTENT is the venue list under the stage and the stage is the upgrade over
   it. `globe.js` owns everything inside `#gstage` and `#globe-panel` (sizes,
   tabindex, the cursor, the tooltip's position); this styles the boxes it
   draws into and never anything it sets itself. Nocturne tokens throughout,
   and nothing here moves, so reduced motion has nothing to stop. */
body.fullpage.globebody{height:auto;display:block;overflow:visible}
body.globebody #gwrap{padding:0 var(--gutter) 48px;
  /* The stage's height, §14's box: square-ish on a phone (92vw), and on a
     desktop the window less the header, the lede and a margin, so the whole
     globe is in view on arrival and the list starts just below the fold. */
  --gstage-h:min(92vw, calc(100svh - 160px))}
.glede{max-width:52rem;margin:16px auto 12px;text-align:center;color:var(--muted);font-size:14px;
  line-height:1.5;text-wrap:balance}
.gstage{position:relative;height:var(--gstage-h);border-radius:var(--radius-lg);
  /* THE GLOBE WITH NO SCRIPT, the landing's rule one page over: the page's own
     ground with the ocean disc on it at the script's radius (`RAD`, .44 of the
     stage's short side = 88% of `closest-side`), so a reader with no script or
     no canvas sees a globe where the stage is rather than a tall empty box over
     the list. The script paints its Earth over this exact circle at 1x and
     larger as it zooms, so the CSS disc is never seen beside the drawn one.
     Under the disc the stage is TRANSPARENT, so its ground is the page's own:
     the contract's solid `--bg` drew a visible rectangle against the body's
     gradient on the 1440px preview (2026-10-09), and the stage is meant to
     read as the room the globe hangs in, not as a box. */
  background:radial-gradient(circle closest-side,rgb(101 153 193) 0%,rgb(92 142 183) 45%,rgb(64 108 148) 76%,
    rgb(38 72 104) 87.5%,rgb(110 165 215 / .28) 88.5%,rgb(110 165 215 / 0) 95%)}
.gstage:focus-visible{outline:2px solid var(--accent);outline-offset:3px}
.gstage canvas{position:absolute;inset:0;width:100%;height:100%;display:block;border-radius:inherit}

/* Zoom in, zoom out, reset: the site's own small buttons, bottom right of the
   stage, over the corner the disc leaves empty. */
.gcontrols{position:absolute;right:12px;bottom:12px;z-index:2;display:flex;gap:6px}
.gcontrols button{min-width:36px;height:36px;padding:0 11px;border:1px solid var(--line);
  border-radius:var(--radius-md);background:var(--panel);color:var(--ink);cursor:pointer;
  font:500 15px/1 var(--font-body)}
.gcontrols button:hover{border-color:var(--accent);color:var(--color-accent-300)}
.gcontrols button:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
.gcontrols [data-zoom]{font-size:19px;padding:0}

/* The hover/tap tooltip: the script positions it (absolute, no pointer
   events) and keeps it inside the stage; this is only the box. */
.gtip{z-index:3;max-width:260px;padding:5px 9px;background:var(--panel);color:var(--ink);
  font-size:12px;line-height:1.35;border:1px solid var(--line);border-radius:var(--radius-md);
  box-shadow:0 4px 14px rgb(0 0 0 / .35)}

/* The venue panel. `#globe-panel` and not `.gpanel`: the contract gives it the
   graph page's class too, and the graph's `.gpanel` is an absolutely placed
   250px box that becomes a translated bottom sheet under 900px - every one of
   those properties is undone here by the id, so the two pages' panels can
   change independently. Under 900px it is a card in the flow, full width
   between the stage and the list, which is where a tap on a phone sends the
   eye next. */
#globe-panel{position:relative;inset:auto;width:auto;max-width:none;max-height:none;overflow:visible;
  transform:none;transition:none;margin:14px 0 0;padding:14px 16px 16px;font-size:14px;
  background:var(--panel);color:var(--ink);border:1px solid var(--line);border-radius:var(--radius-lg)}
#globe-panel .gpanel-close{float:right;width:32px;height:32px;margin:-6px -8px 4px 8px;padding:0;
  border:0;border-radius:var(--radius-md);background:transparent;color:var(--muted);
  font:400 22px/1 var(--font-body);cursor:pointer}
#globe-panel .gpanel-close:hover{color:var(--ink);background:color-mix(in srgb, var(--ink) 7%, transparent)}
#globe-panel .gpanel-close:focus-visible{outline:2px solid var(--accent);outline-offset:1px}
#globe-panel .gpanel-venue+.gpanel-venue{margin-top:16px;padding-top:14px;border-top:1px solid var(--line)}
#globe-panel .gpanel-name{margin:0;padding:0;border:0;background:none;font-size:18px;line-height:1.25;
  text-transform:none;letter-spacing:0;color:var(--ink)}
#globe-panel .gpanel-count{margin:2px 0 0;font-size:13px}
#globe-panel .muted,.glist .muted{color:var(--muted)}
#globe-panel .gpanel-releases{list-style:none;margin:12px 0 0;padding:0;display:grid;gap:10px}
#globe-panel .gpanel-release{display:flex;gap:10px;align-items:flex-start}
#globe-panel .gpanel-cover{flex:none;width:44px;height:auto;border-radius:var(--radius-sm);
  box-shadow:0 3px 10px rgb(0 0 0 / .4)}
#globe-panel .gpanel-text{display:flex;flex-direction:column;gap:2px;min-width:0;line-height:1.35}
#globe-panel .gpanel-text time{color:var(--muted);font-size:12.5px}

/* At 900px and up the panel is a card OVER the stage's left, where the disc
   (centred, .88 of the short side) leaves room on a landscape stage: the same
   grid cell as the stage, aligned top-left, scrolling inside itself rather
   than growing past the stage. */
@media (min-width:900px){
  body.globebody #gwrap{display:grid;grid-template-columns:minmax(0,1fr)}
  .glede{grid-row:1;grid-column:1}
  .gstage{grid-row:2;grid-column:1}
  .glist{grid-row:3;grid-column:1}
  #globe-panel{grid-row:2;grid-column:1;z-index:2;align-self:start;justify-self:start;
    width:min(360px, calc(100% - 24px));margin:12px;max-height:calc(var(--gstage-h) - 24px);overflow:auto;
    box-shadow:0 10px 30px rgb(0 0 0 / .45)}
}
/* On a phone the controls overlapped the disc (measured at 390px: the stage is
   346 x 358 and the disc's lower-right edge passes under the buttons), so they
   sit in a row just BELOW the stage there, which keeps its height for them. */
@media (max-width:699px){
  .gstage{margin-bottom:50px}
  .gcontrols{top:calc(100% + 8px);bottom:auto;right:0}
}

/* Street level (PLAN.globe-tiles.md §2). Past the cap the script lays a flat
   map canvas (`#globe-map`, under `.gstage canvas`'s own rule) over the sphere,
   adds the Aerial / Map pair to the control strip (shown only while the map is),
   and writes the credits of what is on screen into `.gattrib`, bottom left, in
   the corner the controls leave free. The pressed layer reads as the hover state
   does; the script sets `aria-pressed`, this only shows it. */
.gcontrols [data-layer][aria-pressed="true"]{border-color:var(--accent);color:var(--color-accent-300)}
.gattrib{position:absolute;left:8px;bottom:8px;z-index:2;max-width:calc(100% - 380px);
  padding:3px 8px;border-radius:var(--radius-sm);font-size:11px;line-height:1.4;color:var(--muted);
  background:color-mix(in srgb, var(--panel) 86%, transparent)}
.gattrib a{color:var(--ink)}
.gcredit{font-size:13px}
@media (max-width:699px){
  .gattrib{max-width:calc(100% - 16px)}
}

/* The list: the page's content, and what the canvas is an upgrade over. */
.glist{width:100%;max-width:52rem;margin:28px auto 0;justify-self:center}
.glist h2{margin-top:0}
.glist ol{margin:0;padding-left:1.6em}
.glist ol>li{margin:0 0 14px}
.glist ol>li::marker{color:var(--muted)}
.glist ul{margin:4px 0 0;padding-left:1.2em;list-style:disc}
.glist ul li{margin:2px 0}
.glist time{color:var(--muted);font-size:13px;white-space:nowrap}
.glist .gaps{margin:22px 0 0;font-size:14px}
