/* =============================================================================
   domainchaos.com — the paper edition
   -----------------------------------------------------------------------------
   The previous build was a single 30KB file: a dark console, one orange, a
   glow on every edge, and every colour in it a literal. This is its opposite
   and deliberately so, in the register the.net.im uses — white paper, one ink,
   a great deal of air, and type doing the work the glow used to do.

   The whole system is five decisions:

     ink      #1E262B   near-black with the blue left in, never pure #000
     paper    #FFFFFF
     deep     #11161C   the dark band, one step colder than the ink
     line     a 12% ink hairline; there are no heavy borders anywhere
     measure  1265px

   This sheet is the LIGHT edition and the canonical one. The dark edition is
   assets/css/dark.css: an override sheet scoped to html[data-dark], loaded
   after this one, holding nothing but the literals this file could not express
   as tokens. Nothing in this file may be edited to serve the dark edition —
   that is what the override sheet is for, and keeping that boundary is what
   stops the light edition regressing every time the dark one is touched.

   THE ONE RULE when adding to this file: prefer a token. A declaration that
   reads var(--ink) or var(--line) is already correct in both editions and
   needs no second line anywhere. Only a literal has to be answered twice, and
   the literals that cost the most are the ones where a colour is a SURFACE
   rather than text — a band, a panel, a field. Those are the ones the audit in
   tools/audit.js counts.
   ========================================================================== */

/* ------------------------------------------------------------------ webfont
   DM Sans, self-hosted, the same two files the.net.im serves. It used to be an
   @import of fonts.googleapis.com sitting at the top of an inline <style> —
   which is the worst of every option: a render-blocking stylesheet, on a
   third-party origin, discovered only after the HTML parser reached the middle
   of the document, in a page that otherwise made no network request at all
   before first paint.

   Variable across both axes the sheet uses (opsz 9-40, wght 200-700), so the
   four-weight inversion the design rests on is 94KB of woff2 rather than
   twelve separate faces. Split on the same unicode ranges Google splits on, so
   a page of English never downloads the accented block. */
/* ORBITRON — the squared instrument face the flight-deck dials are set in.
   SIL Open Font License 1.1, so it ships with the site rather than being
   fetched: an 11KB latin subset, variable 400-900, the same one Google serves
   and the same unicode range DM Sans is split on. Self-hosted for the same
   reason DM Sans is — a dial that draws its numerals from a canvas cannot wait
   on a third-party stylesheet, and the CSP does not have to be widened for a
   file that is already here. app.js asks document.fonts for it before the
   first dial is drawn; see the note there. */
@font-face {
    font-family: 'Orbitron';
    font-style: normal;
    font-weight: 400 900;
    font-display: swap;
    src: url('/assets/fonts/orbitron-latin.woff2') format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
                   U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122,
                   U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
    font-family: 'DM Sans';
    font-style: normal;
    font-weight: 200 700;
    font-display: swap;
    src: url('/assets/fonts/dm-sans-latin.woff2') format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
                   U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122,
                   U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
    font-family: 'DM Sans';
    font-style: normal;
    font-weight: 200 700;
    font-display: swap;
    src: url('/assets/fonts/dm-sans-latin-ext.woff2') format('woff2');
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
                   U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF,
                   U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

:root {
    --ink:        #1E262B;
    --ink-77:     rgba(30, 38, 43, .77);
    /* The secondary ink, at .64 rather than .55. .55 measures 3.6:1 on the
       paper, under the 4.5:1 body-text floor, and this token carries most of
       the small type on the site: every eyebrow, every field label, every
       registrar name, the whole of the results table's second line. At .64 it
       measures 4.7:1 and is still plainly the quieter of the two inks. */
    --ink-55:     rgba(30, 38, 43, .64);
    --paper:      #ffffff;
    --tint:       #FAFAFA;  /* recessed panel ground, added for Network Labs */
    --deep:       #11161C;
    --deep-soft:  #171E26;
    --line:       rgba(30, 38, 43, .12);
    --line-soft:  rgba(30, 38, 43, .07);
    /* The inverse hairline: for rules drawn ON the deep band while the page
       itself is light. The dark edition has to flip this one the other way or
       those rules draw white on white. */
    --line-dark:  rgba(255, 255, 255, .13);
    --muted:      #717171;

    /* DomainChaos was #ff8800 throughout — a screen orange, chosen against
       #0a0a0a. It measures 2.1:1 on white, so on paper it is not an accent, it
       is a highlighter. The lineage survives as the burnt print red of the
       same family; the dark edition takes it back up to #F97316, which is the
       original orange one step calmer and holds on the deep ground. Used for
       hairlines, eyebrows and live indicators only — never as a large fill. */
    --accent:     #C2410C;
    /* The accent as a FILL, with dark type on it. Inking a colour is right for
       type and wrong for a solid block, so the two are separate tokens and the
       fill keeps its saturation. */
    --accent-fill: #FB923C;
    --on-accent:  #2A1206;

    /* ------------------------------------------------------------- semantics
       The status of a domain is the one place this site needs colour to carry
       meaning, so these four are the only hues besides the accent.

       Every one of them is three steps down its scale from where a dark-ground
       palette would put it. #00cc66 — the old build's "AVAILABLE" — measures
       1.8:1 on white; at #15803D it is 4.9:1 and still unmistakably green. The
       dark edition moves all four back up; see dark.css. */
    --free:       #15803D;   /* available            */
    --taken:      #B91C1C;   /* registered           */
    --unsure:     #B45309;   /* could not determine  */
    --info:       #1D4ED8;   /* links, social        */
    --free-wash:   rgba(21, 128, 61, .07);
    --taken-wash:  rgba(185, 28, 28, .06);
    --unsure-wash: rgba(180, 83, 9, .07);

    --measure:    1265px;
    --gutter:     clamp(20px, 5vw, 56px);

    /* The height of the sticky header, published as a token because two things
       have to agree about it: the header, and every anchor target on the page.
       A heading scrolled to the top of the viewport lands underneath a sticky
       bar unless something subtracts the bar first. */
    --nav-h:      74px;
    --band:       clamp(56px, 6.4vw, 96px);
    --ease:       cubic-bezier(.22, .61, .36, 1);
}

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

html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

/* NO DOUBLE-TAP ZOOM; PINCH STAYS. manipulation = pan-x pan-y pinch-zoom: it removes
   only the double-tap gesture (and the wait for it), so two quick presses of a nudge
   key are two nudges. Browsers intersect touch-action up to the root, so this covers
   every element that does not say otherwise. The interactive surfaces carry it as
   well, for engines that read the element itself. Written this early on purpose: a
   class rule further down (the platter's none, a rail's pan-y) wins on specificity or
   on order. NEVER maximum-scale or user-scalable=no in the viewport meta. */
html { touch-action: manipulation; }
button, a, input, select, textarea, label, summary, [role="button"], [role="radio"], [role="tab"], [role="option"],
.vz-tile, .vz-stage, .dk-lib__list, .dk-qlist { touch-action: manipulation; }

/* Applied to anything with an id rather than to a hand-kept list of sections:
   a scroll margin costs nothing on an element nobody ever scrolls to, and a
   list is a thing to forget to update. */
[id] { scroll-margin-top: calc(var(--nav-h) + 14px); }

body {
    margin: 0;
    background: var(--paper);
    color: var(--ink-77);
    font-family: 'DM Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
                 Helvetica, Arial, sans-serif;
    font-size: 17px;
    line-height: 1.62;
    /* R4: the floor, by inheritance. Set the base readable and thin only what
       is deliberately large. The opposite order leaks — anything with no class
       of its own inherits the thin weight from a wrapper, and you end up with
       11px labels at weight 300 that you cannot find a rule for. */
    font-weight: 400;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

h1, h2, h3, h4 { color: var(--ink); margin: 0; font-weight: 600; }

.d1 {
    font-size: clamp(2.1rem, 5.2vw, 3.9rem);
    font-weight: 200; letter-spacing: -.035em; line-height: 1.09;
}
.d2 {
    font-size: clamp(1.75rem, 3.5vw, 2.77rem);
    font-weight: 600; letter-spacing: -.04em; line-height: 1.06;
}
.d3 {
    font-size: clamp(1.25rem, 2vw, 1.6rem);
    font-weight: 300; letter-spacing: -.028em; line-height: 1.22;
}

/* The eyebrow. Every band opens with one; it is the only place small-caps
   tracking appears, so it reads as a system label rather than as text. 700,
   because tracking thins a face further and 500 at this size on paper is a
   smear. */
.eyebrow {
    display: flex; align-items: center; gap: .7rem;
    font-size: .688rem; font-weight: 700;
    letter-spacing: .19em; text-transform: uppercase;
    color: var(--ink-55);
    margin: 0 0 1.6rem;
}
.eyebrow::before { content: ''; width: 26px; height: 1px; background: var(--accent); flex: 0 0 auto; }
.eyebrow.on-deep { color: rgba(255, 255, 255, .62); }

.lede {
    font-size: clamp(1.02rem, 1.35vw, 1.19rem);
    line-height: 1.6; font-weight: 300; color: var(--ink-77);
    max-width: 62ch;
}
.lede + .lede { margin-top: 1rem; }

a { color: inherit; }

.wrap { max-width: var(--measure); margin: 0 auto; padding-inline: var(--gutter); }
.band { padding-block: var(--band); }
/* ------------------------------------------------------------------ the seam
   --band is a band's internal room, but what a reader sees between two
   sections is the void, and the void is not the padding. Where an unpainted
   band meets another the two paddings stack and the void is 2x--band; where
   one meets the deep band only half of it is white, because the other half is
   painted and reads as the band rather than as a gap. One uniform rule, and
   the visible white on the front page ran 101px to 262px across seven seams.

   So the seam is halved on both sides wherever two unpainted bands touch and
   left whole against a painted one. Every void is then --band, whatever it
   stands next to. The tint counts as unpainted on purpose: #FAFAFA against
   #FFFFFF is not a boundary the eye stops at. :has() is what makes the
   trailing half knowable, and where it is missing the band simply keeps the
   whole padding it shipped with.
   ------------------------------------------------------------------------ */
.band:not(.band--deep) + .band:not(.band--deep) { padding-top: calc(var(--band) * .5); }
.band:not(.band--deep):has(+ .band:not(.band--deep)) { padding-bottom: calc(var(--band) * .5); }
/* A hairline between two bands is a divider drawn IN the void, not a block
   that fills it, so the seam is still doubled and still wants halving. */
.band:not(.band--deep) + .rule + .band:not(.band--deep) { padding-top: calc(var(--band) * .5); }
.band:not(.band--deep):has(+ .rule + .band:not(.band--deep)) { padding-bottom: calc(var(--band) * .5); }

/* ---------------------------------------------------------------------------
   THE DEEP BAND

   The ink tokens are re-declared here rather than a list of `.band--deep .x`
   overrides being kept downstream. Everything on this sheet paints its
   secondary text with var(--ink-55) and its hairlines with var(--line), and
   every one of those would be near-black on a near-black ground the moment the
   component was used inside a deep band.

   --ink stays a FILL colour in a handful of places — the filled button, the
   pressed segment — so those are restated below rather than inheriting a white
   block with white type in it.
   ------------------------------------------------------------------------ */
.band--deep {
    --ink:        #FFFFFF;
    --ink-77:     rgba(255, 255, 255, .72);
    --ink-55:     rgba(255, 255, 255, .58);
    --line:       rgba(255, 255, 255, .16);
    --line-soft:  rgba(255, 255, 255, .09);
    --accent:     #F97316;
    --muted:      #8A9199;
    /* The semantic hues go back up their scale on a dark ground — the same
       three steps, in the other direction. See R6. */
    --free:       #4ADE80;
    --taken:      #F87171;
    --unsure:     #FBBF24;
    --info:       #93C5FD;

    background: var(--deep); color: var(--ink-77);
}
.band--deep h1, .band--deep h2, .band--deep h3, .band--deep h4 { color: #fff; }

/* The places --ink is a background rather than a text colour. */
.band--deep .btn                     { background: #fff; color: var(--deep); }
.band--deep .btn:hover               { background: rgba(255, 255, 255, .86); }
.band--deep .btn--ghost              { background: transparent; color: #fff; border-color: rgba(255,255,255,.38); }
.band--deep .btn--ghost:hover        { background: #fff; color: var(--deep); border-color: #fff; }
.band--deep .seg__b[aria-pressed="true"] { background: #fff; border-color: #fff; color: var(--deep); }

.band--tint { background: #FAFAFA; }
.rule { height: 1px; background: var(--line); border: 0; margin: 0; }

/* =============================================================================
   NAV
   ========================================================================== */

.nav {
    position: sticky; top: 0; z-index: 60;
    background: rgba(255, 255, 255, .86);
    backdrop-filter: saturate(180%) blur(14px);
    -webkit-backdrop-filter: saturate(180%) blur(14px);
    border-bottom: 1px solid var(--line-soft);
}
.nav__in {
    max-width: var(--measure); margin: 0 auto; padding: 0 var(--gutter);
    height: var(--nav-h); display: flex; align-items: center; gap: 1rem;
}
.nav__mark { display: flex; align-items: center; gap: .8rem; text-decoration: none; margin-right: auto; }
/* The mark. No box, no ring, no fill.

   The old build ringed a photographic disc in 4px of orange with a 45px glow;
   the rewrite before this one swapped that for a hairline and a border-radius,
   which was quieter but was still a circle drawn around a logo that already had
   one. Two edges, concentric, neither of them content.

   A wordmark on paper needs no edge at all: the letterforms are the shape, and
   the thing that separates them from the page is the page. Height is set here
   and width is left to the viewBox so the 86:24 proportion cannot be squashed
   the way an <img> with two hard attributes squashed the old one.

   The colour is inherited, not declared — currentColor in the SVG picks up
   whatever --ink resolves to in this edition, which is the entire reason it can
   be one file for both. */
.nav__logo { display: block; height: 17px; width: auto; color: var(--ink); }

/* The mark is a <div> of two links now — the the.net.im plate and the local
   wordmark — so each half re-declares the flex row the single anchor used to
   be. The plate's height is set here and its width left to the 189:80 ratio,
   for the same no-squashing reason as the wordmark above. */
.nav__mark a { display: flex; align-items: center; gap: .8rem; text-decoration: none; }
.nav__plate { display: block; height: 28px; width: auto; }
.nav__mark b {
    font-size: .95rem; font-weight: 600; letter-spacing: -.03em; color: var(--ink);
}
.nav__mark span {
    font-size: .6rem; font-weight: 700; letter-spacing: .32em;
    text-transform: uppercase; color: var(--ink-55);
    padding-left: .8rem; border-left: 1px solid var(--line);
}
@media (max-width: 980px) { .nav__mark span { display: none; } }

.nav__links { display: flex; align-items: center; gap: 1.9rem; }
@media (max-width: 1100px) { .nav__links { display: none; } }
.nav__links a {
    text-decoration: none; font-size: .875rem; font-weight: 500;
    color: var(--ink-77); letter-spacing: -.01em; position: relative; padding-block: .4rem;
}
.nav__links a::after {
    content: ''; position: absolute; left: 0; right: 0; bottom: 0;
    height: 1px; background: var(--ink); transform: scaleX(0); transform-origin: left;
    transition: transform .32s var(--ease);
}
.nav__links a:hover::after, .nav__links a[aria-current]::after { transform: scaleX(1); }
.nav__links a[aria-current] { color: var(--ink); }

/* ---------------------------------------------------------------------------
   THE EDITION PILL

   Lifted whole from the.net.im, because it is the same control doing the same
   job and a visitor who has met one should not have to learn the other. Two
   editions of this page — this one, paper, and indexdark.html — and the pill
   is where a visitor moves between them.

   Deliberately a segmented control rather than another nav link: the two are
   alternatives to each other, not destinations alongside Generator and
   Registrars, and a segmented control is the one shape that says so without a
   word of explanation.

   Exactly one segment carries .is-on, and it is written into the markup rather
   than worked out at runtime, because on any given page the answer is a
   constant: this file is always the light one. tools/mkdark.py moves it.

   The moving part is a single thumb behind both segments, not a background on
   whichever segment is lit. That is what lets it slide: on hover it travels to
   the segment under the cursor and previews the switch before the click.
   ------------------------------------------------------------------------ */
.mode-pill {
    position: relative; flex: 0 0 auto;
    display: inline-flex; align-items: center;
    padding: 3px; border-radius: 999px;
    border: 1px solid var(--line);
    /* A shallow well, so the thumb reads as sitting in the track rather than
       floating on the navbar. Both values are ink at single digits of alpha. */
    background: linear-gradient(180deg, rgba(30,38,43,.045), rgba(30,38,43,.025));
    box-shadow: inset 0 1px 2px rgba(30, 38, 43, .055);
}
.mode-pill__thumb {
    position: absolute; z-index: 0;
    top: 3px; bottom: 3px; left: 3px; width: calc(50% - 3px);
    border-radius: 999px; background: var(--ink);
    box-shadow: 0 1px 2px rgba(30, 38, 43, .26), 0 6px 18px rgba(30, 38, 43, .14);
    transition: transform .42s var(--ease);
}
/* Which half is lit. The class is on the container and written into the
   markup: on any one page the answer never changes. */
.mode-pill--dark .mode-pill__thumb { transform: translateX(100%); }

/* Hover preview. There are exactly two segments, so "the unlit one is hovered"
   already says which side the thumb belongs on and the rule needs no position
   selector — which matters, because the lit segment is a <span> and the unlit
   one an <a>, and a :first-of-type / :last-of-type pair counts types. Where
   :has is unsupported the pill simply does not preview, which costs nothing. */
.mode-pill:has(.mode-pill__seg:not(.is-on):hover) .mode-pill__thumb { transform: translateX(100%); }
.mode-pill--dark:has(.mode-pill__seg:not(.is-on):hover) .mode-pill__thumb { transform: none; }

.mode-pill__seg {
    position: relative; z-index: 1;
    display: inline-flex; align-items: center; justify-content: center;
    /* Both segments hold the same width, because the thumb is half the track
       and an uneven split would leave it short of one end. */
    min-width: 2.5rem; padding: .4rem .45rem; border-radius: 999px;
    text-decoration: none; color: var(--ink-55);
    transition: color .32s var(--ease);
}
/* The lit segment is a <span>, not a link — it is the page you are already on,
   and a link to here is a reload that costs a round trip and returns the same
   document. */
.mode-pill__seg.is-on { color: var(--paper); cursor: default; }
/* The fallback where :has is missing and the thumb therefore cannot move: the
   unlit segment darkens in place. It must not go to paper — with no thumb
   underneath, paper on the track is a label that vanishes. */
.mode-pill__seg:not(.is-on):hover { color: var(--ink); }
/* While a segment is hovered the thumb is under IT, so the lit colour follows
   the thumb rather than the class. Equal specificity, later wins. */
.mode-pill:has(.mode-pill__seg:hover) .mode-pill__seg.is-on { color: var(--ink-55); }
.mode-pill:has(.mode-pill__seg:hover) .mode-pill__seg:hover { color: var(--paper); }
.mode-pill__seg:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
/* 13px, and the same 13 is written on the <svg> tag itself as width and
   height. That duplication is the point: an SVG with a viewBox and no
   dimensions of its own falls back to 300x150 when no stylesheet reaches it. */
.mode-pill__i { width: 13px; height: 13px; flex: 0 0 auto; display: block; }

@media (prefers-reduced-motion: reduce) { .mode-pill__thumb { transition: none; } }

/* The hamburger. At every width — it opens the whole menu rather than a fold
   of the header, so on a wide screen the navbar's section links and the menu's
   tools are two different offers rather than the same one twice. */
.nav__burger {
    display: flex; width: 40px; height: 40px; padding: 0;
    background: transparent; border: 1px solid var(--line); border-radius: 8px;
    cursor: pointer; align-items: center; justify-content: center;
    color: var(--ink);
    transition: border-color .24s var(--ease);
}
.nav__burger:hover { border-color: var(--ink); }
.nav__burger svg { display: block; }
/* transform-box: view-box puts the origin in the icon's own coordinate space
   rather than on the line's own zero-height box, and the translate has to come
   second — CSS applies transform functions right to left. */
.nav__burger line {
    transform-box: view-box; transform-origin: 9px 6px;
    transition: transform .28s var(--ease);
}
.nav__burger[aria-expanded="true"] .nav__burger-t { transform: rotate(45deg) translateY(2.5px); }
.nav__burger[aria-expanded="true"] .nav__burger-b { transform: rotate(-45deg) translateY(-2.5px); }
@media (prefers-reduced-motion: reduce) { .nav__burger line { transition: none; } }

/* THE HEADER ON A PHONE. Three things have to fit inside 335 usable pixels at
   375 and 280 at 320: the mark, the edition pill, and the way into the rest of
   the site. Everything here is measured against those two numbers. */
@media (max-width: 1100px) { .nav__in { gap: .9rem; } }
@media (max-width: 640px) {
    :root { --nav-h: 62px; }
    .nav__in { gap: .55rem; }
    .nav__mark b { font-size: .86rem; }
    /* A segment is a glyph in a box, and the box is the tap target. The glyph
       stays at 13px — it is meant to be small — and what grows is the box. */
    .mode-pill__seg { min-width: 1.9rem; padding: .58rem .4rem; }
    /* No ring on a phone: the burger is the only thing in that corner and the
       outline was the heaviest object in the bar for no work it was doing.
       44px is the smallest a finger reliably hits. */
    .nav__burger { width: 44px; height: 44px; border-color: transparent; }
    .nav__burger:hover { border-color: transparent; }
    .nav__in > .btn { display: none; }
}
@media (max-width: 380px) { .nav__mark b { display: none; } }

/* =============================================================================
   THE MENU
   ========================================================================== */
.menu[hidden] { display: none; }
.menu {
    position: fixed; inset: 0; z-index: 200; display: block;
    /* Clipped: on the way out the scroll lock is released the instant close()
       runs while the panel is still travelling, and an unclipped panel sitting
       outside the viewport for those 360ms is a horizontal scrollbar that
       appears and leaves for no visible reason. */
    overflow: hidden;
}
.menu__scrim {
    position: absolute; inset: 0;
    /* A scrim exists to dim what is behind it, so it stays dark in both
       editions — see R2. It is the one black wash on this sheet that the dark
       edition does not re-polarise. */
    background: rgba(17, 22, 28, .38);
    -webkit-backdrop-filter: blur(3px); backdrop-filter: blur(3px);
    opacity: 0; transition: opacity .32s var(--ease);
}
.menu.is-open .menu__scrim { opacity: 1; }
.menu__panel {
    position: absolute; top: 0; right: 0; height: 100%;
    width: 100%; max-width: 404px;
    background: var(--paper);
    border-left: 1px solid var(--line);
    box-shadow: -24px 0 60px rgba(30, 38, 43, .10);
    display: flex; flex-direction: column;
    transform: translateX(100%);
    transition: transform .36s var(--ease);
}
.menu.is-open .menu__panel { transform: none; }
/* The panel takes focus itself on open so a screen reader reads the dialog's
   label. It is a container, not a control, and the sheet-wide :focus-visible
   rule was drawing a 2px accent ring around all 404 pixels of it. */
.menu__panel:focus, .menu__panel:focus-visible { outline: none; }
@media (prefers-reduced-motion: reduce) { .menu__scrim, .menu__panel { transition: none; } }

.menu__head {
    flex: 0 0 auto; height: 74px;
    display: flex; align-items: center; gap: .8rem;
    padding: 0 1.15rem 0 1.4rem;
    border-bottom: 1px solid var(--line-soft);
}
.menu__logo { display: block; height: 24px; width: auto; }
.menu__title {
    font-size: .6rem; font-weight: 700; letter-spacing: .3em; text-transform: uppercase;
    color: var(--ink-55); padding-left: .8rem; border-left: 1px solid var(--line);
    margin-right: auto;
}
.menu__x {
    width: 34px; height: 34px; flex: 0 0 auto; padding: 0;
    display: flex; align-items: center; justify-content: center;
    background: transparent; border: 1px solid var(--line); border-radius: 8px;
    color: var(--ink-55); cursor: pointer;
    transition: border-color .24s var(--ease), color .24s var(--ease);
}
.menu__x:hover { border-color: var(--ink); color: var(--ink); }
.menu__x svg { width: 13px; height: 13px; }
@media (max-width: 640px) { .menu__x { width: 44px; height: 44px; } }

.menu__search {
    flex: 0 0 auto; position: relative;
    padding: .9rem 1.4rem;
    border-bottom: 1px solid var(--line-soft);
    background: var(--paper);
}
.menu__search svg {
    position: absolute; left: 2.05rem; top: 50%; transform: translateY(-50%);
    width: 13px; height: 13px; color: var(--ink-55); pointer-events: none;
}
.menu__q {
    font: inherit; font-size: .88rem; font-weight: 500; width: 100%;
    padding: .6rem 2.1rem .6rem 2.05rem;
    color: var(--ink); background: rgba(30, 38, 43, .035);
    /* --line is a rule between two things that are already there; a text input
       is a box that has to announce itself, so it gets a real border. */
    border: 1px solid rgba(30, 38, 43, .52); border-radius: 8px;
    transition: border-color .24s var(--ease), background .24s var(--ease);
    -webkit-appearance: none; appearance: none;
}
.menu__q::placeholder { color: var(--ink-55); font-weight: 400; }
.menu__q::-webkit-search-cancel-button { display: none; }
.menu__q:focus { outline: none; border-color: var(--ink); background: var(--paper); }
.menu__q:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Safari on iOS zooms the page when a focused input is under 16px, and a zoomed
   page carries the 404px panel — and the field that was just tapped — off the
   visual viewport. 16px is the threshold at which it does not, so the field
   takes it wherever the zoom can happen. Matched on the pointer rather than on
   a width: a phone held sideways is 932px wide and still zooms. */
@media (hover: none) and (pointer: coarse) {
    .menu__q { font-size: 16px; }
    .field { font-size: 16px; }
}
.menu__clear {
    /* The root sheet and the sub-directory copies had drifted apart — 1.75rem
       against 2rem, a round button against a 6px one — so the clear sat in a
       different place depending on which page you opened the menu from. One
       geometry, stated here, and it is measured off the FIELD's own inset
       rather than the panel's padding so it cannot drift again. */
    position: absolute; right: 1.9rem; top: 50%; transform: translateY(-50%);
    width: 22px; height: 22px; padding: 0; display: flex;
    align-items: center; justify-content: center;
    background: transparent; border: 0; border-radius: 50%;
    color: var(--ink-55); cursor: pointer;
}
.menu__clear[hidden] { display: none; }
.menu__clear:hover { color: var(--ink); background: rgba(30, 38, 43, .06); }
.menu__clear svg { width: 10px; height: 10px; }

.menu__count {
    flex: 0 0 auto; margin: 0; padding: .55rem 1.4rem 0;
    font-size: .62rem; font-weight: 700; letter-spacing: .18em;
    text-transform: uppercase; color: var(--ink-55);
}
.menu__count b { color: var(--ink); font-weight: 700; }
.menu__count[data-filtered="1"] b { color: var(--accent); }

.menu__body { flex: 1 1 auto; overflow-y: auto; padding: 1.1rem 1.4rem 1.6rem; }
.menu__sec + .menu__sec { margin-top: 1.7rem; }
.menu__sec[hidden] { display: none; }
.menu__h {
    display: flex; align-items: center; gap: .8rem;
    font-size: .6rem; font-weight: 700; letter-spacing: .22em; text-transform: uppercase;
    color: var(--ink-55); margin: 0 0 .5rem;
}
.menu__h::after { content: ''; height: 1px; background: var(--line); flex: 1 1 auto; }
.menu__row {
    display: flex; align-items: baseline; gap: .7rem;
    padding: .62rem 0; border-bottom: 1px solid var(--line-soft);
    text-decoration: none; color: var(--ink-77);
    transition: padding-left .2s var(--ease), color .2s var(--ease);
}
.menu__row:hover { color: var(--ink); padding-left: .5rem; }
.menu__row[hidden] { display: none; }
.menu__row:last-child { border-bottom: 0; }
.menu__l { flex: 1 1 auto; min-width: 0; }
.menu__l b { display: block; font-size: .9rem; font-weight: 600; letter-spacing: -.015em; color: var(--ink); }
.menu__l span { display: block; font-size: .74rem; font-weight: 400; color: var(--ink-55); line-height: 1.4; }
.menu__go { flex: 0 0 auto; color: var(--ink-55); font-size: .8rem; font-style: normal; }
.menu__empty { margin: 1.4rem 0 0; text-align: center; font-size: .82rem; color: var(--ink-55); }
.menu__empty[hidden] { display: none; }

.menu__foot {
    flex: 0 0 auto; padding: 1rem 1.4rem;
    border-top: 1px solid var(--line-soft); background: var(--paper);
    display: flex; justify-content: center;
}
/* The footer's copy of the pill is the same control, so it is the same size: a
   segmented pill held to its own width and centred, one step larger than the
   header's because a panel footer has the room. Stretched to the full width of
   the panel it reads as a broken button rather than as the pill above. */
.menu__foot .mode-pill { display: inline-flex; width: auto; }
.menu__foot .mode-pill__seg { flex: 0 0 auto; min-width: 3.6rem; padding: .6rem .8rem; }
@media (max-width: 460px) { .menu__panel { max-width: 100%; } }

/* =============================================================================
   BUTTONS
   ========================================================================== */
.btn {
    display: inline-flex; align-items: center; justify-content: center; gap: .6rem;
    font: inherit; font-size: .82rem; font-weight: 600; letter-spacing: .02em;
    padding: .72rem 1.15rem; border-radius: 8px;
    border: 1px solid transparent; background: var(--ink); color: #fff;
    text-decoration: none; cursor: pointer;
    transition: background .25s var(--ease), color .25s var(--ease),
                border-color .25s var(--ease), transform .25s var(--ease);
}
.btn:hover { background: #000; transform: translateY(-1px); }
.btn:active { transform: none; }
.btn[disabled] { opacity: .45; cursor: not-allowed; transform: none; }
.btn--ghost { background: transparent; color: var(--ink); border-color: var(--line); }
.btn--ghost:hover { background: var(--ink); color: #fff; border-color: var(--ink); }
.btn--sm { font-size: .72rem; padding: .5rem .8rem; }
.btn--outline {
    background: transparent; color: #fff; border-color: rgba(255, 255, 255, .45);
    text-transform: uppercase; font-size: .72rem; letter-spacing: .13em; font-weight: 600;
    padding: .82rem 1.35rem;
}
.btn--outline:hover { background: #fff; color: var(--ink); border-color: #fff; }
.btn i { transition: transform .25s var(--ease); font-style: normal; }
.btn:hover i { transform: translateX(3px); }

/* =============================================================================
   HERO
   ========================================================================== */
/* ONE HEADLINE, ONE SHORT BODY, THREE KEYS ON ONE ROW — inside the first screen of
   a phone for every one of the twenty landings. The bottom padding is the band
   seam's (the .band:has(+ .band) rule), so only the top is set here. */
.hero { padding-top: clamp(36px, 6vw, 80px); }
.hero__in { max-width: 44rem; }
.hero .eyebrow { margin-bottom: 1.1rem; }
.hero h1 { margin-bottom: 1rem; text-wrap: balance; }
.hero .lede { max-width: 54ch; }
.hero__act { display: flex; flex-wrap: wrap; gap: .5rem; margin-top: 1.4rem; }
/* The three counts under the hero. Not a statistic each — a description of
   what the tool has in it, which is the only claim this page can make without
   a server behind it. */
.hero__facts {
    display: flex; flex-wrap: wrap; gap: 0 3rem;
    margin-top: 2.6rem; padding-top: 1.6rem; border-top: 1px solid var(--line);
}
.hero__fact b {
    display: block; font-size: 1.7rem; font-weight: 200; letter-spacing: -.04em;
    color: var(--ink); line-height: 1.1;
}
.hero__fact span {
    display: block; font-size: .625rem; font-weight: 700; letter-spacing: .2em;
    text-transform: uppercase; color: var(--ink-55); margin-top: .3rem;
}

/* =============================================================================
   THE CONSOLE
   -----------------------------------------------------------------------------
   The generator, its controls and its results. Everything here was a literal
   in the old file — #0f0f0f, #1a1a1a, #222, #0c0c0c, five greys inside twenty
   pixels of each other — and the whole of that is now three surfaces:

     --sur-1   the panel               a 2.5% ink wash
     --sur-2   a field inside a panel  a 4% ink wash + a real border
     --sur-3   a result row            paper, held by a hairline

   R2: on the dark ground those become white lifts, and the numbers do not
   transfer — 5% white on black reads far stronger than 5% black on white. The
   answers are in dark.css and they are not the same alphas.
   ========================================================================== */
.console {
    --sur-1: rgba(30, 38, 43, .025);
    --sur-2: rgba(30, 38, 43, .04);
    border: 1px solid var(--line); border-radius: 14px;
    background: var(--sur-1);
    overflow: hidden;
}
/* BOTH CONSOLE HEADS ARE ONE HEIGHT, and it is the status strip's. The stage's
   head was rebuilt to 36px and carry six readings; leaving the deck's at 54 to
   say "nothing loaded" put two different bars on one page claiming to be the
   same piece of furniture. */
.console__head {
    display: flex; align-items: center; gap: .8rem;
    padding: 0 1.2rem; height: 2.25rem;
    border-bottom: 1px solid var(--line);
    background: var(--paper);
}
@media (max-width: 620px) {
    .console__head { height: auto; min-height: 2.25rem; padding: .35rem .9rem; flex-wrap: wrap; }
}
.console__title {
    font-size: .625rem; font-weight: 700; letter-spacing: .2em;
    text-transform: uppercase; color: var(--ink-55); margin: 0 auto 0 0;
    display: flex; align-items: center; gap: .55rem;
}
/* The live dot. The one place the accent is allowed to be a fill, and it is
   6 pixels across. */
.console__dot {
    width: 6px; height: 6px; border-radius: 50%; background: var(--accent);
    flex: 0 0 auto;
}
/* ============================================== THE STATUS STRIP =========== *
   SHORTER, AND IT EARNS THE ROOM. The head was .9rem of padding around a single
   sentence — 54px of console saying "playing your file". It is the one line
   that can report the whole desk at a glance, so it reports it: source, track,
   rate, what is drawing, how hard it is being driven, and what is queued
   behind. Each segment is a micro-label over a value, hairline-separated, and
   HIDES ITSELF when it has nothing to say — a strip padded out with dashes is
   worse than a short one.
   ========================================================================== */
/* .vz-sbar, NOT .vz-bar — .vz-bar is already the meter's 4px level bar in this
   same sheet, and reusing the name handed this strip `display: block` and a
   four-pixel height. The collision is silent: nothing errors, the bar just
   collapses and looks like a padding bug. */
/* SIZED BY ITS CONTENT, with no minimum. The tallest thing in it is a 20px
   segment, so .4rem of padding either side is a 36px strip against the 54px
   the head used to take for one sentence — and it now carries six readings
   instead. A min-height would only put the air back. */
/* A DEFINITE HEIGHT, because the content will not settle on one. The head took
   54px to say one sentence; this says six things in 36 and centres them, and a
   fixed height means adding a seventh reading later cannot quietly grow the
   strip back. On a phone it releases and wraps instead of clipping. */
.vz-sbar {
    padding: 0 1.2rem; height: 2.25rem; gap: 0 1rem;
    align-items: center; flex-wrap: nowrap; overflow: hidden;
}
.vz-sbar .console__title { margin-right: .2rem; }
.vz-sbar__seg {
    display: flex; align-items: baseline; gap: .4rem; min-width: 0;
    padding-left: 1rem; border-left: 1px solid var(--line);
}
.vz-sbar__seg[hidden] { display: none; }
.vz-sbar__seg i {
    font-style: normal; font-size: .54rem; font-weight: 700;
    letter-spacing: .14em; text-transform: uppercase; color: var(--muted);
    flex: 0 0 auto;
}
.vz-sbar__seg b {
    font-size: .76rem; font-weight: 500; color: var(--ink);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0;
}
/* the track name is the one thing that can be long, so it is the one thing
   allowed to take the slack and then ellipsis */
.vz-sbar__seg--grow { flex: 1 1 auto; min-width: 4rem; }
.vz-sbar__seg--next b { color: var(--accent); }
.vz-sbar .cw-stat { margin-left: auto; font-size: .7rem; flex: 0 0 auto; }
/* THE CARDS KEY: 24x20 to look at, 44x44 to hit (the ::after). It takes the strip's
   auto margin, so it sits just left of the status word; at <=620px the status word
   takes a row of its own and the key ends the row above it, adding no row. */
.vz-cards {
    position: relative; flex: 0 0 auto; margin-left: auto;
    width: 24px; height: 20px; padding: 0;
    display: inline-flex; align-items: center; justify-content: center;
    background: none; border: 1px solid transparent; border-radius: var(--cwd-r, 3px);
    color: var(--muted); cursor: pointer;
    touch-action: manipulation; -webkit-tap-highlight-color: transparent;
}
.vz-cards::after { content: ''; position: absolute; top: -12px; bottom: -12px; left: -10px; right: -10px; }
.vz-cards:hover { color: var(--ink); border-color: var(--line); }
.vz-cards:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.vz-cards[aria-pressed="true"] { color: var(--accent); }
.vz-cards[aria-pressed="true"] .vz-cards__x { display: none; }
.vz-sbar .vz-cards + .cw-stat { margin-left: .6rem; }
@media (max-width: 620px) { .vz-sbar .vz-cards + .cw-stat { margin-left: 0; } }
/* the dot is the transport: lit while something is actually running */
#vz-dot { background: var(--line); transition: background .2s ease, box-shadow .2s ease; }
#vz-dot[data-live] {
    background: var(--accent);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 22%, transparent);
}
@media (max-width: 900px) {
    .vz-sbar__seg--grow { flex-basis: 100%; padding-left: 0; border-left: 0; }
}
@media (max-width: 620px) {
    .vz-sbar { padding: .35rem .9rem; height: auto; flex-wrap: wrap; gap: .25rem .7rem; overflow: visible; }
    .vz-sbar__seg { padding-left: .7rem; }
    .vz-sbar .cw-stat { flex-basis: 100%; margin-left: 0; }
}

/* ONE TRACK THAT CAN SHRINK (483 review). An auto track grows to its widest
   child's min-content, so at 320 the deck's key row (repeat, dice and four
   hold chips) pushed the column 17.6px past the padding and the console clipped
   two dozen readouts. */
.console__body { --cb-pad: 1.2rem; padding: var(--cb-pad); display: grid; gap: 1.15rem; grid-template-columns: minmax(0, 1fr); }
@media (max-width: 620px) { .console__body { --cb-pad: .9rem; } }

/* -------------------------------------------------------------- the fields */
.field-row { display: flex; gap: .55rem; align-items: stretch; flex-wrap: wrap; }
.field-row > .field { flex: 1 1 15rem; min-width: 0; }
.field {
    font: inherit; font-size: .95rem; font-weight: 500;
    padding: .74rem .9rem; border-radius: 9px;
    color: var(--ink); background: var(--paper);
    /* A real border, not a hairline: an input is a box that has to announce
       itself, and --line at 12% on a white panel is an input you cannot find. */
    border: 1px solid rgba(30, 38, 43, .5);
    -webkit-appearance: none; appearance: none;
    transition: border-color .22s var(--ease), box-shadow .22s var(--ease);
}
.field::placeholder { color: var(--ink-55); font-weight: 400; }
.field:focus { outline: none; border-color: var(--ink); box-shadow: 0 0 0 3px rgba(30, 38, 43, .07); }
.field:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.field[aria-invalid="true"] { border-color: var(--taken); }

/* The pasted list. A block field rather than a row, because what goes in it is
   several lines and a single-line input that scrolls sideways hides everything
   but the last thing typed. Resizable vertically only: horizontal resize on a
   flex child drags the panel's whole layout with it. */
.field--area {
    display: block; width: 100%; resize: vertical; min-height: 5.5rem;
    line-height: 1.55; font-size: .9rem;
    /* A list of domains is columnar data and reads as such — and a monospace
       face is also what makes a stray character in a pasted line visible. */
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}
/* The button under a full-width field, pushed to the end of the row so it sits
   under the field's trailing edge rather than floating at its start. */
.field-row--end { justify-content: flex-end; margin-top: .55rem; }
/* No background fill here on purpose: the only surface tokens are --paper and
   --deep, and a hint sits on either depending on the band it is in, so a filled
   chip would be right in one place and a visible patch in the other. A hairline
   box needs no ground and is correct in both editions. */
.hint code {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: .92em; padding: .05em .35em; border-radius: 4px;
    border: 1px solid var(--line); color: var(--ink-77);
}

.lbl {
    display: block; font-size: .625rem; font-weight: 700; letter-spacing: .18em;
    text-transform: uppercase; color: var(--ink-55); margin-bottom: .45rem;
}
.hint { font-size: .78rem; color: var(--ink-55); margin: .4rem 0 0; }

/* ------------------------------------------------------------- the segments
   Five TLD sets and two generators. The old build had these as ten buttons
   that turned solid orange when active; here the pressed state is the ink
   itself, which is the same shape the pill and the filters use. */
.seg { display: flex; flex-wrap: wrap; gap: .4rem; }
.seg__b {
    font: inherit; font-size: .78rem; font-weight: 600; letter-spacing: -.005em;
    padding: .5rem .85rem; border-radius: 999px;
    background: transparent; color: var(--ink-77);
    border: 1px solid var(--line); cursor: pointer;
    transition: border-color .2s var(--ease), background .2s var(--ease), color .2s var(--ease);
}
.seg__b:hover { border-color: var(--ink); color: var(--ink); }
.seg__b[aria-pressed="true"] { background: var(--ink); border-color: var(--ink); color: var(--paper); }
.seg__b small { font-weight: 500; opacity: .62; margin-left: .35rem; }

.seg--wide { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: .4rem; }
.seg--wide .seg__b { text-align: center; border-radius: 9px; }
@media (max-width: 560px) { .seg--wide { grid-template-columns: 1fr; } }

/* --------------------------------------------------------------- the dials */
.dials { display: grid; grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr)); gap: 1.1rem; }
.dial[hidden] { display: none; }
.dial__top { display: flex; align-items: baseline; justify-content: space-between; gap: .6rem; }
.dial__val {
    font-size: .78rem; font-weight: 700; color: var(--accent);
    font-variant-numeric: tabular-nums;
}
input[type="range"] {
    width: 100%; margin: .35rem 0 0;
    accent-color: var(--ink);
    /* Firefox draws the track at its own height and ignores accent-color for
       the groove on some builds; the height keeps the two engines within a
       pixel of each other. */
    height: 22px;
}
input[type="range"]:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; }

/* -------------------------------------------------------------- the switches
   Real checkboxes with a real label, laid out as a row of chips. The old build
   had four bare checkboxes and four labels floating in a flex row, one of
   which ("Mobile") set a body class that made a desktop render as a phone. */
.switches { display: flex; flex-wrap: wrap; gap: .45rem; }
.sw {
    display: inline-flex; align-items: center; gap: .5rem;
    padding: .45rem .8rem .45rem .6rem; border-radius: 999px;
    border: 1px solid var(--line); cursor: pointer;
    font-size: .78rem; font-weight: 600; color: var(--ink-77);
    transition: border-color .2s var(--ease), color .2s var(--ease);
    user-select: none;
}
.sw:hover { border-color: var(--ink); color: var(--ink); }
.sw input { width: 15px; height: 15px; margin: 0; accent-color: var(--ink); flex: 0 0 auto; }
.sw:has(input:checked) { border-color: var(--ink); color: var(--ink); }
.sw:has(input:focus-visible) { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ------------------------------------------------------------- the run row */
.run { display: flex; gap: .55rem; flex-wrap: wrap; align-items: center; }
.run .btn--go { flex: 1 1 16rem; font-size: .92rem; padding: .95rem 1.4rem; letter-spacing: .06em; text-transform: uppercase; }
.run__kbd { margin-left: auto; font-size: .72rem; color: var(--ink-55); }
kbd {
    font: inherit; font-size: .72rem; font-weight: 600;
    padding: .1rem .35rem; border: 1px solid var(--line); border-radius: 5px;
    background: var(--paper); color: var(--ink-77);
}

/* =============================================================================
   RESULTS
   ========================================================================== */
.out {
    border-top: 1px solid var(--line);
    background: var(--paper);
    padding: 1.2rem;
    min-height: 22rem;
}
@media (max-width: 620px) { .out { padding: .9rem; } }

.out__bar {
    display: flex; align-items: center; flex-wrap: wrap; gap: .5rem;
    margin-bottom: .9rem;
}
.out__n {
    font-size: .625rem; font-weight: 700; letter-spacing: .18em;
    text-transform: uppercase; color: var(--ink-55); margin: 0 auto 0 0;
    font-variant-numeric: tabular-nums;
}
.out__n b { color: var(--ink); }

/* The welcome panel: what the tool is, before it has been asked to do
   anything. It is the only long-form copy inside the console. */
.welcome { max-width: 60ch; }
.welcome h3 { font-size: 1.28rem; font-weight: 300; letter-spacing: -.03em; margin: 0 0 .8rem; }
.welcome p { margin: 0 0 .9rem; font-size: .92rem; }
.welcome ul { margin: 0 0 1rem; padding-left: 1.1rem; font-size: .92rem; }
.welcome li { margin-bottom: .35rem; }
.welcome li::marker { color: var(--accent); }
.welcome strong { color: var(--ink); font-weight: 600; }

/* -------------------------------------------------------------- a result row
   The domain is the largest type in the row and everything else is a label
   beside it. It was 1.28rem at 600 on a #1a1a1a slab with a 4px orange bar
   down the left; here the row is paper on paper, held by a hairline, and the
   status is what carries the colour. */
.dom {
    display: grid;
    grid-template-columns: 1fr auto;
    align-items: center; gap: .5rem 1rem;
    padding: .85rem 1rem;
    border: 1px solid var(--line); border-radius: 10px;
    background: var(--paper);
    transition: border-color .18s var(--ease);
}
.dom + .dom { margin-top: .5rem; }
.dom:hover { border-color: rgba(30, 38, 43, .28); }
.dom[data-state="free"]    { border-left: 3px solid var(--free); }
.dom[data-state="taken"]   { border-left: 3px solid var(--taken); }
.dom[data-state="unknown"] { border-left: 3px solid var(--unsure); }
.dom[data-state="norda"]   { border-left: 3px solid var(--line); }

.dom__name {
    font-size: 1.16rem; font-weight: 600; letter-spacing: -.025em;
    color: var(--ink); overflow-wrap: anywhere; min-width: 0;
    display: flex; align-items: baseline; gap: .5rem; flex-wrap: wrap;
}
.dom__tld { color: var(--ink-55); font-weight: 500; }
.dom__acts { display: flex; gap: .35rem; flex-wrap: wrap; justify-content: flex-end; }

/* On a phone the two-column row fails badly rather than gracefully: the acts
   column resolves to its max-content (~230px of chip and buttons), the 1fr
   name column is left ~20px, and overflow-wrap:anywhere — right for genuinely
   long names — obliges by setting the name vertically, two letters a line.
   One column: the name owns its row and the actions sit under it. */
@media (max-width: 560px) {
    .dom { grid-template-columns: 1fr; }
    .dom__acts { justify-content: flex-start; }
}
.dom__detail { grid-column: 1 / -1; }
.dom__detail:empty { display: none; }

/* The icon buttons on a row: WHOIS, star, copy. Square, hairline, a glyph
   each — at three per row and twenty-five rows, anything with a word in it
   made the column wider than the domains. */
.ib {
    width: 32px; height: 32px; padding: 0; flex: 0 0 auto;
    display: inline-flex; align-items: center; justify-content: center;
    background: transparent; border: 1px solid var(--line); border-radius: 8px;
    color: var(--ink-55); cursor: pointer;
    transition: border-color .2s var(--ease), color .2s var(--ease), background .2s var(--ease);
}
.ib:hover { border-color: var(--ink); color: var(--ink); }
.ib[disabled] { opacity: .4; cursor: default; }
.ib svg { width: 14px; height: 14px; display: block; }
.ib[aria-pressed="true"] { color: var(--accent); border-color: var(--accent); background: rgba(194, 65, 12, .06); }
/* The busy state. A spinner would be a fourth moving thing on a page that is
   already listing twenty-five rows; a pulse on the button that was pressed is
   enough and it stays where the eye already is. */
.ib[data-busy="1"] { animation: pulse 1.1s ease-in-out infinite; }
@keyframes pulse { 50% { opacity: .38; } }
@media (prefers-reduced-motion: reduce) { .ib[data-busy="1"] { animation: none; opacity: .55; } }

/* ------------------------------------------------------------- status chips
   Four states, and the fourth is the one the old build did not have. It
   reported AVAILABLE whenever a query failed — a timeout, a CORS refusal, a
   registry with no RDAP at all — which is the single most expensive thing a
   tool like this can get wrong, because the answer looks like the good news. */
.st {
    display: inline-flex; align-items: center; gap: .4rem;
    font-size: .625rem; font-weight: 700; letter-spacing: .16em; text-transform: uppercase;
    padding: .28rem .6rem; border-radius: 999px; white-space: nowrap;
    border: 1px solid currentColor;
}
.st::before { content: ''; width: 5px; height: 5px; border-radius: 50%; background: currentColor; }
.st--free    { color: var(--free);   background: var(--free-wash); }
.st--taken   { color: var(--taken);  background: var(--taken-wash); }
.st--unknown { color: var(--unsure); background: var(--unsure-wash); }
.st--norda   { color: var(--ink-55); background: transparent; }
.st--busy    { color: var(--ink-55); background: transparent; }
/* An ending that publishes no RDAP, where DNS says the name is not delegated.
   Deliberately NOT the green of --free: it is a narrowing, not a yes, and a row
   that looks identical to a confirmed availability is a row that will be read
   as one. The undetermined amber, filled, so it reads as a finding rather than
   as the blank that plain --norda is. */
.st--maybe   { color: var(--unsure); background: var(--unsure-wash); }

/* ---------------------------------------------------------- the detail slab
   What RDAP actually said. A definition list, because that is what it is. */
.rec {
    margin-top: .7rem; padding: .8rem .9rem;
    border: 1px solid var(--line); border-radius: 9px;
    background: rgba(30, 38, 43, .022);
    font-size: .84rem;
}
.rec dl { display: grid; grid-template-columns: auto 1fr; gap: .2rem .9rem; margin: 0; }
.rec dt {
    font-size: .58rem; font-weight: 700; letter-spacing: .16em; text-transform: uppercase;
    color: var(--ink-55); padding-top: .22rem;
}
.rec dd { margin: 0; color: var(--ink-77); overflow-wrap: anywhere; font-variant-numeric: tabular-nums; }
.rec dd b { color: var(--ink); font-weight: 600; }
.rec__note { margin: .6rem 0 0; font-size: .78rem; color: var(--ink-55); }
@media (max-width: 480px) { .rec dl { grid-template-columns: 1fr; gap: 0 0; } .rec dt { padding-top: .5rem; } }

/* ------------------------------------------------------------- the buy row
   Five registrars. The old build gave each one its brand colour as a solid
   fill: #00cc66 with black type, #0033a0 with white, #f26522 with white at
   2.9:1. Five brand fills in a row is five accents on a page that has one, and
   half of them failed contrast in either edition.

   They are ghost buttons now, all in the ink, each carrying its brand as a
   6px dot. The colour still identifies the registrar; it is no longer trying
   to be a background for text. This is R7's "keep the raw value for fills" —
   the raw value survives, on the only surface small enough to hold it. */
.buys { display: flex; flex-wrap: wrap; gap: .4rem; margin-top: .75rem; }
.buy {
    display: inline-flex; align-items: center; gap: .45rem;
    font-size: .74rem; font-weight: 600; letter-spacing: -.005em;
    padding: .45rem .75rem; border-radius: 8px;
    border: 1px solid var(--line); color: var(--ink-77);
    text-decoration: none; white-space: nowrap;
    transition: border-color .2s var(--ease), color .2s var(--ease);
}
.buy:hover { border-color: var(--ink); color: var(--ink); }
.buy__dot { width: 6px; height: 6px; border-radius: 50%; flex: 0 0 auto; }
.buy::after {
    content: '↗'; font-size: .68em; color: var(--ink-55); margin-left: .05rem;
}

/* ------------------------------------------------------------ social links */
.socials { display: flex; flex-wrap: wrap; gap: .4rem; margin-top: .6rem; }
.social {
    display: inline-flex; align-items: center; gap: .35rem;
    font-size: .74rem; font-weight: 600;
    padding: .38rem .7rem; border-radius: 8px;
    border: 1px solid var(--line); color: var(--info);
    text-decoration: none; white-space: nowrap;
}
.social:hover { border-color: currentColor; }

/* The announcement cards live in their own sheet — assets/css/toast.css, the
   LAB DMT system — because they are a component this page borrows rather than
   one it owns, and keeping them separate is what lets the next page take them
   without taking the console with them. The one rule this sheet has about them
   is where they sit in the stack: above the nav (60) and the menu (200). */

/* The TOAST:ON/OFF switch in the console head. The engine owns its label and
   its state; this is only what it looks like sitting in the bar. */
.tbtn {
    font: inherit; font-size: .58rem; font-weight: 700; letter-spacing: .14em;
    padding: .34rem .6rem; border-radius: 999px;
    background: transparent; color: var(--ink-55);
    border: 1px solid var(--line); cursor: pointer;
    transition: border-color .2s var(--ease), color .2s var(--ease);
}
.tbtn:hover { border-color: var(--ink); color: var(--ink); }
.tbtn.on { color: var(--accent); border-color: var(--accent); }

/* --------------------------------------------------------------- compact
   What replaces the old build's "Mobile" checkbox — which was on by default
   and made a 27-inch display render at phone padding, because it was a
   preference standing in for a media query.

   Width is the media query's job and it is done above. This is the other
   thing that checkbox was half-doing and never said: density. It is a real
   preference — some people want twenty-five rows on one screen — so it is
   off by default, remembered, and it changes nothing but spacing. */
.is-compact .out { padding: .7rem; }
.is-compact .dom { padding: .5rem .7rem; }
.is-compact .dom + .dom { margin-top: .3rem; }
.is-compact .dom__name { font-size: 1rem; }
.is-compact .console__body { gap: .8rem; padding: .9rem; }
.is-compact .rec { padding: .6rem .7rem; font-size: .8rem; }

/* =============================================================================
   THE REGISTER — the TLD sets, as a table of what is in each one
   ========================================================================== */
.reg { border-top: 1px solid var(--line); }
.reg__row {
    display: grid; grid-template-columns: 13rem 1fr auto;
    gap: .4rem 1.6rem; align-items: baseline;
    padding: 1.1rem 0; border-bottom: 1px solid var(--line);
}
@media (max-width: 760px) { .reg__row { grid-template-columns: 1fr; } }
.reg__k { font-size: 1.05rem; font-weight: 600; letter-spacing: -.025em; color: var(--ink); }
.reg__v { font-size: .88rem; color: var(--ink-55); overflow-wrap: anywhere; }
.reg__n {
    font-size: .625rem; font-weight: 700; letter-spacing: .18em; text-transform: uppercase;
    color: var(--ink-55); white-space: nowrap; font-variant-numeric: tabular-nums;
}

/* =============================================================================
   POSITIONS — the three-up grid used by the "what a check tells you" band
   ========================================================================== */
.three { display: grid; grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr)); gap: 2.4rem; }
.pos__n {
    font-size: .625rem; font-weight: 700; letter-spacing: .2em;
    color: var(--accent); margin-bottom: .8rem;
}
.pos h3 { font-size: 1.28rem; font-weight: 300; letter-spacing: -.03em; margin-bottom: .6rem; }
.pos p { margin: 0; font-size: .95rem; }

/* =============================================================================
   FOOTER
   ========================================================================== */
.foot { border-top: 1px solid var(--line-dark); background: var(--deep); color: rgba(255, 255, 255, .5); }
.foot__in { display: flex; flex-wrap: wrap; align-items: center; gap: 1rem 2rem; padding-block: 2.2rem; }
.foot__in p { margin: 0; font-size: .78rem; }
.foot__links { display: flex; flex-wrap: wrap; gap: 1.4rem; margin-left: auto; }
.foot__links a { font-size: .78rem; text-decoration: none; color: rgba(255, 255, 255, .5); }
.foot__links a:hover { color: #fff; }
/* A finger needs 44px; the footer's words are 20px tall with 18-22px between them. */
@media (pointer: coarse) {
    .foot__links a, .foot__legal { display: inline-flex; align-items: center; min-height: 44px; }
}

/* =============================================================================
   REVEAL
   -----------------------------------------------------------------------------
   Held to a short rise with no scale and no stagger beyond a beat.

   The hidden state is gated on html.js — the class the inline <head> latch
   sets before anything paints. Only a browser definitely running our script
   gets an element hidden pending that script's reveal; everyone else gets the
   content. Without this, the h1 and the whole console would be opacity:0 until
   the observer ran, so any throw before that point serves an empty page rather
   than an unanimated one.
   ========================================================================== */
html.js .rise { opacity: 0; transform: translateY(14px); transition: opacity .7s var(--ease), transform .7s var(--ease); }
html.js .rise.is-in { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) {
    html.js .rise { opacity: 1; transform: none; transition: none; }
    .btn, .dom, .seg__b, .ib, .buy { transition: none; }
}

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

/* Read, not seen. The pill's lit segment is a span rather than a link, and a
   span carrying only an aria-label is announced by nothing — so it carries its
   words as real text and hides them here instead. */
.vh {
    position: absolute; width: 1px; height: 1px; margin: -1px;
    padding: 0; border: 0; overflow: hidden; white-space: nowrap;
    clip: rect(0 0 0 0); clip-path: inset(50%);
}

/* The skip link. background:var(--ink) + color:#fff, which the dark edition
   has to answer or it is white on white — invisible to every check that only
   looks at what is on screen, and the very first thing a keyboard user
   reaches. */
.skip {
    position: absolute; left: -9999px; top: 0; z-index: 400;
    background: var(--ink); color: #fff; padding: .8rem 1.2rem; text-decoration: none;
}
.skip:focus { left: 0; }

/* Print. The console is a screen tool and its controls mean nothing on paper;
   what a printed page is for is the list of names and their status. */
@media print {
    .nav, .menu, .console__body, .out__bar .btn, .dom__acts, .foot, .hero__act, .toast { display: none !important; }
    body { font-size: 12px; }
    .band { padding-block: 1rem; }
    .dom { break-inside: avoid; border-left-width: 3px; }
}

/* =============================================================================
   NETWORK LABS — the instrument outputs
   -----------------------------------------------------------------------------
   Added for the.net.im/labluxa/. Everything below reads the same tokens the rest
   of the sheet does (--ink, --paper, --line, --free/--taken/--unsure), so the
   dark edition inherits it through dark.css with no second copy. The output
   panels are set in the mono face the rest of the console diagnostics use.
   ========================================================================== */
.probe-out, .short-out, .pw-out {
    margin-top: 1rem;
    border: 1px solid var(--line);
    border-radius: 10px;
    background: var(--paper);
    padding: 1rem 1.15rem;
}
.probe-out {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: .82rem;
    line-height: 1.65;
    max-height: 60vh;
    overflow-y: auto;
}
.probe-out > div { margin: .18rem 0; }
.probe-label {
    color: var(--ink-55);
    margin-top: 1rem !important;
    font-size: .74rem;
    letter-spacing: .01em;
}
.probe-note { color: var(--ink-55); font-style: italic; }
.probe-card {
    margin: .5rem 0 !important;
    padding: .7rem .85rem;
    background: var(--tint);
    border-left: 3px solid var(--line);
    border-radius: 8px;
}
.probe-card.good { border-left-color: var(--free); }
.probe-hop {
    padding: .35rem .6rem;
    background: var(--tint);
    border-left: 2px solid var(--line);
    border-radius: 6px;
}
.probe-continent {
    margin-top: 1.1rem !important;
    padding-bottom: .35rem;
    border-bottom: 1px solid var(--line);
    font-weight: 600;
    color: var(--ink);
}
.probe-site {
    display: flex;
    flex-wrap: wrap;
    gap: .5rem;
    align-items: baseline;
    padding: .2rem 0;
}
.probe-site b { font-weight: 600; }
.probe-ip { color: var(--ink-55); font-size: .74rem; }
.probe-site .good, .probe-site .bad { margin-left: auto; }
.probe-verdict, .probe-done {
    margin-top: 1.1rem !important;
    padding: .85rem 1rem;
    background: var(--tint);
    border-left: 3px solid var(--accent);
    border-radius: 8px;
}
.probe-rating { font-size: 1.15rem; font-weight: 700; display: inline-block; margin-top: .3rem; }

.good { color: var(--free); font-weight: 600; }
.warn { color: var(--unsure); font-weight: 600; }
.bad  { color: var(--taken); font-weight: 600; }

/* Shortener + password results share one card shape. */
.short-out__link, .pw-out__word {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 1.25rem;
    font-weight: 600;
    color: var(--ink);
    word-break: break-all;
    margin: 0 0 .8rem 0;
}
.pw-out__word { color: var(--accent); }
.short-out .field-row, .pw-out .field-row { margin-bottom: .6rem; }

@media (max-width: 560px) {
    .probe-out { font-size: .78rem; }
    .short-out__link, .pw-out__word { font-size: 1.05rem; }
}

/* The per-instrument output toolbar (Download PDF). Shown by app.js once a run
   has produced something worth exporting. */
.out-tools {
    display: flex;
    gap: .5rem;
    flex-wrap: wrap;
    margin-top: 1rem;
}

/* =============================================================================
   SEARCH — the box's own furniture
   ========================================================================== */
.sr-quick {
    margin: .4rem 0 .6rem;
    padding: .75rem .95rem;
    background: var(--tint);
    border-left: 3px solid var(--accent);
    border-radius: 8px;
    font-size: .92rem;
}
.sr-snip {
    flex-basis: 100%;
    font-size: .78rem; color: var(--ink-55); line-height: 1.5;
    font-family: var(--font, inherit);
}
.sr-img-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
    gap: .5rem;
    margin: .5rem 0;
}
.sr-img {
    display: block; aspect-ratio: 4 / 3; overflow: hidden;
    border: 1px solid var(--line); border-radius: 8px; background: var(--tint);
}
.sr-img img { width: 100%; height: 100%; object-fit: cover; display: block; }
.sr-img:hover { border-color: var(--accent); }
.sr-launch { display: flex; flex-wrap: wrap; gap: .45rem; }
.sr-history { display: flex; flex-wrap: wrap; gap: .4rem; }
.sr-chip { border-radius: 999px; }
.sr-chip--x { color: var(--ink-55); }
.probe-out .probe-site b a { color: var(--ink); text-decoration: none; }
.probe-out .probe-site b a:hover { text-decoration: underline; }
@media (max-width: 560px) {
    .sr-img-grid { grid-template-columns: repeat(auto-fill, minmax(96px, 1fr)); }
}

/* =============================================================================
   STOCKS — the tape's own furniture
   ========================================================================== */
.st-indices {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
    gap: .6rem;
}
.st-index {
    display: flex; flex-direction: column; gap: .15rem;
    padding: .7rem .9rem;
    border: 1px solid var(--line); border-radius: 10px;
    background: var(--paper);
}
.st-index__n {
    font-size: .62rem; font-weight: 700; letter-spacing: .16em;
    text-transform: uppercase; color: var(--ink-55);
}
.st-index b {
    font-size: 1.15rem; font-weight: 600; letter-spacing: -.02em;
    font-variant-numeric: tabular-nums;
}
.st-row { align-items: center; }
.st-px { margin-left: auto; font-variant-numeric: tabular-nums; text-align: right; }
.st-act { display: inline-flex; gap: .25rem; margin-left: .5rem; }
.st-sugg {
    font: inherit; font-size: .72rem; font-weight: 600;
    padding: .1rem .55rem; margin: 0 .12rem;
    border: 1px solid var(--line); border-radius: 999px;
    background: transparent; color: var(--ink); cursor: pointer;
}
.st-sugg:hover { border-color: var(--accent); }
.st-chart { margin-top: 1rem; }
.st-chart__title { margin: 0 0 .5rem; font-size: .95rem; }
.st-chart canvas {
    width: 100%; height: auto; display: block;
    border: 1px solid var(--line); border-radius: 10px;
    background: var(--paper);
}
@media (max-width: 560px) {
    .st-px { flex-basis: 100%; margin-left: 0; text-align: left; }
}

/* =============================================================================
   THE TAPE — three crawling strips under the hero
   -----------------------------------------------------------------------------
   The homepage's crawl strips, carrying prices instead of properties. Three
   rows rather than two — trending, most active, in the news — because each row
   is a different feed and mixing them would make the tape a list of symbols
   with no answer to "why is this one here".

   Which is also why each row keeps a LABEL at its head, outside the crawl. On
   the homepage every chip is the same kind of thing, so the strip needs no
   caption; here a reader who glances at the tape mid-crawl has to be able to
   tell an unfamiliar ticker that is trending from one that just reported. A
   caption riding inside the crawl would answer that question only while it
   happened to be on screen, so the tag is pinned and the rail scrolls past it.

   Speed is not set here. The script measures the laid-out run and writes
   animation-duration, so a row of six chips and a row of sixteen crawl at the
   same pixels per second — and the three speeds are deliberately unequal, so
   the rows never fall into step and read as one moving block.
   ========================================================================== */
.tape {
    border-top: 1px solid var(--line);
    border-bottom: 1px solid var(--line);
    background: var(--tint);
    overflow: hidden;
    padding: calc(.95rem - 3px) 0 .75rem;
}
.tape__row {
    display: flex; align-items: center; gap: .7rem;
    padding-left: var(--gutter);
}
.tape__row + .tape__row { margin-top: calc(.5rem - 6px); }

/* One column for three tags of different lengths, so the three rails start on
   the same vertical line — the tape reads as a block rather than as three
   independent strips that happen to be stacked. */
.tape__tag {
    flex: 0 0 auto; width: 6.9rem;
    font-size: .58rem; font-weight: 700; letter-spacing: .14em;
    text-transform: uppercase; color: var(--ink-55);
    white-space: nowrap;
}
.tape__tag::before {
    content: '\25C6'; color: var(--accent);
    font-size: .55rem; margin-right: .4rem; letter-spacing: 0;
}

.tape__rail {
    /* min-width: 0 is the whole reason this row cannot widen the document. A
       flex item's min-width is auto, which is its CONTENT width — and the
       content here is a nowrap run of sixteen chips. Without this the rail
       measures a couple of thousand pixels, the row grows to hold it, and the
       page picks up a horizontal scrollbar that drags every fixed element on
       it sideways. */
    flex: 1 1 auto; min-width: 0;
    display: flex; overflow: hidden;
    /* Three pixels of slack inside the clip: a chip works out to a fractional
       height and the clip would otherwise round down and shave its bottom row
       of pixels. */
    padding: 3px 0;
    -webkit-mask-image: linear-gradient(90deg, #000 0, #000 92%, transparent 100%);
            mask-image: linear-gradient(90deg, #000 0, #000 92%, transparent 100%);
}
/* The copies past the first, hidden from assistive technology. display:contents
   is what keeps them direct flex children of the track — a plain wrapper would
   collapse every copy into one flex item and the loop would jump. */
.tape__echo { display: contents; }

.tape__track {
    display: flex; align-items: center; flex: 0 0 auto;
    gap: .55rem;
    /* Matches `gap` exactly, or the track is one gap short of twice a run and
       the -50% keyframe lands half a gap off — a visible hitch once per pass. */
    padding-right: .55rem;
    white-space: nowrap;
    animation: tape-run 120s linear infinite;
    backface-visibility: hidden;
    /* Parked until the tape is on screen AND the row has real chips in it. A
       row still saying "reading the tape" has nothing to crawl. */
    animation-play-state: paused;
}
.tape.is-live .tape__row.is-ready .tape__track {
    animation-play-state: running; will-change: transform;
}
@keyframes tape-run {
    from { transform: translate3d(0, 0, 0); }
    to   { transform: translate3d(-50%, 0, 0); }
}

.t-chip {
    display: inline-flex; align-items: baseline; gap: .5rem;
    flex: 0 0 auto;
    padding: .34rem .8rem;
    border: 1px solid var(--line); border-radius: 999px;
    background: var(--paper);
    font: inherit; text-align: left; cursor: pointer;
    transition: border-color .22s var(--ease);
}
.t-chip:hover { border-color: var(--accent); }
.t-chip:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.t-sym { font-size: .78rem; font-weight: 700; letter-spacing: -.01em; color: var(--ink); }
.t-px  { font-size: .73rem; color: var(--ink-77); font-variant-numeric: tabular-nums; }
.t-mv  { font-size: .69rem; font-weight: 600; font-variant-numeric: tabular-nums; }
/* The two states that are not a quote. Neither is a button — there is nothing
   to press — so neither gets the hover or the pointer. */
.t-chip--wait, .t-chip--none {
    cursor: default; font-size: .73rem; color: var(--ink-55);
    background: transparent; border-style: dashed;
}
.t-chip--none { color: var(--taken); border-color: var(--line); }
.t-chip--wait:hover, .t-chip--none:hover { border-color: var(--line); }

.tape__foot {
    display: flex; align-items: center; gap: .6rem; flex-wrap: wrap;
    padding: .55rem var(--gutter) 0;
    font-size: .66rem; color: var(--ink-55);
}
.tape__re {
    font: inherit; font-size: .6rem; font-weight: 700; letter-spacing: .12em;
    text-transform: uppercase;
    padding: .16rem .6rem;
    border: 1px solid var(--line); border-radius: 999px;
    background: transparent; color: var(--ink-55); cursor: pointer;
}
.tape__re:hover { border-color: var(--ink); color: var(--ink); }

/* A visitor who has asked for less motion gets the same chips, parked — and
   the rail becomes scrollable so the ones past the fold are still reachable.
   The mask goes with the crawl: a fade over a strip you scroll by hand reads
   as a rendering fault rather than as an edge. */
@media (prefers-reduced-motion: reduce) {
    .tape__track { animation: none; }
    .tape__rail {
        overflow-x: auto; scrollbar-width: thin;
        -webkit-mask-image: none; mask-image: none;
    }
}

@media (max-width: 640px) {
    .tape { padding: calc(.7rem - 3px) 0 .6rem; }
    .tape__row { padding-left: 14px; gap: .5rem; }
    .tape__tag { width: 5.2rem; font-size: .5rem; letter-spacing: .08em; }
    .tape__tag::before { font-size: .48rem; margin-right: .3rem; }
    .tape__foot { padding-left: 14px; padding-right: 14px; }
    .t-chip { gap: .4rem; padding: .3rem .62rem; }
    .t-sym { font-size: .72rem; } .t-px { font-size: .68rem; } .t-mv { font-size: .64rem; }
}

/* ------------------------------------------------------------- a row that acts
   .menu__row was written for <a>. A <button> wearing the same class arrives
   with the user agent's own 2px outset border, its buttonface background, its
   13px Arial and — because .menu__row is a flex container — a width that
   shrinks to its own text. Eight such rows of differing lengths read as a
   staircase of framed grey boxes beside a clean list of links, which is exactly
   what they were.

   Everything here is a reset, not a style. The point is that a button in this
   menu is indistinguishable from a link in it. The bottom rule is re-declared
   because `border: 0` above would take it with the rest, and the existing
   `.menu__row:last-child` still wins on specificity, so the last row of a
   section stays clean. */
button.menu__row {
    width: 100%;
    box-sizing: border-box;
    -webkit-appearance: none;
    appearance: none;
    background: none;
    border: 0;
    border-bottom: 1px solid var(--line-soft);
    border-radius: 0;
    font: inherit;
    text-align: left;
    cursor: pointer;
}

/* =========================================================== CLOCKWORK ====
   The observatory's own furniture. Everything reads the tokens, so the dark
   edition answers only the sky washes and the four canvas inks — all of them
   tokens of this section, declared here and answered in dark.css. The planet
   colours are identity colours and live in app.js, literal in both editions,
   the way the subway bullets do on Transit. */
:root {
    /* the sky over a city, as a wash on its clock card */
    --cw-day:   rgba(251, 191, 36, .13);
    --cw-civil: rgba(194, 65, 12, .09);
    --cw-night: rgba(29, 78, 216, .08);
    /* canvas inks the JS reads at draw time */
    --cw-sun:      #B45309;   /* the sun's disc, the day arc, the orrery's sun */
    --cw-twi:      #1D4ED8;   /* the twilight wings on the arc                 */
    --cw-moon-lit: #F3EEE2;   /* the moon's lit portion                        */
    --cw-moon-dk:  #232B33;   /* …and its shadowed one                         */
}

.cw-stat { margin-left: auto; font-size: .78rem; letter-spacing: .04em; color: var(--muted);
           font-variant-numeric: tabular-nums; }
.cw-stat--live { color: var(--free); }

/* ------------------------------------------------------- the city finder */
.cw-hits { margin-top: .45rem; border: 1px solid var(--line); border-radius: 10px;
           overflow: hidden; background: var(--paper); }
.cw-hit { display: flex; align-items: center; gap: .7rem; width: 100%; text-align: left;
          padding: .55rem .8rem; border: 0; border-bottom: 1px solid var(--line-soft);
          background: none; color: var(--ink-77); font: inherit; cursor: pointer;
          transition: background .15s var(--ease), color .15s var(--ease); }
.cw-hit:last-child { border-bottom: 0; }
.cw-hit:hover { background: var(--tint); color: var(--ink); }
.cw-hit__name { font-weight: 600; }
.cw-hit__zone { margin-left: auto; font-size: .74rem; color: var(--muted);
                white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }

/* -------------------------------------------------------- the clock board */
.cw-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(158px, 1fr));
           gap: .9rem; }
.cw-card { border: 1px solid var(--line-soft); border-radius: 12px;
           padding: .8rem .7rem .6rem; background: var(--paper);
           display: flex; flex-direction: column; align-items: center; gap: .12rem;
           min-width: 0; }
/* EVERY CARD IS THE EDITION IT IS BEING READ IN. Each one used to be filled
   with the colour of its own sky, so a board of sixteen cities was sixteen
   different grounds — three pale tints fighting each other and the page, and
   on the dark edition a row of pale boxes that had nothing to do with the
   edition at all.

   The sky is still measured and still shown: it is the word under the time,
   and a hairline along the top of the card in that sky's colour. The reading
   is kept; the wallpaper is not. */
.cw-card[data-sky] { background: var(--paper); position: relative; overflow: hidden; }
.cw-card[data-sky]::before {
    content: ''; position: absolute; inset: 0 0 auto 0; height: 2px;
    background: var(--line);
}
.cw-card[data-sky="day"]::before   { background: var(--cw-sun, #B45309); }
.cw-card[data-sky="civil"]::before { background: var(--cw-twi, #1D4ED8); }
.cw-card[data-sky="night"]::before { background: var(--ink); opacity: .38; }

/* One clock, on its own, in its own window. */
/* ALWAYS THERE, NEVER LOUD.
   It was opacity:0 until the card was hovered, which meant that on a phone —
   where there is no hover — every clock's pop-out was invisible and
   unreachable, and on a desktop nobody knew it existed until their mouse
   happened to cross a card. A control that only appears once you have already
   found it is not a control.

   So it sits at a quiet third and comes up to full on hover or focus. On a
   coarse pointer it is full always, because that device will never hover. */
.cw-card__pop {
    position: absolute; top: 6px; right: 6px;
    width: 1.35rem; height: 1.35rem; line-height: 1;
    display: inline-flex; align-items: center; justify-content: center;
    font: inherit; font-size: .62rem;
    border-radius: 50%; border: 1px solid var(--line);
    background: var(--paper); color: var(--ink-55, var(--muted));
    cursor: pointer; opacity: .38; transition: opacity .16s ease, border-color .16s ease;
}
.cw-card:hover .cw-card__pop,
.cw-card__pop:hover,
.cw-card__pop:focus-visible { opacity: 1; border-color: var(--accent); color: var(--accent); }
@media (pointer: coarse) {
    /* NO HOVER ON THIS DEVICE, EVER — and a bigger target, because 1.35rem is
       under the forty-four pixels a finger needs. */
    .cw-card__pop { opacity: 1; width: 1.9rem; height: 1.9rem; font-size: .72rem; }
}
@media (prefers-reduced-motion: reduce) { .cw-card__pop { transition: none; } }
html[data-cw-pop] .cw-card__pop { display: none; }
.cw-card__cv { width: 110px; height: 110px; }
.cw-card__name { margin: .3rem 0 0; max-width: 100%; overflow: hidden; text-overflow: ellipsis;
                 white-space: nowrap; font-weight: 600; font-size: .88rem; color: var(--ink); }
.cw-card__time { margin: 0; font-variant-numeric: tabular-nums; font-weight: 700;
                 font-size: 1.04rem; letter-spacing: .02em; color: var(--ink); }
.cw-card__sub { margin: 0; max-width: 100%; overflow: hidden; text-overflow: ellipsis;
                white-space: nowrap; font-size: .68rem; color: var(--muted); }
.cw-card__sky { margin: 0; font-size: .6rem; font-weight: 700; letter-spacing: .16em;
                text-transform: uppercase; color: var(--ink-55); }
.cw-card__ops { display: flex; gap: .3rem; margin-top: .4rem; }
.cw-op { -webkit-appearance: none; appearance: none; border: 1px solid var(--line);
         background: none; color: var(--ink-55); border-radius: 7px;
         width: 27px; height: 22px; padding: 0; font: inherit; font-size: .68rem;
         line-height: 1; cursor: pointer;
         transition: color .15s var(--ease), border-color .15s var(--ease); }
.cw-op:hover { border-color: var(--ink); color: var(--ink); }

/* ------------------------------------------------------------ the sky desk */
select.field { max-width: 24rem; }
.cw-arc { display: block; width: 100%; height: clamp(170px, 32vw, 240px); }
.cw-figs { display: flex; gap: 1rem 1.6rem; flex-wrap: wrap;
           border-top: 1px solid var(--line-soft); padding-top: 1rem; }
.cw-fig { display: flex; flex-direction: column; gap: .12rem; }
.cw-fig__l { font-size: .64rem; font-weight: 700; letter-spacing: .13em;
             text-transform: uppercase; color: var(--muted); }
.cw-fig b { font-variant-numeric: tabular-nums; font-weight: 700; font-size: 1rem;
            color: var(--ink); white-space: nowrap; }
.cw-moonrow { display: flex; gap: 1.3rem; align-items: center; flex-wrap: wrap;
              border-top: 1px solid var(--line-soft); padding-top: 1.1rem; }
.cw-moon { width: 110px; height: 110px; flex: 0 0 auto; }
.cw-moonrow__t { flex: 1 1 16rem; min-width: 0; }
.cw-phase { margin: 0 0 .2rem; font-size: 1.08rem; font-weight: 700;
            letter-spacing: -.01em; color: var(--ink); }

/* -------------------------------------------------------------- the orrery */
.cw-orr { display: block; width: 100%; height: clamp(340px, 58vw, 540px); }
.cw-scrubrow { display: flex; gap: .8rem; align-items: center; flex-wrap: wrap; }
.cw-scrub { flex: 1 1 14rem; min-width: 0; height: 28px; margin: 0;
            accent-color: var(--accent); background: transparent; }
.cw-date { font-variant-numeric: tabular-nums; font-weight: 700; font-size: .95rem;
           color: var(--ink); white-space: nowrap; }

@media (max-width: 640px) {
    .cw-grid { grid-template-columns: repeat(auto-fill, minmax(138px, 1fr)); gap: .7rem; }
    .cw-hit__zone { display: none; }
    .cw-figs { gap: .8rem 1.1rem; }
}

/* Same argument as .menu__q above: under 16px, iOS Safari zooms the focused
   field and carries the layout off-screen. Matched on the pointer, not the
   width, because a narrow desktop window does not zoom. */
@media (hover: none) and (pointer: coarse) {
    .field { font-size: 16px; }
}

/* --------------------------------------------------------------- the dial */
.cw-face { margin-top: .9rem; }
.cwd__say2 b { color: var(--ink); font-weight: 600; }

/* --------------------------------------------------------- the popped board
   Same page, one attribute. Everything that frames the board on the site is
   furniture in a window opened to watch the clocks, so it goes — and the
   board itself is given the room. */
html[data-cw-pop] .nav,
html[data-cw-pop] .foot,
html[data-cw-pop] footer,
html[data-cw-pop] .band:not(:has(.cw-grid)),
html[data-cw-pop] .cw-face,
/* THE WHOLE DESK GOES. It is the controls for a board, and a popped window is
   a VIEW of a board — it has no use for a search field, and the panel rail
   would have written to the SAME localStorage as the board behind it, which is
   the bug popMode() spends a paragraph on. Naming the container rather than
   each control is also why this rule stopped rotting: the last version still
   named .cw-bar three renames later, so the popped window went on carrying a
   search box it had no use for. */
html[data-cw-pop] .cwd,
html[data-cw-pop] .cw-hits,
html[data-cw-pop] .console__head,
html[data-cw-pop] .hint { display: none !important; }
html[data-cw-pop] body { padding: 0; }
/* Nothing in a popped window scrolls: it holds one screenful by construction,
   so a scrollbar there is a rounding error made visible. */
html[data-cw-pop], html[data-cw-pop] body { overflow: hidden; height: 100%; }
/* THE BAND THAT HOLDS THE GRID KEEPS ITS HEADING, and the heading is 241px of
   a 760px window — measured, it pushed the last row of clocks clean off the
   bottom. A popped window was asked for clocks. Everything in that band which
   is not the grid goes, and the band, its wrap and its console stop taking
   room of their own. */
html[data-cw-pop] .band:has(.cw-grid) > .wrap > .eyebrow,
html[data-cw-pop] .band:has(.cw-grid) > .wrap > .d2,
html[data-cw-pop] .band:has(.cw-grid) > .wrap > .lede { display: none !important; }
html[data-cw-pop] .band:has(.cw-grid) { padding: 0; }
html[data-cw-pop] .band:has(.cw-grid) > .wrap { max-width: none; padding: 0; }
html[data-cw-pop] .console { border: 0; box-shadow: none; background: none; }
html[data-cw-pop] .console__body { padding: 0; display: block; }
/* A single popped clock is given the window rather than sitting in the corner
   of a grid built for sixteen. */
/* One clock: the whole window, less a margin, and square. */
html[data-cw-only] .cw-grid { display: flex; align-items: center; justify-content: center; }
html[data-cw-only] .cw-card { margin: 0; width: min(88vmin, 100%); }
html[data-cw-only] .cw-card__cv { width: 100%; height: auto; aspect-ratio: 1 / 1; }
html[data-cw-only] .cw-card[hidden] { display: none !important; }

/* ------------------------------------------------------------- the lights
   The board's small controls — the two arrows that walk a clock along, the
   cross that retires it, and the pop-out — are 20-odd pixels of outline
   each. At that size a flat border-colour swap on hover is almost invisible,
   which is why they read as decoration rather than as controls.

   Three things, and none of them is a colour change on its own:

     LIFT      a pixel of travel, so the control moves under the cursor
     GLOW      a soft ring in the accent, sized in ems so it scales with the
               control rather than being a fixed halo around a shrinking dot
     FILL      the ink ground on press, which is the same language the rest of
               the estate uses for "this is happening now"

   The press state is separate from hover on purpose: a control that only
   lights on hover has nothing to say on a touch screen, where there is no
   hover at all and :active is the whole vocabulary. */
.cw-card__pop,
.cw-op {
    transition: transform .14s var(--ease, ease),
                box-shadow .18s var(--ease, ease),
                background .18s var(--ease, ease),
                border-color .18s var(--ease, ease),
                color .18s var(--ease, ease);
}
.cw-card__pop:hover,
.cw-op:hover {
    transform: translateY(-1px);
    border-color: var(--accent);
    color: var(--accent);
    box-shadow: 0 0 0 .18em color-mix(in srgb, var(--accent) 16%, transparent);
}
.cw-card__pop:active,
.cw-op:active {
    transform: translateY(0);
    background: var(--ink); border-color: var(--ink); color: var(--paper);
    box-shadow: 0 0 0 .1em color-mix(in srgb, var(--ink) 14%, transparent);
}
.cw-card__pop:focus-visible,
.cw-op:focus-visible {
    outline: 2px solid var(--accent); outline-offset: 2px;
}
/* A control that lifts under a cursor should not lift under a finger that is
   only scrolling past it. */
@media (hover: none) {
    .cw-card__pop, .cw-op { opacity: 1; }
    .cw-card__pop:hover, .cw-op:hover {
        transform: none; box-shadow: none;
    }
}
@media (prefers-reduced-motion: reduce) {
    .cw-card__pop, .cw-op { transition: none; }
    .cw-card__pop:hover, .cw-op:hover { transform: none; }
}

/* --------------------------------------------------------------- the bar
   Three controls on one line. The search takes whatever is left after the dial
   and the two buttons have what they need — named minmax(0, 1fr) rather than
   1fr, because a 1fr track grows to its content's min-content width and a long
   placeholder would set the width of the whole row.

   Below 640 the row becomes two: the search across the top, the dial and the
   buttons under it. Not a stack of four — the point of the bar is that the
   controls are together. */
html[data-cw-pop] .cw-card[data-sky]::before { display: none; }
html[data-cw-pop] .cw-card__cv {
    width: var(--cw-dial, 100%); height: auto; aspect-ratio: 1 / 1; max-width: 100%;
}
/* THE TYPE UNDER A POPPED DIAL IS SET IN PIXELS BY popFit().
   It used to be a clamp() here and an unrelated clamp() in the fit, and the
   two disagreed by about six pixels a row — which is nothing on the first row
   and two dozen by the fourth, where the city name ended up under the edge of
   a window with overflow: hidden over it. One set of numbers now: popFit
   writes --cw-name, --cw-time and --cw-lgap, and the line-heights below are
   the ones its arithmetic assumes. Do not put a clamp() back. */
html[data-cw-pop] .cw-card__name {
    margin: var(--cw-lgap, .3rem) 0 0;
    font-size: var(--cw-name, .8rem); line-height: 1.3;
    font-weight: 600; letter-spacing: .01em;
}
html[data-cw-pop] .cw-card__time {
    margin: 0; font-size: var(--cw-time, .92rem); line-height: 1.32;
    font-variant-numeric: tabular-nums; font-weight: 700;
}
html[data-cw-pop] .cw-grid {
    /* Its own 14px top margin, plus a 100vh height, is 100vh + 14 — which is a
       scrollbar on a window that was sized to hold exactly what is in it. */
    margin: 0;
    display: grid;
    gap: var(--cw-gap, 1rem);
    grid-template-columns: repeat(var(--cw-cols, 4), minmax(0, 1fr));
    align-content: center; justify-content: center;
    padding: var(--cw-pad, 1rem);
    height: 100vh; box-sizing: border-box;
    overflow: hidden;
}

/* ------------------------------------------------------------ the crosshair
   One control in the whole window, and it does the one thing worth doing to a
   wall of clocks: deals a different dial.

   Bottom left, because the eye reads a grid from the top left and a control
   parked there would be the first thing it meets. It sits at a quarter
   opacity until the pointer is anywhere in the window, so it is discoverable
   without being present — and it goes to full opacity on its own hover and on
   keyboard focus, which is the only state a keyboard user has.

   The crosshair is drawn, not a glyph: at 18 pixels a font's ✛ lands wherever
   its metrics put it, and this has to sit on its own centre. */
/* THE WHEEL THAT FLOATS. Bottom-left, on the board and in every popped
   window. It was a crosshair, and a crosshair is a picture of aim rather than
   of chance.

   IT IS NOT HIDDEN UNTIL HOVER. The old one sat at opacity .28 and only came
   up under a mouse, which on a touch screen means invisible — there is no
   hover on a phone, so a control that appears on hover is a control that does
   not exist there. It is quiet at rest, full under a pointer, and simply full
   size on a coarse pointer. */
.cw-shuffle {
    position: fixed; left: 1rem; bottom: 1rem; z-index: 40;
    width: 3rem; height: 3rem; padding: 0;
    display: inline-flex; align-items: center; justify-content: center;
    border-radius: 50%; cursor: pointer;
    border: 1px solid var(--line);
    background: var(--paper);
    opacity: .62;
    box-shadow: 0 .12rem .5rem rgba(0, 0, 0, .10);
    transition: opacity .2s ease, transform .16s ease,
                box-shadow .2s ease, border-color .2s ease;
}
.cw-shuffle__cv { display: block; width: 2.5rem; height: 2.5rem; }
.cw-shuffle:hover {
    opacity: 1;
    transform: translateY(-1px);
    border-color: var(--accent);
    box-shadow: 0 .2rem .8rem color-mix(in srgb, var(--accent) 20%, transparent);
}
.cw-shuffle:active { transform: translateY(0) scale(.95); }
.cw-shuffle:focus-visible {
    opacity: 1;
    outline: 2px solid var(--accent); outline-offset: 3px;
}
@media (pointer: coarse) {
    .cw-shuffle { width: 3.4rem; height: 3.4rem; opacity: .92; }
    .cw-shuffle__cv { width: 2.9rem; height: 2.9rem; }
}
/* The name of the dial, said once as it changes and then gone. */
.cw-shuffle__say {
    position: fixed; left: 4.2rem; bottom: 1.55rem; z-index: 40;
    font-size: .72rem; font-weight: 700; letter-spacing: .08em;
    text-transform: uppercase; color: var(--ink); opacity: 0;
    pointer-events: none; transition: opacity .3s ease;
}
.cw-shuffle__say.is-on { opacity: .68; }
@media (prefers-reduced-motion: reduce) {
    .cw-shuffle, .cw-shuffle:hover, .cw-shuffle:active { transition: none; transform: none; }
}


/* ------------------------------------------------------- the shelf as a door
   Every drawer shelf is a room now, and the room is a page. The heading is the
   link to it: same size, same tracking, same colour as a plain heading, with an
   arrow that only moves on hover — a heading that shouted would compete with
   the tiles under it, which are the thing somebody came to the drawer for. */
.menu__h--door { padding: 0; }
.menu__h--door a {
    display: inline-flex; align-items: center; gap: .4em;
    color: inherit; text-decoration: none;
}
.menu__h--door a i { font-style: normal; opacity: 0; transform: translateX(-3px); }
.menu__h--door a:hover, .menu__h--door a:focus-visible { color: var(--accent); }
.menu__h--door a:hover i, .menu__h--door a:focus-visible i {
    opacity: 1; transform: none;
}
.menu__h--door a i { transition: opacity .16s ease, transform .16s ease; }
.menu__h--door a:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 4px; }
@media (prefers-reduced-motion: reduce) {
    .menu__h--door a i { transition: none; }
}

/* ------------------------------------------------------------- the panels
   Ten slots, a name and two buttons. The row wraps rather than stacking, the
   same way the bar above it does. */



/* ==========================================================================
   THE PANEL WHEEL, THE SINGLE POP-OUT ROW, AND FOCUS
   ==========================================================================
   THE WHEEL'S OWN COLOURS ARE TOKENS, not literals in the canvas code. It is
   drawn from getComputedStyle, so dark.css can re-ink it the way it re-inks
   everything else — and a wheel painted with --ink would come out white on the
   dark edition, because --ink is the TEXT colour there. Mahogany is mahogany
   in both editions; only its brightness moves. */
:root {
    --cw-wheel-rim:   #3B2B1F;
    --cw-wheel-track: #241A12;
    --cw-wheel-cone:  #E9E3D7;
    --cw-wheel-red:   #A82219;
    --cw-wheel-black: #16191D;
    --cw-wheel-green: #1B7442;
    --cw-wheel-brass: #B08D46;
    --cw-wheel-ball:  #FAF7F0;
}

/* The wheel, at a size that can print its own numbers, beside what it does. */
.cw-wheel canvas { display: block; width: 100%; height: auto; aspect-ratio: 1 / 1; }
.cw-wheel:hover { transform: scale(1.03); }
.cw-wheel:active { transform: scale(.98); }
.cw-wheel:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; }

/* One clock, on its own. */

/* FOCUS — the dial and nothing else.
   The city and the time are drawn ON the canvas in this mode (see drawFace),
   so nothing is lost by taking the type off the card: it has moved onto the
   clock, which is where a clock has always carried it. */
html[data-cw-focus] .cw-card__ops,
html[data-cw-focus] .cw-card__pop,
html[data-cw-focus] .cw-card__name,
html[data-cw-focus] .cw-card__time,
html[data-cw-focus] .cw-card__sub,
html[data-cw-focus] .cw-card__sky { display: none !important; }
html[data-cw-focus] .cw-card {
    border: 0; box-shadow: none; background: none; padding: 0;
}
html[data-cw-focus] .cw-card[data-sky]::before { display: none; }
html[data-cw-focus] .cw-card__cv { width: 100%; height: auto; aspect-ratio: 1 / 1; }
/* THE LATCH IS THE DESK'S, NOT AN ID'S.
   There was a second rule here — #cw-focus[aria-pressed="true"], an 8% accent
   wash with accent text — left over from before the desk existed. At (1,1,0)
   it beat .cwd__b[aria-pressed="true"] at (0,2,0), so the one control that is
   supposed to carry the panel's single orange rendered as a 2%-alpha ghost and
   nothing said so. Two rules for one job, and the id won because it was an id.
   The desk owns its own states; see .cwd__b[aria-pressed="true"] above. */

/* THE POPPED BOARD ON A PHONE. popFit() solves the columns from the real
   width, but 100vh on iOS Safari is TALLER than the visible page — the
   browser's own chrome sits over the bottom of it, which is exactly where the
   last row of city names is. dvh is the visible height, and the visible height
   is what the fit's arithmetic is about. */
@supports (height: 100dvh) {
    html[data-cw-pop], html[data-cw-pop] body { height: 100dvh; }
}


/* ==========================================================================
   THE DESK — the board's controls, as one instrument panel
   ==========================================================================
   A Rams/Braun panel, and what that actually buys is arithmetic rather than
   atmosphere. What was here before:

       five control heights   (input 50, select 50, button 46, slot 30, name 50)
       four radii             (9px field, 8px button, 999px slot, 50% wheel)
       three type sizes for what is one job (.95rem, .82rem, .68rem)
       three column widths in a four-column grid
       a 152px wheel with a paragraph beside it
       seven bands stacked down the page, none aligned with any other

   What is here now:

       ONE height             --cwd-h, everywhere, including the slots
       ONE radius             --cwd-r = 3px, everywhere but the wheel
       ONE label recipe       10px / 600 / .14em / upper / --muted
       ONE control recipe     13px / 500 / --ink
       FOUR equal columns     and four equal keys under them
       an 84px wheel

   NO .field, NO .btn. Both carry html[data-dark] rules in the estate's shared
   dark.css, which is linked AFTER this sheet and therefore wins every tie at
   equal weight — a class this sheet owns and that sheet has never heard of
   needs no #main prefix and no dark twin. The edition difference lives in the
   token VALUES (--ink, --paper, --line, --muted, --accent), which dark.css
   repoints for the whole page at once. That is the laband pattern; the
   alternative is the composer one, and composer needed a paragraph of cascade
   notes to make it hold.

   THE HAIRLINE IS NOT --line. Measured, --line is rgba(30,38,43,.12), which
   resolves to #E4E5E6 on paper — 1.26:1. Faint AND load-bearing is the worst
   pair a border can be: it is what made the old fields need a 50%-ink border
   to be findable at all, which is why they read as heavy. 34% ink mixed with
   TRANSPARENT (never with --paper: a .band--deep section repoints --ink and
   leaves --paper alone, and a two-token mix goes white-on-white there) is
   2.4:1 — a line you can see and not a box you have to look past.
   ========================================================================== */
.cwd {
    --cwd-h:    2.125rem;                       /* 34px: every control, no exceptions */
    --cwd-r:    3px;
    --cwd-gap:  .4rem;
    --cwd-edge: color-mix(in srgb, var(--ink) 34%, transparent);
    --cwd-rule: color-mix(in srgb, var(--ink) 13%, transparent);
    display: grid; gap: .7rem;
    margin: 0 0 1rem;
}

/* THE LABEL. One recipe, used above a field, at the head of a rail and inside
   the readout — the same job in all three places, so the same type. */
.cwd__l {
    display: block;
    font-size: .625rem; font-weight: 600; line-height: 1.2;
    letter-spacing: .14em; text-transform: uppercase;
    color: var(--muted);
    white-space: nowrap;
}

/* FOUR EQUAL COLUMNS. minmax(0, 1fr) rather than 1fr, or a 32-city select
   whose longest option is "Dar es Salaam · Tanzania" sets the width of the
   other three tracks — a 1fr track grows to min-content and the whole desk
   goes asymmetric behind one long word. */
/* FIVE ACROSS, AND FIVE ACROSS UNDER IT. The desk went from four columns to
   five when the board's size became a control rather than a constant; the rule
   that matters is not the number but that BOTH rows carry the same one, so the
   fields and the keys keep sharing a grid. */
.cwd__fields {
    display: grid; gap: var(--cwd-gap);
    grid-template-columns: repeat(5, minmax(0, 1fr));
}
.cwd__f { display: grid; gap: .28rem; min-width: 0; }

.cwd__i {
    font: inherit; font-size: .8125rem; font-weight: 500;
    /* THE FALLBACK IS NOT DECORATION. --cwd-h is declared on .cwd, so a page
       that uses these controls WITHOUT a .cwd container resolves this to
       nothing — and an invalid var() at computed-value time does not fall back
       to the previous declaration, it falls back to `auto`. The two rules
       beside this one already carry the fallback; this one did not, so on such
       a page every input and select shipped 23px tall next to 34px keys and the
       row grew eleven pixels of dead air under them. That gap is what a reader
       sees and reports, and nothing about it points at a missing fallback. */
    height: var(--cwd-h, 2.125rem); line-height: normal;
    padding: 0 .55rem;
    box-sizing: border-box; width: 100%; min-width: 0;
    color: var(--ink); background: var(--paper);
    border: 1px solid var(--cwd-edge, color-mix(in srgb, var(--ink) 34%, transparent));
    border-radius: var(--cwd-r, 3px);
    -webkit-appearance: none; appearance: none;
    transition: border-color .18s ease, box-shadow .18s ease;
}
/* A select needs its own arrow once appearance is none, and it has to be drawn
   rather than fetched — an SVG in a data: URI cannot read a custom property,
   so the caret is a rotated square built from two borders and it inherits the
   ink of the edition it is on. */
select.cwd__i {
    padding-right: 1.5rem;
    background-image:
        linear-gradient(45deg, transparent 50%, currentColor 50%),
        linear-gradient(135deg, currentColor 50%, transparent 50%);
    background-position: right .78rem center, right .55rem center;
    background-size: .24rem .24rem, .24rem .24rem;
    background-repeat: no-repeat;
    color: var(--ink);
}
/* 42% OF THE INK IS 2.51:1 ON PAPER — a placeholder is the only text in a
   field before anything is typed, and it is the text that tells the reader what
   the field is for. 62% measures 4.62:1 on paper and 4.71:1 on dark. */
.cwd__i::placeholder { color: color-mix(in srgb, var(--ink) 62%, transparent); }
.cwd__i:hover  { border-color: color-mix(in srgb, var(--ink) 52%, transparent); }
.cwd__i:focus  { outline: none; border-color: var(--accent);
                 box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 22%, transparent); }

/* FOUR KEYS, ONE WIDTH, ONE HEIGHT. */
.cwd__keys {
    display: grid; gap: var(--cwd-gap);
    grid-template-columns: repeat(5, minmax(0, 1fr));
}
.cwd__b {
    font: inherit; font-size: .6875rem; font-weight: 600;
    letter-spacing: .1em; text-transform: uppercase;
    height: var(--cwd-h, 2.125rem); padding: 0 .5rem;
    display: inline-flex; align-items: center; justify-content: center;
    box-sizing: border-box; min-width: 0; white-space: nowrap;
    color: var(--ink); background: transparent;
    border: 1px solid var(--cwd-edge, color-mix(in srgb, var(--ink) 34%, transparent));
    border-radius: var(--cwd-r, 3px);
    cursor: pointer;
    transition: background .16s ease, color .16s ease, border-color .16s ease;
}
.cwd__b:hover { background: var(--ink); color: var(--paper); border-color: var(--ink); }
.cwd__b:active { transform: translateY(1px); }
.cwd__b:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* THE ONE LATCHING KEY. Braun's single orange, spent on the one control that
   holds a state rather than performing an action. */
.cwd__b[aria-pressed="true"] {
    background: var(--accent); color: #fff; border-color: var(--accent);
}
/* var(--paper), NOT #fff. On the dark edition --ink IS white, so this mix
   resolves to a PALE orange — rgb(250,140,64) — and white text on it measures
   2.36:1. The rule two above it already knows this and uses --paper; this one
   was written with a literal and, at (0,3,0), outranks the (0,2,0) rule that
   was put there to fix exactly this. Measured after: 5.29:1 on paper against
   rgb(164,60,18), 6.98:1 on dark against rgb(250,140,64). It also governs the
   inline SVG glyphs on the icon keys, which are stroked in currentColor. */
.cwd__b[aria-pressed="true"]:hover {
    background: color-mix(in srgb, var(--accent) 82%, var(--ink));
    border-color: color-mix(in srgb, var(--accent) 82%, var(--ink));
    color: var(--paper);
}

/* AN AUTHOR `display` BEATS THE UA SHEET'S [hidden] RULE, WHICH IS NOT WHAT
   ANYBODY MEANS BY IT. `.cwd__b { display: inline-flex }` is an author
   declaration; `[hidden] { display: none }` in the browser's own sheet is not,
   and origin beats specificity — so every .cwd__b the page tried to hide with
   the attribute stayed on screen. The queue's own NEXT key (#dk-next) ships
   hidden and is revealed only when something is queued: it was permanently
   visible, and pressing it over an empty queue did nothing at all. The sheet
   already carries this companion for four other classes; these are the two it
   was missing. */
.cwd__b[hidden] { display: none !important; }

/* THE RAIL. Label, ten slots, a name and two keys, all on the one height. */
.cwd__rail {
    display: flex; align-items: center; gap: var(--cwd-gap); flex-wrap: wrap;
    padding-top: .7rem; border-top: 1px solid var(--cwd-rule);
}
.cwd__l--rail { align-self: center; }
.cwd__i--name { flex: 1 1 9rem; min-width: 7rem; max-width: 18rem; width: auto; }
.cwd__rail .cwd__b { flex: 0 0 auto; padding: 0 .85rem; }

/* THE SLOTS. Square, on the desk's height, and numbered — the whole value of
   a rail of ten over a dropdown of ten is seeing which are taken without
   opening anything. */
.cw-slots { display: flex; flex-wrap: wrap; gap: .25rem; }
.cw-slot {
    font: inherit; font-size: .6875rem; font-weight: 600;
    font-variant-numeric: tabular-nums;
    height: var(--cwd-h, 2.125rem); min-width: var(--cwd-h, 2.125rem); padding: 0 .3rem;
    display: inline-flex; align-items: center; justify-content: center;
    box-sizing: border-box;
    color: var(--muted); background: transparent;
    border: 1px solid var(--cwd-rule); border-radius: var(--cwd-r);
    cursor: pointer;
    transition: border-color .16s ease, color .16s ease, background .16s ease;
}
.cw-slot b { font-size: inherit; font-weight: 600; }
/* The name a full slot carries is a title, not a second line — the rail is one
   row high and stays one row high. */
.cw-slot span { display: none; }
.cw-slot.is-full { color: var(--ink); border-color: var(--cwd-edge); }
.cw-slot.is-on {
    color: #fff; background: var(--accent); border-color: var(--accent);
}
.cw-slot:hover { border-color: var(--ink); color: var(--ink); }
.cw-slot.is-on:hover { color: #fff; background: var(--accent); }
.cw-slot:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* THE READOUT. The wheel and everything the desk has to say. */
.cwd__read {
    display: flex; align-items: flex-start; gap: .85rem;
    padding-top: .7rem; border-top: 1px solid var(--cwd-rule);
}
.cw-wheel {
    flex: 0 0 auto; padding: 0; border: 0; background: none; cursor: pointer;
    width: 5.25rem;                              /* 84px, from 152 */
    border-radius: 50%; line-height: 0;
    transition: transform .16s ease;
}
.cw-wheel canvas { display: block; width: 100%; height: auto; aspect-ratio: 1 / 1; }
.cw-wheel:hover { transform: scale(1.04); }
.cw-wheel:active { transform: scale(.97); }
.cw-wheel:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; }
.cwd__say { flex: 1 1 0; min-width: 0; display: grid; gap: .18rem; align-content: start; }
.cwd__say1 { margin: 0; font-size: .78rem; line-height: 1.45; color: var(--muted); }
.cwd__say2 {
    margin: 0; font-size: .72rem; line-height: 1.4; color: var(--muted);
    display: flex; gap: .5rem; align-items: baseline;
}
.cwd__say2 .cwd__l { flex: 0 0 2.6rem; }
.cwd__say2 b { color: var(--ink); font-weight: 600; }
.cwd__say3 { margin: .1rem 0 0; font-size: .72rem; line-height: 1.4; color: var(--accent); }

/* --------------------------------------------------------------- MOBILE
   TWO columns, not one. Four fields stacked is four rows of a phone screen
   spent before you reach a clock; two by two is one screenful of desk and the
   grid stays symmetric, which is the whole point of it.

   16px ON THE INPUTS, and the test is the POINTER, not the width: iOS Safari
   zooms the page on focus for anything under 16px and then leaves the fixed
   furniture off-screen, and a narrow desktop window does not zoom. */
/* Five columns of select need about a thousand pixels to stay readable; below
   that the desk folds to three and then to two rather than squeezing five
   labels into a hundred pixels each. */
@media (max-width: 1000px) {
    .cwd__fields,
    .cwd__keys { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@media (max-width: 640px) {
    .cwd__fields,
    .cwd__keys { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .cwd__i--name { flex: 1 1 100%; max-width: none; }
    .cwd__read { gap: .7rem; }
    .cw-wheel { width: 4.25rem; }
}
@media (hover: none) and (pointer: coarse) {
    .cwd { --cwd-h: 2.75rem; }
    .cwd__i { font-size: 16px; }
    .cw-slot { min-width: 2.4rem; }
}


/* ==========================================================================
   THE PLACARD — the same desk type, floating
   ==========================================================================
   Set in the desk's own recipe so it reads as part of the instrument rather
   than as a browser tooltip: the 10px/600/.14em label on the left, tabular
   values on the right, one hairline between the head and the table.

   IT IS NOT A .cw-card CHILD. One element for the whole board, moved and
   refilled — see geoShow(). position: fixed rather than absolute, because it
   is placed from getBoundingClientRect(), which is already viewport-relative;
   an absolute placard inside a scrolled page lands a scroll-offset away. */
.cw-geo {
    position: fixed; z-index: 60;
    min-width: 15rem; max-width: min(21rem, calc(100vw - 1rem));
    padding: .6rem .7rem;
    background: var(--paper);
    border: 1px solid color-mix(in srgb, var(--ink) 30%, transparent);
    border-radius: 3px;
    box-shadow: 0 .35rem 1.1rem rgba(0, 0, 0, .13);
    pointer-events: none;                 /* never steal the hover it describes */
}
.cw-geo[hidden] { display: none; }
.cw-geo__h {
    margin: 0; font-size: .82rem; font-weight: 600; line-height: 1.25;
    color: var(--ink);
}
.cw-geo__s {
    margin: .05rem 0 .45rem;
    font-size: .625rem; font-weight: 600; letter-spacing: .12em;
    text-transform: uppercase; color: var(--accent);
}
.cw-geo__dl {
    margin: 0; padding-top: .45rem;
    border-top: 1px solid color-mix(in srgb, var(--ink) 13%, transparent);
    display: grid; grid-template-columns: 4.4rem minmax(0, 1fr);
    gap: .2rem .5rem; align-items: baseline;
}
.cw-geo__dl dt {
    margin: 0; font-size: .5625rem; font-weight: 600; letter-spacing: .13em;
    text-transform: uppercase; color: var(--muted); line-height: 1.5;
}
.cw-geo__dl dd {
    margin: 0; font-size: .7rem; line-height: 1.45; color: var(--ink);
    font-variant-numeric: tabular-nums;
    overflow-wrap: anywhere;
}
/* NO PLACARD ON A TOUCH SCREEN. There is no hover to open it with, and the
   focusin path would fire it under the finger on a card's own arrows. The
   title= attribute is what answers there, and a long press is how. */
@media (hover: none) { .cw-geo { display: none !important; } }

/* ==========================================================================
   THE BENCH — stopwatches and countdowns
   --------------------------------------------------------------------------
   The face is Orbitron, the same squared instrument type the flight-deck dials
   are set in, so a stopwatch on this page reads as the same machine as the
   clocks above it rather than as a widget bolted on.
   ========================================================================== */
.tm-bar {
    display: flex; flex-wrap: wrap; align-items: center; gap: .5rem .8rem;
    margin: 1.4rem 0 1.2rem; padding-bottom: 1rem;
    border-bottom: 1px solid var(--line);
}
.tm-bar__n {
    margin-left: auto; font-size: .66rem; font-weight: 700; letter-spacing: .12em;
    text-transform: uppercase; color: var(--ink-55); font-variant-numeric: tabular-nums;
}
/* Each way of aiming a countdown is one unbroken group, so a wrap never leaves
   a stray number above the field it belongs to. */
.tm-set { display: inline-flex; align-items: center; gap: .3rem; }
.tm-set__l {
    font-size: .6rem; font-weight: 700; letter-spacing: .12em;
    text-transform: uppercase; color: var(--ink-55);
}
/* SIXTEEN PIXELS on every field: under it, iOS Safari zooms the page on focus.
   The estate's rule; see the note in the shared site.css. */
.tm-set__f, .tm-set__n, .tm-set__u, .tm-set__o {
    font-size: 16px; padding: .34rem .45rem; border-radius: 4px;
    border: 1px solid var(--line); background: var(--paper); color: var(--ink);
}
.tm-set__n { width: 4.4rem; font-variant-numeric: tabular-nums; }
.tm-set__o { max-width: 15rem; }

.tm-grid {
    display: grid; gap: .7rem;
    grid-template-columns: repeat(auto-fill, minmax(min(248px, 100%), 1fr));
}
.tm__none { grid-column: 1 / -1; margin: 0; font-size: .84rem; color: var(--ink-55); max-width: 60ch; }

.tm {
    border: 1px solid var(--line); border-radius: 8px;
    padding: .6rem .65rem; background: var(--paper);
    border-left: 2px solid var(--line);
}
/* A running instrument is marked at its edge and nowhere else — a card that
   changed colour while it ran would be a card that is hard to read while it is
   doing the one thing it is for. */
.tm[data-run="1"] { border-left-color: var(--accent); }
.tm--down[data-over="1"] { border-left-color: var(--taken); }

.tm__h { display: flex; align-items: center; gap: .3rem; }
.tm__lab {
    flex: 1 1 auto; min-width: 0;
    font-family: inherit; font-weight: 600; font-size: .68rem; line-height: 1.2; letter-spacing: .1em; text-transform: uppercase;
    color: var(--ink-55); background: none; border: 0; padding: .1rem 0;
}
.tm__lab:focus { outline: none; color: var(--ink); }
.tm__x {
    flex: 0 0 auto; width: 20px; height: 20px; line-height: 1; font-size: .9rem;
    background: none; border: 0; cursor: pointer; color: var(--ink-55); border-radius: 4px;
}
.tm__x:hover { color: var(--taken); }

/* THE FACE. Tabular figures so the digits do not jitter sideways as they change
   — at a hundredth of a second a proportional face is unreadable. The hundredths
   are smaller and quieter: at the same weight as the seconds, two digits
   changing a hundred times a second drag the eye off the number that matters. */
.tm__face {
    margin: .15rem 0 .1rem; display: flex; align-items: baseline; gap: .12rem;
    font-family: 'Orbitron', ui-monospace, SFMono-Regular, Menlo, monospace;
    font-variant-numeric: tabular-nums; color: var(--ink);
}
.tm__face b { font-size: 1.5rem; font-weight: 700; letter-spacing: -.01em; }
.tm__face i {
    font-style: normal; font-size: .84rem; font-weight: 600;
    color: var(--ink-55); padding-bottom: .08rem;
}
.tm__face i::before { content: '.'; }
.tm--down[data-over="1"] .tm__face b { color: var(--taken); }

.tm__sub {
    margin: 0 0 .45rem; font-size: .64rem; line-height: 1.3; color: var(--ink-55);
    min-height: .9rem;
}
.tm__btns { display: flex; gap: .3rem; }
.tm__b {
    flex: 1 1 auto;
    font-family: inherit; font-weight: 700; font-size: .62rem; line-height: 1; letter-spacing: .1em; text-transform: uppercase;
    color: var(--ink); background: none;
    border: 1px solid var(--line); border-radius: 4px;
    padding: .38rem .3rem; cursor: pointer;
}
.tm__b:hover { border-color: var(--accent); color: var(--accent); }
.tm__b--go { background: var(--ink); color: var(--paper); border-color: var(--ink); }
.tm__b--go:hover { background: var(--accent); border-color: var(--accent); color: var(--paper); }
.tm[data-run="1"] .tm__b--go { background: var(--accent); border-color: var(--accent); }

.tm__laps { list-style: none; margin: .45rem 0 0; padding: 0; max-height: 7.2rem; overflow-y: auto; }
.tm__laps li {
    display: grid; grid-template-columns: 1.3rem 1fr auto; gap: .3rem;
    align-items: baseline; padding: .16rem 0;
    border-top: 1px solid var(--line-soft);
    font-family: 'Orbitron', ui-monospace, Menlo, monospace;
    font-variant-numeric: tabular-nums; font-size: .68rem;
}
.tm__laps i { font-style: normal; font-size: .56rem; color: var(--ink-55); }
.tm__laps span { color: var(--ink-55); font-size: .6rem; }

/* ---------------------------------------------------------- popped out
   The bench alone: no navbar, no other bands, no footer. app.js already sets
   data-cw-pop for the clocks; this adds the one case it did not know about. */
html[data-cw-pop="1"][data-cw-only="bench"] .band:not(#bench),
html[data-cw-pop="1"][data-cw-only="bench"] .nav,
html[data-cw-pop="1"][data-cw-only="bench"] .foot { display: none !important; }

/* THE BENCH HAS TO BE PUT BACK, not merely left alone. The clock pop hides
   every band that does not hold a clock grid — html[data-cw-pop] .band:not(
   :has(.cw-grid)) — and it hides it with !important, so the bench, which has
   no grid, was hidden by the very rule that makes a popped clock work. Setting
   its padding was not enough: a popped bench opened on an empty page. */
html[data-cw-pop="1"][data-cw-only="bench"] #bench { display: block !important; padding: .9rem 0; }
html[data-cw-pop="1"][data-cw-only="bench"] #bench > .wrap { max-width: none; padding: 0 .9rem; }

/* A POPPED BOARD OF CLOCKS IS SIZED TO FIT AND MUST NOT SCROLL; ten timers are
   a list and must. The pop rules lock the document at 100% with overflow
   hidden, which would cut the tenth instrument off the bottom of the window. */
html[data-cw-pop="1"][data-cw-only="bench"],
html[data-cw-pop="1"][data-cw-only="bench"] body { overflow: auto; height: auto; }

/* ---------------------------------------------------------------------------
   THE POPPED BENCH IS THE INSTRUMENTS AND NOTHING ELSE.
   It used to carry the whole desk into the window — add-a-stopwatch, three
   ways to aim a countdown, the pop button that had just been pressed, and the
   tally — above one small card in a 760x680 sheet of empty white. A popped
   window is a thing you park in the corner of a screen and glance at; every
   control in it is a control you already used to make it. So the bar goes, and
   what is left is the size and shape of the stopwatches themselves. The window
   then measures its own content and shrinks to it — see fitPopWindow(). */
html[data-cw-pop="1"][data-cw-only="bench"] .tm-bar { display: none !important; }
html[data-cw-pop="1"][data-cw-only="bench"] #bench { padding: 0 !important; }
html[data-cw-pop="1"][data-cw-only="bench"] #bench > .wrap { max-width: none; padding: 0; }
html[data-cw-pop="1"][data-cw-only="bench"] .tm-grid {
    gap: 8px; padding: 8px; align-content: start;
    grid-template-columns: repeat(auto-fill, minmax(min(228px, 100%), 1fr));
}
/* The card loses the framing it needs on a page and keeps the edge that says
   whether it is running. A popped window has its own frame already. */
html[data-cw-pop="1"][data-cw-only="bench"] .tm { border-radius: 5px; padding: .5rem .55rem; }
/* × removes an instrument from the bench behind this window, which is not what
   a close control in a popped window looks like it does. Closing the window is
   the close control. */
html[data-cw-pop="1"][data-cw-only="bench"] .tm__x { display: none; }
html[data-cw-pop="1"][data-cw-only="bench"] .tm__lab { pointer-events: none; }
html[data-cw-pop="1"][data-cw-only="bench"] #bench .eyebrow,
html[data-cw-pop="1"][data-cw-only="bench"] #bench .d2,
html[data-cw-pop="1"][data-cw-only="bench"] #bench .lede { display: none; }


/* =============================================================================
   THE MOON
   -----------------------------------------------------------------------------
   ONE INK, AND THE DARK EDITION BRINGS ITS OWN PAPER. The disc is engraved, not
   painted: every tone on it is the density of a single ink laid in dots and
   lines. So there is no light-moon colour and no dark-moon colour to keep in
   step — the dark edition fills the lit limb with its own sheet of ivory first
   and then takes the identical plate on top of it. Four tokens, and two of them
   exist only for the dark ground.
   ========================================================================== */
:root {
    --mn-ink:   #141210;                  /* the one ink */
    --mn-sheet: rgba(20, 18, 16, .20);    /* paper: no sheet — this is for the phase marks */
    --mn-night: transparent;              /* paper: the night IS the page */
    --mn-glow:  rgb(150, 162, 192);
    --mn-ashen: rgb(126, 134, 158);
}
html[data-dark] {
    --mn-ink:   #262933;
    --mn-sheet: #EEEADE;                  /* the ivory the dark edition prints on */
    --mn-night: #0A0A0D;
}

.mn { display: grid; gap: 1.6rem; grid-template-columns: minmax(0, 400px) minmax(0, 1fr);
      align-items: start; }
.mn__disc { margin: 0; display: flex; flex-direction: column; gap: .55rem; align-items: center; }
.mn__cv { display: block; width: 100%; max-width: 400px; height: auto; }
.mn__cap { margin: 0; font-size: .72rem; line-height: 1.5; color: var(--muted); text-align: center;
           max-width: 34ch; }
.mn__side { min-width: 0; display: flex; flex-direction: column; gap: .55rem; }
.mn__name { margin: 0; font-size: 1.5rem; font-weight: 700; letter-spacing: -.015em;
            color: var(--ink); line-height: 1.1; }
.mn__illum { margin: 0 0 .3rem; font-size: .88rem; color: var(--muted); }
.mn__illum b { color: var(--ink); font-variant-numeric: tabular-nums; }
.mn__figs { margin-top: .2rem; }
.mn__lbl { margin: .5rem 0 0; font-size: .64rem; font-weight: 700; letter-spacing: .13em;
           text-transform: uppercase; color: var(--muted); }

/* WHAT COMES NEXT — four rows on one grid so the dates line up under each
   other. A ragged column of dates is a column nobody scans. */
.mn__next { list-style: none; margin: .1rem 0 0; padding: 0; display: grid; gap: .3rem; }
.mn__n { display: grid; grid-template-columns: 1.1rem minmax(6.5rem, auto) 1fr auto;
         align-items: baseline; gap: .5rem; font-size: .84rem; }
.mn__nt { color: var(--ink); font-weight: 600; }
.mn__nd { color: var(--muted); font-variant-numeric: tabular-nums; }
.mn__na { color: var(--muted); font-variant-numeric: tabular-nums; font-size: .78rem; }

/* The little disc beside each phase is drawn in CSS: a circle, and a second
   one over it in the page's own ground, offset to cut the crescent. */
.mn__ni { width: .78rem; height: .78rem; border-radius: 50%; display: inline-block;
          align-self: center; border: 1px solid var(--mn-ink); background: var(--mn-sheet);
          opacity: .85; }
.mn__ni[data-ph="new"]   { background: var(--mn-night); }
.mn__ni[data-ph="full"]  { background: var(--mn-sheet); }
.mn__ni[data-ph="first"] { background: linear-gradient(90deg, var(--mn-night) 50%, var(--mn-sheet) 50%); }
.mn__ni[data-ph="last"]  { background: linear-gradient(90deg, var(--mn-sheet) 50%, var(--mn-night) 50%); }

.mn__scrub { display: flex; align-items: center; gap: .7rem; flex-wrap: wrap;
             border-top: 1px solid var(--line-soft); padding-top: 1rem; margin-top: .3rem; }
.mn__scrub .mn__lbl { margin: 0; flex: 0 0 auto; }

/* THE PLAY KEY. A disc with a triangle in it, drawn in CSS rather than shipped
   as a glyph — and it becomes two bars while it is running, because a control
   that looks identical whether or not it is doing something is a control you
   press twice. */
.mn__play {
    flex: 0 0 auto; width: 1.7rem; height: 1.7rem; padding: 0; cursor: pointer;
    border: 1px solid var(--line); border-radius: 50%;
    background: none; color: var(--ink);
    display: inline-flex; align-items: center; justify-content: center;
}
.mn__play:hover { border-color: var(--accent); color: var(--accent); }
.mn__play span {
    width: 0; height: 0; margin-left: .13em;
    border-left: .48em solid currentColor;
    border-top: .30em solid transparent;
    border-bottom: .30em solid transparent;
}
.mn__play[aria-pressed="true"] span {
    margin-left: 0; width: .5em; height: .56em; border: 0;
    background: linear-gradient(90deg, currentColor 0 34%, transparent 34% 66%, currentColor 66% 100%);
}
.mn__range { flex: 1 1 12rem; min-width: 8rem; }
.mn__now { flex: 0 0 auto; }

@media (max-width: 760px) {
    .mn { grid-template-columns: minmax(0, 1fr); }
    .mn__disc { max-width: 100%; }
    .mn__n { grid-template-columns: 1.1rem minmax(5.5rem, auto) 1fr; }
    .mn__na { grid-column: 2 / -1; }
}

/* =============================================================================
   VIZZI
   -----------------------------------------------------------------------------
   THE STAGE IS THE PAGE'S ONE PIECE OF FURNITURE. Everything else is a control
   sitting under it, so the layout rule is simple: the stage keeps a fixed
   aspect until it is asked to fill the screen, and the tiles inside it are a
   square grid that never reflows mid-performance.
   ========================================================================== */
/* THE STAGE HEADING NEEDS AIR UNDER IT. .d2 carries no bottom margin — it is
   used in sixty places and most of them follow it with a lede that has its own
   — but here the next thing is the console's own border, which landed directly
   under the heading's descenders. Scoped, so the other fifty-nine are untouched. */
.vz-hed { margin: 0 0 1.6rem; }

/* THE FOLD. The homepage register's minimise key, exactly: a hairline with one
   small key on its right end; ^ while folded, v while open. 26px to look at, 44px
   to hit. The state is the key's aria-expanded; site.js mirrors it to data-open on
   .vz-more in the same call, and the panel opens off that (no :has() dependency).
   The height travels on grid rows (0fr to 1fr), which animates to the content's
   real height at every width without measuring it. visibility, not opacity, is
   what hides it: a visibility:hidden subtree is out of the accessibility tree,
   and the switch is delayed until the fold has closed so the text does not blink
   out before the panel has moved. Print unfolds it and drops the key. */
.vz-more { margin: 0; }
#deck .vz-more { margin-bottom: 1.25rem; }
.vz-fold {
    position: relative; margin: 1.6rem 0 .2rem;
    border-top: 1px solid var(--line);
    display: flex; justify-content: flex-end;
}
.vz-fold__b {
    position: relative; top: -13px;
    width: 26px; height: 26px; flex: 0 0 auto; padding: 0;
    display: inline-flex; align-items: center; justify-content: center;
    background: var(--paper); border: 1px solid var(--line); border-radius: 7px;
    color: var(--ink-55); cursor: pointer;
    touch-action: manipulation; -webkit-tap-highlight-color: transparent;
    transition: color .2s var(--ease), border-color .2s var(--ease);
}
.vz-fold__b::after { content: ''; position: absolute; top: -9px; right: -9px; bottom: -9px; left: -9px; }
.vz-fold__b:hover, .vz-fold__b:focus-visible { color: var(--ink); border-color: var(--ink); }
.vz-fold__b svg { display: block; transition: transform .28s var(--ease); }
.vz-fold__b[aria-expanded="false"] svg { transform: rotate(180deg); }
.vz-more__p {
    display: grid; grid-template-rows: 0fr;
    visibility: hidden;
    transition: grid-template-rows .28s var(--ease), visibility 0s linear .28s;
}
.vz-more[data-open] > .vz-more__p {
    grid-template-rows: 1fr; visibility: visible;
    transition: grid-template-rows .28s var(--ease), visibility 0s linear 0s;
}
.vz-more__in { min-height: 0; overflow: hidden; }
.vz-more__in > .lede { margin: 0; padding-top: .35rem; }
.vz-more__in > .vz-cat { margin-top: 1.1rem; }
@media (prefers-reduced-motion: reduce) {
    .vz-more__p, .vz-more[data-open] > .vz-more__p, .vz-fold__b svg { transition: none; }
}
@media print {
    .vz-fold { display: none !important; }
    .vz-more__p { grid-template-rows: 1fr !important; visibility: visible !important; }
}

/* TWENTY-FOUR TRACKS, AND THE ROW IS FLUSH BECAUSE THE SPANS SAY SO.

   The desk was four equal columns holding FIVE cells, one of which spanned the
   full width. Five cells cannot tile four tracks: auto-placement put SOURCE
   alone in row one and pushed MODE to row two, so columns two to four of the
   first row — 844 x 54 px — were empty, and column four of the last row another
   269 x 54. Measured at 1280 that is 60,102 px2 of a 396,228 px2 desk: FIFTEEN
   PER CENT of the panel, in two rectangles, and no amount of padding, gap or
   align-content moves it. That is the hole in the screenshot.

   Three runs whose spans sum to the module close the row by construction:
   6 + 8 + 10 = 24. Nothing is left over because nothing can be. */
.vz-desk {
    display: grid; gap: 1rem var(--cwd-gap, .4rem);
    grid-template-columns: repeat(24, minmax(0, 1fr));
    margin-bottom: 1rem;
}
.vz-desk__f--src   { grid-column: span 6; }
.vz-desk__f--stage { grid-column: span 8; }
.vz-desk__f--word  { grid-column: span 10; }
.vz-desk__f--wide  { grid-column: 1 / -1; }
/* the mode band's heading carries Shuffle, because shuffling is something you
   do to MODES — and it keeps the key inside the panel slots.js already watches */
/* IT HAS TO WRAP. Six transport keys at 34px, the word MODE, the mode's name
   and Shuffle come to more than a phone is wide, and the phone rule below —
   `.vz-transport { flex-basis: 100% }`, the estate's give-it-its-own-line
   idiom — only does anything in a WRAPPING container. Without this the row was
   laid out at its max-content width and the last two controls, Repeat and
   Shuffle, sat outside the console's clip: not small, not squeezed, GONE. */
.vz-desk__l--head { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem; }
/* and the track it sits in must be allowed to be narrower than its contents,
   or the wrap never gets the chance to happen */
.vz-desk__f { min-width: 0; grid-template-columns: minmax(0, 1fr); }
/* ONE KEY HEIGHT, NO EXCEPTIONS — this was the last 26px key on a console of
   eighty-two at 34, and one odd key is exactly the kind of thing you cannot
   name but can see. */
.vz-shuf { margin-left: 0; font-size: .58rem; height: var(--cwd-h, 2.125rem); padding: 0 .7rem; }
@media (max-width: 900px) {
    .vz-desk__f--src   { grid-column: span 10; }
    .vz-desk__f--stage { grid-column: span 14; }
    .vz-desk__f--word  { grid-column: 1 / -1; }
}
@media (max-width: 620px) {
    .vz-desk__f--src, .vz-desk__f--stage, .vz-desk__f--word { grid-column: 1 / -1; }
}
.vz-desk__f { display: grid; gap: .28rem; min-width: 0; align-content: start; }
.vz-desk__l {
    font-size: .6rem; font-weight: 700; letter-spacing: .14em;
    text-transform: uppercase; color: var(--muted);
}
.vz-keys { display: flex; gap: .3rem; flex-wrap: wrap; }
.vz-keys .cwd__b { flex: 1 1 auto; }

/* THE DROP ZONE IS THE LABEL, so a tap opens the picker and a drag does the
   same job without one. */
.vz-drop {
    display: block; text-align: center; cursor: pointer;
    border: 1px dashed var(--line); border-radius: 8px;
    padding: .7rem .9rem; margin: 0 0 1rem;
    transition: border-color .16s ease, background .16s ease;
}
.vz-drop b { display: block; font-size: .92rem; color: var(--ink); }
.vz-drop span { display: block; font-size: .76rem; color: var(--muted); margin-top: .12rem; }
.vz-drop:hover, .vz-drop[data-over] { border-color: var(--accent); }
.vz-drop[data-over] { background: color-mix(in srgb, var(--accent) 6%, transparent); }

/* THE ROWS MUST BE NAMED, and this is not tidiness — it was a live defect.

   The stage named its COLUMNS and left the rows to `grid-auto-rows: auto`, so
   each row was sized by its content. The content is a canvas whose intrinsic
   height app.js sets from the PREVIOUS measurement, which is a feedback loop
   with no fixed point: measured on the shipping page, four tiles gave rows of
   145.672 and 99.531 — the top row 46% taller than the bottom — and nine gave
   108.641 / 70.766 / 59.797, an 82% spread, with nine different canvas backing
   heights (80, 97, 108, 62, 67, 70, 56, 58, 59) NOT ONE of which matched its
   own tile. Every tile on the stage was drawing at the wrong resolution and
   being stretched to fit.

   Naming the rows makes the tracks independent of what is in them, and the loop
   has nowhere to run: measured after, 204.609 three times over, every canvas
   274x205. */
.vz-stage {
    /* THE GUTTERS ARE THE ONLY LINES ON THE STAGE, so they are drawn as lines
       and not as margins: one hairline of ground between pictures, not six
       pixels of it. At 6px the grid read as a set of framed thumbnails; at 2px
       it reads as one instrument divided. */
    display: grid; gap: 2px;
    aspect-ratio: 16 / 9;
    /* EDGE TO EDGE. The radius and the console's own padding put a white margin
       and four rounded corners around the picture, which is furniture drawn on
       top of the one thing on the page that is supposed to be looked at. The
       stage runs to the console's edges and the gutters between tiles are the
       only lines in it. */
    background: var(--ink); border-radius: 0; overflow: hidden;
    margin-left: calc(var(--cb-pad, 1.2rem) * -1);
    margin-right: calc(var(--cb-pad, 1.2rem) * -1);
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: minmax(0, 1fr);
}
.vz-stage[data-n="4"]  { grid-template-columns: repeat(2, minmax(0, 1fr)); grid-template-rows: repeat(2, minmax(0, 1fr)); }
.vz-stage[data-n="9"]  { grid-template-columns: repeat(3, minmax(0, 1fr)); grid-template-rows: repeat(3, minmax(0, 1fr)); }
/* TWELVE IS FOUR BY THREE, and that is arithmetic rather than taste. Split a
   stage of aspect A into c columns and r rows and every tile is A*r/c: on the
   16:9 stage 4x3 gives 1.337, 3x4 gives 2.411, 6x2 gives 1.715 and 2x6 gives
   5.554, measured as distance from square. 4x3 wins by 1.28x over its nearest
   rival — and on the 4:3 phone stage the same grid comes out at 0.993, which is
   square. One rule, both stages. */
.vz-stage[data-n="12"] { grid-template-columns: repeat(4, minmax(0, 1fr)); grid-template-rows: repeat(3, minmax(0, 1fr)); }
/* FULL SCREEN IS EDGE TO EDGE. The 6px gutter that separates the tiles on the
   page is furniture — it belongs to a stage sitting inside a console with a
   border and a radius. On a whole display it becomes a bright frame drawn
   across the picture, and the UA's own fullscreen backdrop shows through it in
   whatever colour it likes. Nothing between the tiles, and the backdrop
   explicitly painted so no edition can leak white into the gaps. */
.vz-stage:fullscreen { aspect-ratio: auto; width: 100%; height: 100%; border-radius: 0; gap: 0; padding: 0; }
.vz-stage:fullscreen::backdrop { background: #07090C; }
.vz-stage:-webkit-full-screen { aspect-ratio: auto; width: 100%; height: 100%; border-radius: 0; gap: 0; }
/* and a single tile popped out from the right-click menu, same rule */
.vz-tile:fullscreen { width: 100%; height: 100%; }
.vz-tile:fullscreen::backdrop { background: #07090C; }
.vz-tile:fullscreen::after { border-color: transparent; box-shadow: none; }
.vz-tile { position: relative; min-width: 0; min-height: 0; overflow: hidden; cursor: pointer; }
/* The 480ms long-press opens the tile menu; it must not also select text or raise
   the iOS callout under the finger. */
.vz-tile { -webkit-user-select: none; user-select: none; -webkit-touch-callout: none; }
.vz-cv { display: block; width: 100%; height: 100%; }
/* THE TILE'S NAME MOVES TO WHATEVER CORNER IS QUIET — app.js measures all four
   and writes data-corner and data-ink. No blend mode: mix-blend-mode: difference
   is a good trick on a dark ground and a bad one on saturated colour, where it
   returns another mid colour and the word vanishes. A chosen ink with a
   matching halo behind it reads on anything. */
.vz-tile__n {
    position: absolute; margin: 0; pointer-events: none;
    font-size: .56rem; font-weight: 700; letter-spacing: .14em; text-transform: uppercase;
    padding: 2px 5px; border-radius: 3px;
    color: rgba(255, 255, 255, .92);
    background: rgba(0, 0, 0, .34);
    -webkit-backdrop-filter: blur(2px); backdrop-filter: blur(2px);
    transition: opacity .18s ease;
}
.vz-tile__n[data-ink="1"] {
    color: rgba(12, 14, 18, .92);
    background: rgba(255, 255, 255, .40);
}
.vz-tile__n[data-corner="bl"], .vz-tile__n:not([data-corner]) { left: 5px; bottom: 5px; top: auto; right: auto; }
.vz-tile__n[data-corner="br"] { right: 5px; bottom: 5px; top: auto; left: auto; }
.vz-tile__n[data-corner="tl"] { left: 5px; top: 5px; bottom: auto; right: auto; }
.vz-tile__n[data-corner="tr"] { right: 5px; top: 5px; bottom: auto; left: auto; }
.vz-stage:fullscreen .vz-tile__n { font-size: .7rem; }

/* THE AIM RING, AND IT IS A PSEUDO-ELEMENT RATHER THAN A BOX-SHADOW.

   An inset box-shadow paints between an element's own background and its CHILD
   CONTENT — and the child here is an opaque canvas filling the tile, so an
   inset ring is drawn and then immediately covered. It is invisible, and it
   looks like the aim is not working rather than like the ring is behind
   something. ::after is a child too, and it comes last, so it paints on top.

   Two rings, not one: the accent has to separate from an arbitrary picture, and
   whichever of the two the tile happens to match, it cannot match both. */
.vz-tile::after {
    content: ''; position: absolute; inset: 0; pointer-events: none;
    border: 1px solid transparent; border-radius: 0;
    transition: border-color .12s ease, box-shadow .12s ease;
}
/* A TRUE HAIRLINE WHERE THE SCREEN CAN DRAW ONE. 1px on a 2× display is two
   device pixels — twice as heavy as the thinnest line that panel can make. */
@media (min-resolution: 2dppx) {
    .vz-tile::after { border-width: .5px; }
}
.vz-tile[data-aim]::after {
    border-color: var(--accent);
    /* the second ring is a shadow now rather than a second border, so the aim
       costs the picture no width at all */
    box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .34), 0 0 0 0 transparent;
}
/* var(--paper), not #fff: white on the accent measures 2.80:1 on the dark
   edition and fails. --paper inverts with the edition — 5.18:1 on paper,
   6.77:1 on dark. Both measured. */
.vz-tile[data-aim] .vz-tile__n { background: var(--accent); color: var(--paper); }

/* THE METER — what the modes are actually being handed. Without it a still
   picture and a dead microphone look exactly the same, which is the whole
   reason it exists — and it used to say so in a thin line with two thirds of
   the measure empty beside it. Six cells on the panel's own grid, one figure
   each, big enough to read from where you are actually sitting.
   ========================================================================== */
/* NINE TRACKS, BECAUSE THE CELLS ASK FOR NINE. Seven cells, two of which span
   two — bands and sensitivity — is nine track-units, and in a seven-track grid
   that is six cells and one orphan on a second row. Counting them is the whole
   fix. */
.vz-meter {
    display: grid; grid-template-columns: repeat(9, minmax(0, 1fr));
    gap: var(--cwd-gap, .4rem);
    margin: .9rem 0 .2rem; padding-top: .9rem;
    border-top: 1px solid var(--line-soft);
}
.vz-mcell {
    display: grid; gap: .22rem; align-content: start; min-width: 0;
    padding: .45rem .55rem;
    border: 1px solid var(--line); border-radius: var(--cwd-r, 3px);
}
/* the bands cell carries eight bars where the others carry one figure, so it
   takes two tracks — 1+1+2+1+1+1 is seven, and seven is what the mode grid
   above it uses, so the two bands line up */
.vz-mcell--wide { grid-column: span 2; }
.vz-mcell__l {
    display: flex; align-items: center; gap: .35rem;
    font-size: .55rem; font-weight: 700; letter-spacing: .13em;
    text-transform: uppercase; color: var(--muted);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.vz-mcell__n {
    font-variant-numeric: tabular-nums; font-size: 1.35rem; font-weight: 600;
    line-height: 1; letter-spacing: -.02em; color: var(--ink);
}
.vz-mcell__u { font-size: .55rem; letter-spacing: .1em; text-transform: uppercase; color: var(--muted); }
.vz-bar { display: block; width: 100%; height: 4px; background: var(--line); border-radius: 2px; overflow: hidden; }
.vz-bar i { display: block; height: 100%; width: 0; background: var(--accent); transition: width .08s linear; }
.vz-bands { display: flex; align-items: flex-end; gap: 3px; height: 34px; }
.vz-bands i { display: block; flex: 1 1 0; min-width: 0; height: 2px; background: var(--accent); border-radius: 1px; transition: height .07s linear; }
.vz-beat {
    width: 9px; height: 9px; border-radius: 50%; display: inline-block; flex: 0 0 auto;
    border: 1px solid var(--line); background: none; transition: background .09s ease;
}
.vz-beat[data-hit] { background: var(--accent); border-color: var(--accent); }
/* THE CELLS ARE SEVEN BUT THE UNITS ARE NINE — Bands and Sensitivity are two
   each — so the count that has to divide the tracks is nine, not seven. At nine
   tracks it is one clean row. At five it was 5 + 4, an orphan unit on the second
   row; at two it was four full rows and a fifth holding Crest alone with a hole
   beside it. Widening one cell at each breakpoint is the whole fix, and it is
   arithmetic rather than taste: 5 + 5 and 2 + 2 + 2 + 2 + 2. */
@media (max-width: 1000px) {
    .vz-meter { grid-template-columns: repeat(5, minmax(0, 1fr)); }
    .vz-mcell--sens { grid-column: span 3; }
}
@media (max-width: 620px)  { .vz-meter { grid-template-columns: repeat(2, minmax(0, 1fr)); }
                             .vz-mcell--wide { grid-column: span 2; } }

.vz-fxl { margin: .9rem 0 .35rem; font-size: .6rem; font-weight: 700;
          letter-spacing: .14em; text-transform: uppercase; color: var(--muted); }
/* ONE ROW, NOT A HEADING OVER A RANK (490). The rail was eight stretched
   tracks under a heading row of its own — two rows of 134px boxes holding
   six-letter words, under a row that held a label, two keys and a desk's width
   of nothing. The heading, the keys, the Symbols key and None are now ONE
   flex line: the label at the left, the keys in the middle, the two master
   keys at the right end.

   IN WORDS each key is as wide as its word and the keys wrap inside their own
   box, so two ragged lines replace two stretched ones and the head row goes
   away. (The old note here chose a grid over a wrapping line to avoid a widow;
   a widow of short word keys costs less than a blank row, which is what the
   reader asked to lose.) IN SYMBOLS the box is the mode strip's grid of cells
   — see THE FX RAIL further down; fxLayout() in app.js sizes it.
   On a phone the keys take the line under the label in both. */
.vz-fxhead { display: flex; flex-wrap: wrap; align-items: flex-start; gap: var(--cwd-gap, .4rem); margin: 0; }
.vz-fxhead .vz-fxl { margin: 0; height: var(--cwd-h, 2.125rem); display: inline-flex; align-items: center; white-space: nowrap; }
/* THE LABEL HOLDS ITS WIDTH. It shares the row with the keys, and its count
   changes as they are pressed: "none on · tile 1" is 113.8px, "1 on · tile 1"
   87.6, so the first press moved every key 26px left and a second click at the
   same spot landed 9px inside the NEXT key. The floor is the widest the count
   gets — "none on · every tile", 144.1px at 16px rem. Symbols at every width
   (the cells sit beside it down to the width where they take their own line);
   words only on a desk, because on a phone the word keys already sit under it. */
.console[data-keys="sym"] .vz-fxhead .vz-fxl { min-width: 9.125rem; }
@media (min-width: 701px) { .vz-fxhead .vz-fxl { min-width: 9.125rem; } }
.vz-fx { display: flex; flex-wrap: wrap; gap: var(--cwd-gap, .4rem); flex: 1 1 0; min-width: 0; }
.vz-fx__b { justify-content: center; text-align: center; overflow: hidden; flex: 0 0 auto; }
.vz-fxhead .vz-symk--fx { margin-left: auto; }
@media (max-width: 700px) {
    .console:not([data-keys="sym"]) .vz-fxhead .vz-fxl { flex: 1 0 auto; }
    .console:not([data-keys="sym"]) .vz-fxhead > .vz-fx { order: 3; flex-basis: 100%; }
}
.vz-fx__b {
    font: inherit; font-size: .68rem; font-weight: 700; letter-spacing: .08em;
    text-transform: uppercase; cursor: pointer;
    /* ONE KEY HEIGHT ON THE WHOLE CONSOLE. Measured at 1280 before this: the
       desk keys and the mode keys were 34.0, the FX keys 29.2 and the slot keys
       31.2 — three heights on a panel whose whole brief was to be one. */
    height: var(--cwd-h, 2.125rem);
    display: inline-flex; align-items: center;
    padding: 0 .6rem; border-radius: var(--cwd-r, 3px);
    border: 1px solid var(--line); background: none; color: var(--muted);
}
.vz-fx__b:hover { border-color: var(--accent); color: var(--accent); }

/* The catalogue under the stage. */
/* THE CATALOGUE — ONE GRID OF TWENTY-EIGHT, four across and seven down, which
   comes out exactly. Seven per-family grids left five ragged rows between them;
   the family rides on each card instead. */
.vz-cat--flat {
    display: grid; gap: var(--cwd-gap, .4rem);
    grid-template-columns: repeat(4, minmax(0, 1fr));
    margin-top: 1.1rem;
}
.vz-card {
    padding: .6rem .7rem; min-width: 0;
    border: 1px solid var(--line); border-radius: var(--cwd-r, 3px);
}
.vz-card__g {
    margin: 0 0 .3rem; font-size: .52rem; font-weight: 700;
    letter-spacing: .14em; text-transform: uppercase; color: var(--accent);
}
.vz-card__n { margin: 0; font-size: .86rem; font-weight: 600; color: var(--ink); }
.vz-card__l { margin: .15rem 0 0; font-size: .74rem; line-height: 1.42; color: var(--muted); }
@media (max-width: 1000px) { .vz-cat--flat { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 560px)  { .vz-cat--flat { grid-template-columns: minmax(0, 1fr); } }

.vz-cols { display: grid; gap: 2rem; grid-template-columns: repeat(2, minmax(0, 1fr)); }
.vz-cols p { font-size: .89rem; line-height: 1.62; color: var(--ink-70, var(--muted)); max-width: 62ch; }
/* THE BROWSER'S OWN CODEC ANSWER (#23): one line of Orbitron caps under "What it will play" */
.vz-cols p.vz-codecs { font: 600 .66rem/1.5 'Orbitron', ui-monospace, SFMono-Regular, Menlo, monospace;
    letter-spacing: .06em; text-transform: uppercase; color: var(--ink); }
.vz-h3 { margin: 1.1rem 0 .3rem; font-size: .95rem; letter-spacing: -.01em; color: var(--ink); }
.vz-h3:first-child { margin-top: 0; }

/* ONE COLUMN ON A PHONE, and it is the four-key rows that force it. Source and
   Stage each hold four keys; in a half-width cell at 320px that is 25px a key,
   which is a quarter of the 44px floor and unusable however tidy it looks. Full
   width gives each of them 57.5px. Nothing else on the desk is harmed by having
   the whole measure — the fields were only ever side by side to save vertical
   room on a desktop. */

@media (max-width: 760px) {
    .vz-cols { grid-template-columns: minmax(0, 1fr); }
    .vz-stage { aspect-ratio: 4 / 3; }
}
/* THE PHONE FORK IS ABOUT SIZE, NOT SHAPE. 4x3 on a 4:3 phone stage is a
   perfect square and only 75px across; a 3:4 portrait stage three tiles wide
   gives the SAME square at 102px — 83% more tile area — measured 102.4 x 101.9
   at 390 wide and 95.6 x 95.1 at 320. Gated on portrait as well as width,
   because at a tablet's 505px of stage the portrait rule would build a 673px
   tower. */
@media (max-width: 560px) and (orientation: portrait) {
    .vz-stage { aspect-ratio: 3 / 4; }
    .vz-stage[data-n="4"]  { grid-template-columns: repeat(2, minmax(0, 1fr)); grid-template-rows: repeat(2, minmax(0, 1fr)); }
    .vz-stage[data-n="9"]  { grid-template-columns: repeat(3, minmax(0, 1fr)); grid-template-rows: repeat(3, minmax(0, 1fr)); }
    .vz-stage[data-n="12"] { grid-template-columns: repeat(3, minmax(0, 1fr)); grid-template-rows: repeat(4, minmax(0, 1fr)); }
}

/* =============================================================================
   THE DECK
   -----------------------------------------------------------------------------
   Laband's desk, in VIZZI's clothes: the same controls, the same honesty about
   what each one does, but sitting under a stage rather than beside a turntable.
   Every row is LABEL / SLIDER / READOUT on one grid, so the eye can run down
   the readouts alone and take in the whole state.
   ========================================================================== */
.dk-top { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 1.2rem;
          align-items: center; margin-bottom: 1.2rem; }

/* The platter is drawn in CSS: a disc, a label, and a spindle. It turns only
   while something is playing, which is the one piece of state on this page you
   can read from across a room. */
/* IT IS A BUTTON NOW, because it does something. A disc that turns while the
   music plays and cannot be touched is a picture of a turntable; one you can
   stop with a finger and drag to find a bar is a turntable. */
.dk-platter {
    -webkit-appearance: none; appearance: none;
    position: relative; width: 92px; height: 92px; flex: 0 0 auto;
    padding: 0; border: 0; background: none; cursor: grab;
    touch-action: none;
}
.dk-platter:active, .dk-platter[data-grab] { cursor: grabbing; }
.dk-platter:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
/* A RECORD, NOT A STRIPED DISC. The old face was one repeating ring at a
   uniform 3px pitch, which reads as a target rather than as vinyl. A pressing
   has four distinct zones and they are what the eye recognises:

     the LEAD-IN     a smooth band at the rim, no groove in it at all
     the PROGRAMME   the music, grooves packed tight and unevenly — loud
                     passages cut wider, so real vinyl is visibly banded
     the RUN-OUT     smooth again, just outside the label
     the LABEL       paper, and the only colour on the whole thing

   Three stacked repeating gradients at 2px, 3px and 5px give the groove pitch a
   beat rather than a single frequency, which is what stops it reading as a
   pattern. Everything is layered on one element so the whole face turns
   together — except the sheen, which does not (see below). */
.dk-platter__d {
    display: block; width: 92px; height: 92px; border-radius: 50%;
    background:
        /* the label, and the paper ring around the run-out groove */
        radial-gradient(circle at 50% 50%,
            var(--accent) 0 30%, rgba(255,255,255,.10) 30% 31%, transparent 31.5%),
        /* run-out: smooth */
        radial-gradient(circle at 50% 50%, transparent 0 31%,
            rgba(255,255,255,.045) 31% 34%, transparent 34.5%),
        /* the programme, three pitches beating against each other */
        repeating-radial-gradient(circle at 50% 50%,
            rgba(255,255,255,.13) 0 .5px, transparent .5px 2px),
        repeating-radial-gradient(circle at 50% 50%,
            rgba(255,255,255,.07) 0 .5px, transparent .5px 3px),
        repeating-radial-gradient(circle at 50% 50%,
            rgba(0,0,0,.55) 0 1px, transparent 1px 5px),
        /* lead-in: smooth, and a touch lighter where the rim catches the light */
        radial-gradient(circle at 50% 50%, transparent 0 93%,
            rgba(255,255,255,.16) 93% 100%),
        /* the body: vinyl is not flat black, it is black with a sheen in it */
        radial-gradient(circle at 34% 28%, #3A3F45 0%, #16191D 46%, #090B0D 100%);
    box-shadow: 0 1px 5px rgba(0,0,0,.28), inset 0 0 0 1px rgba(0,0,0,.5);
}
/* the spindle, and the small shadow the hole casts into the paper */
.dk-platter__d i {
    position: absolute; left: 50%; top: 50%; width: 6px; height: 6px;
    margin: -3px 0 0 -3px; border-radius: 50%;
    background: var(--paper);
    box-shadow: inset 0 1px 1.5px rgba(0,0,0,.55), 0 0 0 1px rgba(0,0,0,.28);
}
/* THE LIGHT DOES NOT TURN WITH THE RECORD. A highlight drawn onto the disc
   rotates with it, which is the one thing that instantly says "this is a
   picture of a record" rather than a record — the sheen on a real one stays
   where the lamp is while the vinyl moves underneath it. So it lives on the
   BUTTON, over the disc, and never rotates. */
.dk-platter::after {
    content: ''; position: absolute; inset: 0; border-radius: 50%;
    pointer-events: none;
    background:
        linear-gradient(118deg,
            rgba(255,255,255,0) 30%,
            rgba(255,255,255,.11) 44%,
            rgba(255,255,255,.02) 52%,
            rgba(255,255,255,0) 62%);
}
/* and it brightens under the hand, the way a record does when you lean over it */
.dk-platter[data-grab]::after { background:
        linear-gradient(118deg,
            rgba(255,255,255,0) 28%,
            rgba(255,255,255,.17) 44%,
            rgba(255,255,255,.04) 54%,
            rgba(255,255,255,0) 64%); }
@media (prefers-reduced-motion: reduce) { .dk-platter::after { transition: none; } }
/* NO CSS ANIMATION. A keyframe runs on its own clock and cannot be scrubbed,
   paused mid-turn or dragged; the angle is written from the playhead instead,
   which also means the disc is in step with the audio rather than merely near
   it, and a change of speed shows on the label. */
.dk-platter__d { will-change: transform; }
.dk-platter__logo {
    position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);
    font-size: .42rem; font-weight: 700; letter-spacing: .18em;
    color: var(--paper); pointer-events: none; user-select: none;
    /* it sits on the accent centre label, and turns with the disc because it
       is a child of the thing being rotated */
    text-shadow: 0 0 2px rgba(0,0,0,.35);
}
.dk-platter b {
    position: absolute; left: 50%; bottom: -2px; transform: translateX(-50%);
    font-size: .6rem; font-weight: 700; font-variant-numeric: tabular-nums;
    background: var(--paper); padding: 0 .25rem; border-radius: 2px; color: var(--ink);
}

.dk-now { min-width: 0; }
.dk-now__t { margin: 0; font-size: 1.05rem; font-weight: 700; letter-spacing: -.015em; color: var(--ink); }
.dk-now__s { margin: .1rem 0 .5rem; font-size: .78rem; color: var(--muted); }
.dk-scrub { display: grid; gap: .2rem; margin-bottom: .55rem; }
.dk-times { display: flex; justify-content: space-between; font-size: .7rem;
            font-variant-numeric: tabular-nums; color: var(--muted); }
/* THE DECK TRANSPORT IS THE MODE ROW'S (#3, #20): the same five glyph keys in
   one cluster (.vz-transport, .vz-tk below), then Repeat and the music dice.
   Play is the one emphasised key. On a phone the five share the first line and
   Repeat and the dice take the second. */
.dk-keys { display: flex; flex-wrap: wrap; align-items: center; gap: .3rem; }
.dk-tx { margin-left: 0; }
.dk-tx .vz-tk.dk-play { background: var(--ink); color: var(--paper); }
.dk-tx .vz-tk.dk-play:hover { background: var(--accent); color: var(--paper); }
/* the repeat key's "1", shown only while it repeats this one track */
.vz-tk[data-mode] { position: relative; }
.vz-rep1 { display: none; position: absolute; right: 3px; bottom: 2px;
    font: 700 .45rem/1 'Orbitron', ui-monospace, Menlo, monospace; }
.vz-tk[data-mode="one"] .vz-rep1 { display: block; }
.dk-keys .dk-dice { position: relative; }
/* the keys in words, only where there is a keyboard to press them */
.dk-keyhint { display: none; margin: .5rem 0 0; font-size: .78rem; color: var(--ink-55); }
@media (pointer: fine) { .dk-keyhint { display: block; } }
@media (max-width: 620px) {
    .dk-tx { flex: 1 1 100%; }
    .dk-tx .vz-tk { flex: 1 1 0; width: auto; min-width: 0; }
}
/* THE HOLD EFFECTS (#21): four chips after the dice, each in its own colour
   (violet brake, pink tape, rose and red for the two longer stops), filled while
   held. A held key must not scroll, zoom, select or call out, so touch-action is
   none here. Paper values below; the dark edition's are in dark.css. White on
   each paper colour measures >= 6.04:1, #0C1116 on each dark one >= 6.86:1. */
:root { --fx-brk: #6D28D9; --fx-tape: #BE185D; --fx-t2: #BE123C; --fx-t3: #B91C1C; }
.dk-fx { display: inline-flex; gap: .25rem; }
.dk-fx__k { -webkit-appearance: none; appearance: none; height: var(--cwd-h, 2.125rem); padding: 0 .45rem;
  font: 700 .56rem/1 'Orbitron', ui-monospace, Menlo, monospace; letter-spacing: .06em;
  color: var(--fx-c); border: 1px solid var(--fx-c); border-radius: var(--cwd-r, 3px); background: none; cursor: pointer;
  touch-action: none; -webkit-user-select: none; user-select: none; -webkit-touch-callout: none; }
.dk-fx__k[data-fx="brk"] { --fx-c: var(--fx-brk); }
.dk-fx__k[data-fx="tape"] { --fx-c: var(--fx-tape); }
.dk-fx__k[data-fx="t2"] { --fx-c: var(--fx-t2); }
.dk-fx__k[data-fx="t3"] { --fx-c: var(--fx-t3); }
.dk-fx__k:focus-visible { outline: 2px solid var(--fx-c); outline-offset: 2px; }
.dk-fx__k[aria-pressed="true"] { background: var(--fx-c); color: var(--paper); box-shadow: 0 0 9px -1px var(--fx-c); }
.dk-fx__k[aria-disabled="true"] { opacity: .4; cursor: default; }
/* a held key is a finger's width on every touch screen, the tablet's too (they
   were 31.7px wide at 768 and 1024) */
@media (pointer: coarse) { .dk-fx__k { height: 2.75rem; min-width: 2.75rem; } }
@media (max-width: 620px) { .dk-fx { flex: 1 1 auto; } .dk-fx__k { flex: 1 1 0; } }
/* under 361px the four chips cannot share a line with repeat and the dice and
   stay 44 wide, so they take a line of their own */
@media (max-width: 360px) { .dk-fx { flex: 1 1 100%; } }

.dk-grid { display: grid; gap: 1.6rem; grid-template-columns: repeat(3, minmax(0, 1fr));
           border-top: 1px solid var(--line-soft); padding-top: 1.1rem; }
.dk-col { min-width: 0; }
.dk-h { margin: 0 0 .5rem; font-size: .6rem; font-weight: 700; letter-spacing: .14em;
        text-transform: uppercase; color: var(--accent);
        display: flex; align-items: center; justify-content: space-between; gap: .5rem; min-height: 1.5rem; }
/* A KEY IN A COLUMN HEADING (the Space Reset, 483): it adds no row, and all
   three headings share the height so the columns' first rows stay level. */
.dk-h__k {
    font: inherit; font-size: .6rem; font-weight: 700; letter-spacing: .12em; text-transform: uppercase;
    height: 1.5rem; padding: 0 .5rem; border-radius: var(--cwd-r, 3px);
    border: 1px solid var(--line); background: none; color: var(--muted); cursor: pointer;
}
.dk-h__k:hover { border-color: var(--accent); color: var(--accent); }
.dk-h__k:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.dk-row { display: grid; grid-template-columns: 3.4rem minmax(0, 1fr) 4.4rem;
          align-items: center; gap: .5rem; margin-bottom: .4rem; font-size: .74rem; }
.dk-row > span { color: var(--muted); font-weight: 700; letter-spacing: .06em;
                 text-transform: uppercase; font-size: .58rem; }
.dk-row > b { text-align: right; font-variant-numeric: tabular-nums; color: var(--ink);
              font-size: .72rem; font-weight: 600; }
.dk-row--sub { opacity: .78; }
.dk-row--sub > span { font-size: .54rem; }
.dk-range { width: 100%; }
.dk-note { margin: .1rem 0 .6rem; font-size: .68rem; line-height: 1.45; color: var(--muted); }
.dk-curve { display: block; width: 100%; height: auto; margin: .5rem 0 .1rem; }
.dk-chips { display: flex; gap: .25rem; flex-wrap: wrap; margin-top: .5rem; }
/* THE CHIPS ARE LIT WHILE THE DECK IS AT THEM (484, deck.js chipPaint). Unlit
   is text alone: the 1px border stays, transparent, so a chip that lights does
   not move its neighbours. Lit is the deck's own pressed key, inverted — the
   rule .vz-tk uses (and .vz-fx__b used until 490, when a lit FX key took the
   mode keys' green ring instead), so it inverts again in the dark edition where
   the two tokens swap — and hovering a lit chip fills it with the accent, as
   the Play key does. Hover lives only where there is a hover: on a
   touch screen the last chip tapped kept an accent outline that read as lit.
   The keyboard ring is an accent outline OUTSIDE the box, which neither state
   draws. */
.dk-chip {
    -webkit-appearance: none; appearance: none;
    font: inherit; font-size: .64rem; font-weight: 700; letter-spacing: .06em;
    cursor: pointer; padding: .24rem .5rem; border-radius: 3px;
    border: 1px solid transparent; background: none; color: var(--muted);
}
.dk-chip[aria-pressed="true"] { background: var(--ink); border-color: var(--ink); color: var(--paper); }
@media (hover: hover) {
    .dk-chip:hover { border-color: var(--accent); color: var(--accent); }
    .dk-chip[aria-pressed="true"]:hover { background: var(--accent); border-color: var(--accent); color: var(--paper); }
}
.dk-chip:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.dk-meters { display: flex; gap: .9rem; margin: .7rem 0 .6rem; }
.dk-m { display: flex; flex-direction: column; gap: .05rem; }
.dk-m i { font-style: normal; font-size: .54rem; font-weight: 700; letter-spacing: .13em;
          text-transform: uppercase; color: var(--muted); }
.dk-m b { font-size: .8rem; font-variant-numeric: tabular-nums; color: var(--ink); }
.dk-reset { width: 100%; }

@media (max-width: 960px) { .dk-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 640px) {
    .dk-grid { grid-template-columns: minmax(0, 1fr); }
    .dk-top { grid-template-columns: minmax(0, 1fr); }
    .dk-platter { margin: 0 auto; }
}


/* =============================================================================
   THE CONTROLS — laband's Braun sliders, brought over whole
   -----------------------------------------------------------------------------
   Not re-drawn from scratch: this is the same restyle laband already carries,
   copied with its reasoning intact, because a second implementation of a
   slider is a second set of engine quirks to rediscover. It needs
   assets/js/ranges.js, which writes --lb-p (the value, 0..1) and --lb-o (the
   origin — 0.5 on a bipolar control, 0 otherwise) onto every range, and the
   rules below paint the fill BETWEEN those two. That is what makes a control
   centred on zero fill outward from the middle rather than from the left.
   ========================================================================== */
/* ========================================================== THE SLIDERS
   Ten range inputs on this page — a trim and three EQ bands on every card, and
   pitch, speed, level and three more bands on the deck — and until now not one
   of them was drawn. Every one arrived as the browser's default: a fat thumb
   on a pale groove, on a page that measures its own hairlines. The furthest
   the estate had gone was `accent-color: var(--ink)` at site.css:743, which is
   a colour, not a design; nothing more can be said about a native slider until
   it is taken apart with appearance: none.

   Taken apart, it is drawn the way everything else here is: a 2px rail, a
   small square-shouldered thumb, and the accent spent ONCE, on the filled part
   of the rail — the only thing on the control that is a value.

   THE FILL IS NOT GUESSED. js/ranges.js writes --lb-p (0..1) on each input
   from that input's own min/max/value, and the geometry below turns it into
   the exact pixel the thumb centre stands on. Where the control is BIPOLAR —
   pitch, and all six EQ bands, whose min is the negative of their max — the
   fill grows from the CENTRE outward, because a bar anchored at the left edge
   under a band that is cutting 6 dB states the opposite of what is true. */
input[type="range"] {
    /* The geometry lives in one place because the arithmetic below is DERIVED
       from it. Typed out twice, the thumb stops being centred the first time
       somebody changes a size and edits one of the two copies. */
    --lb-trk: 2px;     /* the rail                    */
    --lb-tw:  10px;    /* the thumb, ALONG the travel */
    --lb-th:  14px;    /* the thumb, ACROSS the rail  */
    --lb-p:   0;       /* where the value is,   0..1 — written by ranges.js */
    --lb-o:   0;       /* where the fill starts, 0..1 — 0 left, .5 centre   */

    --lb-rail: var(--line, rgba(30, 38, 43, .12));
    --lb-fill: var(--accent, #C2410C);
    --lb-tick: transparent;   /* only a bipolar rail gets a centre pip */

    /* THE THUMB TRAVELS INSET BY HALF ITS OWN WIDTH: its centre runs from
       tw/2 to (100% - tw/2), never 0 to 100%. A fill painted at a plain
       `--lb-p * 100%` therefore runs ahead of the thumb at one end and lags it
       at the other by tw/2 — five pixels, which on a 54px per-card slider is a
       fifth of the travel and reads as a control that cannot agree with
       itself. Both stops are computed in the thumb's own space:
              x = tw/2 + p x (100% - tw)                                    */
    --lb-x1: calc(var(--lb-tw) / 2 + min(var(--lb-p), var(--lb-o)) * (100% - var(--lb-tw)));
    --lb-x2: calc(var(--lb-tw) / 2 + max(var(--lb-p), var(--lb-o)) * (100% - var(--lb-tw)));

    -webkit-appearance: none; appearance: none;
    /* 18px = the 14px thumb plus its 2px shoulder above and below, exactly. At
       16px Chrome clips the shoulder against the input box; more reserves
       height the control does not use. */
    height: 18px;
    margin: 0; padding: 0; border: 0;
    background: transparent;
    vertical-align: middle;
    cursor: pointer;
    /* pan-y, so dragging the RAIL moves the value and dragging the page still
       scrolls it. Without this a horizontal drag on a phone does both.
       pinch-zoom, so a pinch that happens to start on a rail still zooms. */
    touch-action: pan-y pinch-zoom;
    -webkit-tap-highlight-color: transparent;
}
/* site.css:742 gives every range `margin: .35rem 0 0`, which is right under a
   stacked dial label and wrong inside a flex row: in .lb-ops__band it pushed
   the slider 5.6px below the LO/MID/HI label it belongs to. The base rule
   zeroes it for everybody; the deck's dials, which do want the air, ask for it
   back. (Note that site.css's own rule is 0,1,1 — the same specificity as the
   base rule above — so this sheet wins only because index.html loads it second
   at line 54. That is already true of every other range declaration here.) */
.dial input[type="range"] { margin-top: .35rem; }

/* ranges.js marks the sliders whose min is the negative of their max. The
   attribute only turns a colour on: the centre pip that says where zero is. */
input[type="range"][data-lb-bi="1"] { --lb-tick: var(--ink-55, rgba(30, 38, 43, .64)); }

/* THE TWO ENGINES CANNOT SHARE A RULE. A selector list containing a pseudo the
   engine does not know is INVALID AND THE WHOLE RULE IS DROPPED, so grouping
   ::-webkit-slider-runnable-track with ::-moz-range-track loses the track in
   both browsers rather than half of it in one. They are written twice on
   purpose; the values come from the same custom properties, so there is still
   only one place to change a number. Counted, rather than asserted: of the 77
   blocks in this restyle Blink parses 67 and drops exactly the 10 that are
   ::-moz- only, and none of the survivors lost a declaration. */
input[type="range"]::-webkit-slider-runnable-track {
    height: var(--lb-trk);
    border: 0; border-radius: 0;   /* a rail is a ruled line, not a pill */
    background-color: var(--lb-rail);
    background-image:
        /* the fill, painted between the origin and the value */
        linear-gradient(to right,
            transparent var(--lb-x1), var(--lb-fill) var(--lb-x1),
            var(--lb-fill) var(--lb-x2), transparent var(--lb-x2)),
        /* and, underneath it, the 1px centre pip — hidden by the thumb while
           the value IS zero, which is the one moment it has nothing to say */
        linear-gradient(to right,
            transparent calc(50% - .5px), var(--lb-tick) calc(50% - .5px),
            var(--lb-tick) calc(50% + .5px), transparent calc(50% + .5px));
}
input[type="range"]::-webkit-slider-thumb {
    -webkit-appearance: none; appearance: none;
    box-sizing: border-box;   /* so the forced-colors border cannot resize it */
    width: var(--lb-tw); height: var(--lb-th);
    border: 0; border-radius: 1px;
    background: var(--ink, #1E262B);
    /* THE SHOULDER IS STRUCTURAL. Ink on accent measures 2.97:1 in paper and
       2.80:1 in dark — both under the 3:1 a control needs against what is next
       to it — so where the thumb sits on top of its own fill the two would
       smear into one shape. 2px of page ground between them means neither has
       to carry contrast against the other. */
    box-shadow: 0 0 0 2px var(--paper, #ffffff);
    /* CHROME TOP-ALIGNS THE THUMB TO THE TRACK, so against a 2px rail it hangs
       6px low unless it is pulled up by half the difference:
              margin-top = (track - thumb) / 2 = (2 - 14) / 2 = -6px
       At pointer: coarse the thumb is 18px, so the same expression gives -8px,
       and under prefers-contrast: more the rail is 3px, giving -5.5px. That is
       why it is a calc and not a number: three sizes, one line, none of them
       able to drift. WebKit needs it too — at margin-top: 0 the thumb hangs
       below the rail there exactly as it does in Blink. */
    margin-top: calc((var(--lb-trk) - var(--lb-th)) / 2);
    transition: background-color .16s var(--ease), box-shadow .16s var(--ease);
}
/* Firefox centres its own thumb and needs no margin — and must not be given
   one, or it lands 6px high. */
input[type="range"]::-moz-range-track {
    height: var(--lb-trk);
    border: 0; border-radius: 0;
    background-color: var(--lb-rail);
    background-image:
        linear-gradient(to right,
            transparent var(--lb-x1), var(--lb-fill) var(--lb-x1),
            var(--lb-fill) var(--lb-x2), transparent var(--lb-x2)),
        linear-gradient(to right,
            transparent calc(50% - .5px), var(--lb-tick) calc(50% - .5px),
            var(--lb-tick) calc(50% + .5px), transparent calc(50% + .5px));
}
input[type="range"]::-moz-range-thumb {
    box-sizing: border-box;
    width: var(--lb-tw); height: var(--lb-th);
    border: 0; border-radius: 1px;
    background: var(--ink, #1E262B);
    box-shadow: 0 0 0 2px var(--paper, #ffffff);
    transition: background-color .16s var(--ease), box-shadow .16s var(--ease);
}
/* Firefox is the one engine that fills the rail itself, and it is worth
   having: it is the only fill on the page that survives with JavaScript off.
   But it can only ever fill FROM THE LEFT, so on the seven bipolar controls it
   would draw a half-full bar under a band that is doing nothing. There it is
   turned off and the gradient above — which knows where zero is — does the
   work in every engine alike. */
input[type="range"]::-moz-range-progress {
    height: var(--lb-trk);
    border: 0; border-radius: 0;
    background: var(--lb-fill);
}
input[type="range"][data-lb-bi="1"]::-moz-range-progress { background: transparent; }
input[type="range"]::-moz-focus-outer { border: 0; }

/* THE STATES. There is no hover: a colour flicker on a 10px object is not
   information, and the cursor has already said the thing is movable. :active
   is the one moment worth marking — the shoulder takes the accent, so the
   control lights where the hand is touching it and nothing else moves. */
input[type="range"]:active::-webkit-slider-thumb { box-shadow: 0 0 0 2px var(--accent, #C2410C); }
input[type="range"]:active::-moz-range-thumb     { box-shadow: 0 0 0 2px var(--accent, #C2410C); }

/* The house ring at a smaller offset than site.css:749's 4px. The ring sits
   2px + 2px = 4px outside the box; the gap from a slider to the next band's
   label is .34rem = 5.44px, so it clears by 1.44px. At the inherited 4px
   offset it stood 6px proud and struck the label, and a focus ring that
   damages the thing beside it reads as a bug rather than as focus. */
input[type="range"]:focus { outline: none; }
input[type="range"]:focus-visible { outline: 2px solid var(--accent, #C2410C); outline-offset: 2px; }

/* A KEY THAT IS DOING SOMETHING STAYS LIT. Source and FX are STATE, not
   actions: which input is feeding the page, and which effects are on. A control
   that looks the same engaged as idle is one people press twice — so the
   engaged state is a full inversion, not a tint, and it carries a lamp so it
   reads at a glance and in a photograph. */
.cwd__b, .vz-fx__b {
    /* A <button> KEEPS ITS NATIVE CHROME until appearance is taken off it, and
       on the engines that still honour it the platform control paints over any
       background the sheet asks for. Everything else about these keys was
       styled; this was the one property nobody had needed to set until a key
       had to fill. */
    -webkit-appearance: none; appearance: none;
}
.cwd__b[aria-pressed="true"] {
    background: var(--ink); border-color: var(--ink); color: var(--paper);
}
.cwd__b[data-src] { position: relative; padding-left: 1.5rem; }
.cwd__b[data-src]::before {
    content: ''; position: absolute; left: .62rem; top: 50%; margin-top: -3px;
    width: 6px; height: 6px; border-radius: 50%;
    border: 1px solid var(--line); background: none;
}
.cwd__b[data-src][aria-pressed="true"]::before {
    background: var(--accent); border-color: var(--accent);
    box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 28%, transparent);
}
/* AN FX KEY THAT IS ON IS CHOSEN, AND CHOSEN IS GREEN (490). The FX keys
   used to take the source keys' full inversion, which put a black block among
   sixteen outlines beside a mode strip whose lit keys are green rings — two
   ways of saying "this one" on one panel. They now say it the way the mode
   keys do (the .vz-mkey rule below carries them). */
.vz-fx__b--none { border-style: dashed; }

/* ================================================ THE CURTAIN ============== *
   THE ELEMENT AND THE HANDLERS SHIPPED WITHOUT ANY CSS, which is the quietest
   way a feature can be missing: the markup is in the page, the drag handlers
   set data-on exactly as they should, and nothing appears — so it reads as a
   drag that the page ignored rather than as a stylesheet with a hole in it.

   It is pointer-events: none, and that is load-bearing. A full-screen overlay
   that accepts pointer events becomes the drop target itself and swallows the
   file; it also fires dragleave on the document the instant it appears, which
   makes it strobe as the cursor moves.
   ========================================================================== */
.vz-curtain {
    position: fixed; inset: 0; z-index: 1400;
    display: flex; align-items: center; justify-content: center;
    pointer-events: none; opacity: 0;
    background: color-mix(in srgb, var(--paper) 82%, transparent);
    -webkit-backdrop-filter: blur(3px); backdrop-filter: blur(3px);
    transition: opacity .14s ease;
}
.vz-curtain[data-on] { opacity: 1; }
.vz-curtain__in {
    text-align: center; padding: 2rem 2.6rem;
    border: 2px dashed var(--accent); border-radius: 10px;
    background: var(--paper);
    box-shadow: 0 18px 60px rgba(0, 0, 0, .18);
    transform: scale(.97);
    transition: transform .14s ease;
}
.vz-curtain[data-on] .vz-curtain__in { transform: scale(1); }
.vz-curtain__in b {
    display: block; font-size: 1.35rem; font-weight: 600;
    letter-spacing: -.02em; color: var(--ink);
}
.vz-curtain__in span {
    display: block; margin-top: .3rem; font-size: .84rem; color: var(--muted);
}
@media (prefers-reduced-motion: reduce) {
    .vz-curtain, .vz-curtain__in { transition: none; }
}

/* ============================================== THE TRANSPORT ============== *
   Six glyph keys beside the stage, mirroring the deck's own. They are square
   because a symbol has no natural width, and they sit in the mode heading
   because that is the row you are already looking at while the picture runs.
   ========================================================================== */
/* no auto margin of its own: in the mode row the tail (.vz-tail) carries it,
   and the deck's copy of this cluster sits at the start of its line */
.vz-transport { display: inline-flex; gap: 3px; margin-left: 0; }
.vz-tk {
    width: var(--cwd-h, 2.125rem); min-width: var(--cwd-h, 2.125rem);
    padding: 0; flex: 0 0 auto;
}
.vz-tk svg { width: 1rem; height: 1rem; }
/* THE WIDTH HAS TO GROW WITH THE HEIGHT. The house raises --cwd-h to 2.75rem on
   a touch screen, and these keys take their HEIGHT from that token and their
   WIDTH from a second reference to it that the coarse block never re-evaluates
   — so six of the most-used keys on the page ended up 34px wide and 44px tall:
   a target you can hit vertically and miss horizontally, which is the harder of
   the two to notice and the easier of the two to blame on yourself. */
@media (pointer: coarse) {
    .vz-tk { width: 2.75rem; min-width: 2.75rem; }
    /* The dice beside it had the same fault, worse: 20px wide, 44 tall. */
    .vz-dice { width: 2.75rem; min-width: 2.75rem; padding: 0; }
}
/* THE STAGE HINT, BY POINTER: keys and right-click where it is fine, taps and the
   long-press where it is coarse. */
.vz-hint__coarse { display: none; }
/* a clause of the fine hint never breaks inside itself: at 820 the line wrapped
   with "more" alone on the second */
.vz-hint__c { white-space: nowrap; }
.console__body > .vz-hint { text-wrap: balance; }
@media (pointer: coarse) {
    .vz-hint__fine { display: none; }
    .vz-hint__coarse { display: inline; }
}
/* THE CONSOLE'S ROW GAP IS FOR PANELS, NOT FOR A LINE OF SMALL PRINT (490):
   the hint sat 28px under the slot bar with the body's 19px pad under it — a
   strip of blank card below the last key. It closes up to the rack and eats
   into the pad. The slot bar and the FX row lost their own .9rem margins too,
   which stacked on top of the same gap. */
.console__body > .vz-hint { margin: -.55rem 0 -.45rem; }
.vz-tk[aria-pressed="true"] { background: var(--ink); border-color: var(--ink); color: var(--paper); }
/* The transport, the dice and Shuffle travel together in .vz-tail, which
   carries the auto margin and the gaps; nothing inside it has a margin. */
.vz-shuf { margin-left: 0; font-size: .58rem; height: var(--cwd-h, 2.125rem); padding: 0 .7rem; }
@media (max-width: 620px) {
    .vz-transport { margin-left: 0; flex-basis: 100%; justify-content: space-between; }
    .vz-shuf { margin-left: 0; }
}

/* =============================================== THE MODE PANEL ============ *
   A FAMILY RAIL OVER A FILTERED GRID. Twenty-six keys laid flat, the count when
   this was measured, were most of a phone screen, and there are forty-five
   now; behind a <select> they are invisible and ungrouped. One family
   at a time is the only arrangement that is neither, and the grid's own height
   does the work of keeping the panel a fixed object rather than something that
   grows and shrinks under a thumb already reaching for it.

   COLUMN COUNT GATES ON WIDTH, KEY HEIGHT GATES ON POINTER, and keeping those
   two apart is the whole trick. A phone held sideways is 844px wide and still
   coarse: put the column count in the pointer query and that phone gets two
   420px keys. Height in the pointer query can never cause overflow, so it is
   safe there and nowhere else.
   ========================================================================== */
.vz-desk__f--wide { grid-column: 1 / -1; }
.vz-modep { display: grid; gap: var(--cwd-gap, .4rem); min-width: 0; }
/* NINE CHIPS IN A ROW OF EIGHT LEAVES ONE ON ITS OWN, and a whole row of
   whitespace beside it. The rail was fixed at eight because eight divided
   cleanly; adding Reactors made nine and orphaned it. It flows now — the chips
   are only as wide as their words and wrap when they run out of room, so the
   count can change again without redesigning the rail. */
.vz-modeg {
    display: flex; flex-wrap: wrap;
    gap: var(--cwd-gap, .4rem);
}
.vz-modeg .vz-gchip { flex: 0 1 auto; padding: 0 .55rem; }
/* Nine columns rather than seven: the keys are short words and this is the
   difference between four rows of them and three. */
.vz-modek {
    display: grid; grid-template-columns: repeat(9, minmax(0, 1fr));
    gap: var(--cwd-gap, .4rem);
}
/* -------------------------------------------------------- THE INK SWATCHES
   FOURTEEN SQUARES IN TWO ROWS OF SEVEN, in a menu that is otherwise a column
   of words — which is the point: a colour is the one thing on this desk that a
   name cannot tell you. Each swatch carries its OWN look, gradients included,
   so the four that are not flat colours declare themselves before they are
   pressed rather than after.

   The chosen one is ringed rather than filled, because filling it would cover
   the colour it is there to show. */
.vz-sw {
    display: grid; grid-template-columns: repeat(7, 1fr);
    gap: 4px; padding: .3rem .6rem .15rem;
}
.vz-sw__k {
    -webkit-appearance: none; appearance: none;
    width: 100%; aspect-ratio: 1 / 1; min-height: 20px;
    padding: 0; border-radius: 4px; cursor: pointer;
    /* a hairline in the ink's own colour at low alpha, so a white swatch on a
       white menu still has an edge */
    border: 1px solid rgba(128, 128, 128, .38);
    box-shadow: none;
    transition: transform .1s ease, box-shadow .12s ease;
}
.vz-sw__k:hover { transform: scale(1.12); }
.vz-sw__k:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.vz-sw__k[aria-checked="true"] {
    box-shadow: 0 0 0 2px var(--paper), 0 0 0 3.5px var(--accent);
}
.vz-sw__n {
    margin: .3rem .6rem .1rem; text-align: center;
    font-size: .58rem; font-weight: 700; letter-spacing: .12em;
    text-transform: uppercase; color: var(--muted);
}
@media (pointer: coarse) {
    .vz-sw { gap: 6px; }
    .vz-sw__k { min-height: 30px; }
}

/* ------------------------------------------------------ THE TILE STRIP
   Braun keys: square, flat, unlit until they are the answer. The lit key is
   filled with the accent and given a soft halo of the same colour, because on
   a panel where six other things are also outlined a border alone is not a
   difference you see without looking for it. The halo is drawn OUTSIDE the key
   (box-shadow with a negative spread), so it costs no layout and cannot move
   the row when it appears. */
/* SQUARES, NOT TRACKS. Laid on the mode grid the keys stretched to eighty
   pixels wide and twenty-six tall to hold a single digit — thirteen wide flat
   slabs, which is the opposite of the thing being asked for. A fixed square
   that does not care how many of it there are is the Braun answer: the row is
   as long as the stage is big, it wraps on a phone without a rule about it, and
   every key is the same object whether there are four or twelve. */
.vz-tsel[hidden] { display: none !important; }
/* IT RIDES THE HEADING ROW, not a row of its own. Five keys did not justify a
   whole line of panel height, and the strip belongs to the sentence beside it —
   "Mode · Waterfall", and these are the tiles that sentence is about. */
.vz-tsel {
    display: flex; flex-wrap: wrap; align-items: center;
    gap: .3rem;
    margin: 0 .35rem;
}
.vz-tsel__k {
    -webkit-appearance: none; appearance: none;
    font: inherit; font-size: .65rem; font-weight: 700;
    letter-spacing: .02em; text-transform: uppercase;
    font-variant-numeric: tabular-nums;
    /* ONE KEY HEIGHT ACROSS THE ROW. Sharing a line with the six transport keys
       means sharing their height, or the row reads as two rows that failed to
       line up. */
    width: var(--cwd-h, 2.125rem); height: var(--cwd-h, 2.125rem);
    flex: 0 0 auto; padding: 0;
    display: inline-flex; align-items: center; justify-content: center;
    line-height: 1;
    color: var(--muted); background: none;
    border: 1px solid var(--line); border-radius: var(--cwd-r, 3px);
    cursor: pointer;
    transition: color .12s ease, border-color .12s ease,
                background-color .12s ease, box-shadow .16s ease;
}
.vz-tsel__k:hover { color: var(--accent); border-color: var(--accent); }
.vz-tsel__k[aria-checked="true"] {
    color: var(--paper);
    background: var(--accent);
    border-color: var(--accent);
    box-shadow: 0 0 0 1px var(--accent), 0 0 9px -1px var(--accent);
}
/* ALL IS NOT A TILE, so it is not a numbered square. Same height and the same
   key, wider by the three letters it holds, and set off by a gap of its own —
   grouping by space rather than by a rule, which is the only separator that
   costs the panel nothing. */
.vz-tsel__k[data-all] {
    width: auto; min-width: 2.9rem; padding: 0 .5rem;
    letter-spacing: .06em; white-space: nowrap;
    margin-right: .35rem;
}
/* A FINGER IS 44 PIXELS WIDE. A 27px square is a mouse target and a miss on a
   phone, and the fix is not a smaller gap — it is a bigger key and one more
   row. Twelve numbers plus ALL wrap to three rows on a 375px screen, which is
   the honest cost of twelve tiles — and twelve is the stage's first-visit
   default since 483 (#26), so it is what a phone opens on; at one or four
   tiles it is a single row.

   GATED ON THE POINTER, NOT THE WIDTH — a narrow desktop window still has a
   mouse, and every screen ever made is at some point 560px wide. */
@media (pointer: coarse) {
    .vz-tsel { gap: .34rem; }
    .vz-tsel__k { width: 2.75rem; height: 2.75rem; font-size: .72rem; }
    .vz-tsel__k[data-all] { width: auto; min-width: 3.4rem; padding: 0 .6rem; }
}
/* on a phone the strip takes the whole line under the heading, the same way the
   transport does — three things on one line is a desk, not a phone */
@media (max-width: 700px) {
    .vz-tsel { flex-basis: 100%; margin: .15rem 0 0; }
}
.vz-gchip, .vz-mkey {
    -webkit-appearance: none; appearance: none;
    font: inherit; font-size: .625rem; font-weight: 700;
    letter-spacing: .04em; text-transform: uppercase;
    height: var(--cwd-h, 2.125rem); min-width: 0; padding: 0 .3rem;
    display: inline-flex; align-items: center; justify-content: center;
    text-align: center; line-height: 1.1;
    color: var(--muted); background: none;
    border: 1px solid var(--line); border-radius: var(--cwd-r, 3px);
    cursor: pointer; overflow: hidden;
    /* background-COLOR, not the shorthand (484): a lit split part's green side bars
       are background-image gradients, and the shorthand animated their position and
       size — twelve keys sliding their bars in from the top-left corner over 120 ms. */
    transition: background-color .12s, border-color .12s, color .12s;
}
.vz-gchip:hover, .vz-mkey:hover { border-color: var(--accent); color: var(--accent); }
.vz-gchip:focus-visible, .vz-mkey:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) { .vz-gchip, .vz-mkey { transition: none; } }
/* A BRAUN LIT STATE IS A FULL INVERSION, never a tint: the shipped accent-on-ink
   lamp measured 2.2:1 on the paper edition, which is a rumour and not an
   indicator. */
/* WHAT IS CHOSEN IS GREEN. The rail was a row of identical outlines whose lit
   one differed only by being filled, and the mode keys used the accent — which
   on this page is also the colour of the aim ring, the live dot and half the
   drawn output. Green says "this one" and is not competing with anything the
   modes themselves draw. */
.vz-gchip[aria-selected="true"] {
    background: transparent; border-color: #15803D; color: #15803D;
    box-shadow: inset 0 0 0 1px #15803D;
}
.vz-mkey[aria-pressed="true"],
.vz-fx__b[aria-pressed="true"] {
    background: transparent; border-color: #15803D; color: #15803D;
    box-shadow: inset 0 0 0 1px #15803D;
}
html[data-dark] .vz-gchip[aria-selected="true"],
html[data-dark] .vz-mkey[aria-pressed="true"],
html[data-dark] .vz-fx__b[aria-pressed="true"] {
    border-color: #4ADE80; color: #4ADE80; box-shadow: inset 0 0 0 1px #4ADE80;
}
.vz-mkey[hidden] { display: none; }
/* CLASH, ONE KEY IN EQUAL PARTS — four since 483, see CLASH IN FOUR below; the
   widths quoted in this note are 482's three. The box is ONE track of the mode grid,
   the slot the Clash key always had, split by a grid of three equal fractions —
   so the parts are equal by construction rather than by the length of their
   labels, and the parts are about 38px at 320 and 39px at 1440. It is not two
   tracks: measured, a two-track box that fell on a row's last column wrapped and
   left a hole at two, three, four and six columns (All modes, page one, with
   WORD alone on its row), and every page held 21 tracks against 20. Borders are
   shared (the inner ones are dropped), so the three read as one key; the lit
   part is the same green as any key. */
.vz-msplit {
    min-width: 0;
    display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 0;
}
.vz-msplit[hidden] { display: none; }
.vz-msplit > .vz-mkey {
    flex-direction: column; gap: 1px; padding: 0 .1rem; line-height: 1;
    border-radius: 0;
}
.vz-msplit > .vz-mkey + .vz-mkey { border-left-width: 0; }
/* a shared edge has no left border to light, so a hovered inner part draws its
   own left edge in the accent — otherwise it lit on three sides only */
.vz-msplit > .vz-mkey + .vz-mkey:hover:not([aria-pressed="true"]) { box-shadow: inset 1px 0 0 var(--accent); }
.vz-msplit > .vz-mkey:first-child { border-radius: var(--cwd-r, 3px) 0 0 var(--cwd-r, 3px); }
.vz-msplit > .vz-mkey:last-child { border-radius: 0 var(--cwd-r, 3px) var(--cwd-r, 3px) 0; }
.vz-msplit > .vz-mkey:focus-visible { outline-offset: -3px; }
.vz-msplit__w { font-size: .5rem; letter-spacing: .1em; opacity: .78; }
.vz-msplit__n { font-size: .72rem; letter-spacing: .04em; }
/* CLASH IN FOUR: I, II and III, and IV, the original from 481 (labs 483).
   A part of a four-way key cannot carry the word: CLASH measures 30.8px, and a
   part is 28.5px at 320, 34.8 to 40.9 at six columns, 26.7 to 29.5 at nine and
   23.9 to 26.4 at ten (measured), so the word would clip in almost every part.
   So the key says it ONCE, across the four, and each part keeps its numeral
   exactly where it was. The word is the box's own generated text (its
   data-split, upper-cased) laid over the tops of the parts with pointer-events
   off, so every part is still its full height to press; its alt text is empty,
   because each part already names itself ("Clash IV — Original").
   THE LINES STAY UNDER THE WORD. Centred over four parts the word always
   straddles the middle edge, so no part draws a full-height side any more: the
   dividers, the hover edge and the lit sides run up the lower half only, under
   the numerals, and the top and bottom edges carry the rest. A patch of ground
   behind the word was tried first and measured wrong: these keys sit on the
   tinted band under the console's veil, 245 against a patch of 250 on paper, and
   any ground copied here would drift the same way. It turns green with
   whichever part is chosen. The per-part words stay in the DOM, unseen. */
.vz-msplit { position: relative; }
.vz-msplit > .vz-mkey .vz-msplit__w { visibility: hidden; }
.vz-msplit::before {
    content: attr(data-split);
    content: attr(data-split) / "";
    position: absolute; z-index: 1; pointer-events: none;
    left: 0; right: 0; width: max-content; margin: 0 auto;
    top: calc((100% - .72rem - 1px) / 2 - .25rem);
    font-size: .5rem; font-weight: 700; line-height: 1;
    letter-spacing: .1em; text-indent: .1em; text-transform: uppercase;
    color: var(--muted);
    color: color-mix(in srgb, var(--muted) 78%, transparent);
}
.vz-msplit:has(> .vz-mkey[aria-pressed="true"])::before { color: #15803D; }
html[data-dark] .vz-msplit:has(> .vz-mkey[aria-pressed="true"])::before { color: #4ADE80; }
.vz-msplit > .vz-mkey:not(:last-child) { border-right-width: 0; }
.vz-msplit > .vz-mkey + .vz-mkey { background: linear-gradient(var(--line), var(--line)) 0 100% / 1px 50% no-repeat; }
.vz-msplit > .vz-mkey + .vz-mkey:hover:not([aria-pressed="true"]) {
    box-shadow: none; background-image: linear-gradient(var(--accent), var(--accent));
}
.vz-msplit > .vz-mkey[aria-pressed="true"] {
    border-left-color: var(--line); border-right-color: var(--line);
    box-shadow: inset 0 1px 0 #15803D, inset 0 -1px 0 #15803D;
    background: linear-gradient(#15803D, #15803D) 0 100% / 2px 50% no-repeat,
                linear-gradient(#15803D, #15803D) 100% 100% / 2px 50% no-repeat;
}
html[data-dark] .vz-msplit > .vz-mkey[aria-pressed="true"] {
    border-left-color: var(--line); border-right-color: var(--line);
    box-shadow: inset 0 1px 0 #4ADE80, inset 0 -1px 0 #4ADE80;
    background: linear-gradient(#4ADE80, #4ADE80) 0 100% / 2px 50% no-repeat,
                linear-gradient(#4ADE80, #4ADE80) 100% 100% / 2px 50% no-repeat;
}
/* a part after a lit part drops its divider (the lit part draws that edge),
   unless it is lit too, when its own side bars are the edge (484) */
.vz-msplit > .vz-mkey[aria-pressed="true"] + .vz-mkey:not(:hover):not([aria-pressed="true"]) { background: none; }
/* A SPLIT KEY OF ANY SIZE (483). Clash is four parts and Songbuilder two; the
   box counts its own and app.js writes the count as --parts, so the parts stay
   equal by construction. Four, above, is the fallback. */
.vz-msplit { grid-template-columns: repeat(var(--parts, 4), minmax(0, 1fr)); }

@media (max-width: 1180px) { .vz-modek { grid-template-columns: repeat(6, minmax(0, 1fr)); } }
@media (max-width: 900px)  { .vz-modek { grid-template-columns: repeat(4, minmax(0, 1fr)); }
                             .vz-modeg { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
@media (max-width: 620px)  { .vz-modek { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
/* 460 is set by TYPE, not by taste: 'Tuned Between' is the longest label and
   needs about 108px of key, which three columns stop delivering below here. */
@media (max-width: 460px)  { .vz-modek { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

/* ============================================== THE SOURCE KEYS ============ *
   FOUR SQUARE KEYS CARRYING FOUR SYMBOLS. Square because a symbol has no
   natural width and four equal squares are the only arrangement that cannot
   look accidental; and because a square key is the same target in both axes,
   which is what makes the 44px floor easy rather than a special case.

   The lamp is the key itself. A tinted dot on an untinted key measured 2.2:1 on
   the paper edition — not an indicator, a rumour — so the engaged state is the
   full inversion the rest of this panel uses, and the symbol inverts with it
   because it is drawn in currentColor.
   ========================================================================== */
/* FIVE, NOT FOUR. The source row gained the shelf key; a grid pinned at four
   columns would have wrapped it onto a line of its own under the other three.
   Named .vz-quad still because every other rule in this sheet refers to it. */
.vz-quad { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: var(--cwd-gap, .4rem); }
@media (max-width: 420px) { .vz-quad { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
/* SIX: the four sizes, the stage dice (483) and fullscreen, one row at every width */
.vz-desk__f--stage .vz-keys { display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); gap: var(--cwd-gap, .4rem); }
.vz-desk__f--stage .cwd__b { flex: none; min-width: 0; }
.vz-ik {
    display: inline-flex; align-items: center; justify-content: center;
    padding: 0; min-width: 0;
}
.vz-ik svg { width: 1.15rem; height: 1.15rem; display: block; }
/* THE SYMBOL IS THE ONLY THING ON THE KEY, so the key must not also reserve
   room for the label text that is no longer there. */
.vz-ik[data-src]::before { content: none; }
.vz-ik[data-src] { padding-left: 0; }
/* the word that says what the symbol means, live, in the band heading — so the
   glyph is never the only thing telling you what is playing */
.vz-srcw {
    font-weight: 700; letter-spacing: .1em; color: var(--accent);
    margin-left: .45rem;
}

/* ============================================== SENSITIVITY ================ *
   Four keys and a lamp. Not every mode moves the same amount for the same
   music — a band that idles at 0.3 and peaks at 0.5 draws a picture that barely
   breathes, and the reader concludes the MODE is broken rather than that the
   track is quiet there. One gain in front of every mode fixes it in the one
   place it can be fixed without every mode implementing it separately.
   ========================================================================== */
.vz-mcell--sens { grid-column: span 2; }
.vz-sens { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 3px; }
.vzs {
    -webkit-appearance: none; appearance: none;
    font: inherit; font-size: .55rem; font-weight: 700;
    letter-spacing: .07em; text-transform: uppercase;
    padding: .3rem .1rem; min-width: 0;
    border: 1px solid var(--line); border-radius: var(--cwd-r, 3px);
    background: none; color: var(--muted); cursor: pointer;
}
.vzs:hover { border-color: var(--accent); color: var(--accent); }
.vzs[aria-checked="true"] { background: var(--ink); border-color: var(--ink); color: var(--paper); }
/* THE LAMP IS GREEN AND IT IS THE ONLY GREEN ON THE PAGE, which is what makes
   it read as an indicator rather than as decoration. It is lit only while AUTO
   is actually holding the gain — not merely selected. */
.vz-lamp {
    width: 7px; height: 7px; border-radius: 50%; display: inline-block; flex: 0 0 auto;
    border: 1px solid var(--line); background: none;
    transition: background .2s ease, box-shadow .2s ease;
}
.vz-lamp[data-on] {
    background: #34d17a; border-color: #34d17a;
    box-shadow: 0 0 0 2px color-mix(in srgb, #34d17a 26%, transparent);
}
.vz-menu__h--sub { border-top: 1px solid var(--line-soft); border-bottom: 0; margin-top: .1rem; }
.vz-menu__sens { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 3px; padding: 0 .6rem .5rem; }
@media (pointer: coarse) {
    .vzs { min-height: 2.75rem; display: inline-flex; align-items: center; justify-content: center; }
}
@media (max-width: 620px) {
    .vz-mcell--sens { grid-column: span 2; }
    .vz-mcell--crest { grid-column: span 2; }
}

/* ================================================= THE QUEUE =============== *
   It only takes up room once there is something in it. An empty queue is one
   line of type; a full one is a list you can reorder by playing out of it.
   ========================================================================== */
.dk-queue { margin: .9rem 0 .2rem; padding-top: .85rem; border-top: 1px solid var(--line-soft); }
.dk-queue__top { display: flex; align-items: center; justify-content: space-between; gap: .6rem; flex-wrap: wrap; }
.dk-queue__top p { margin: 0; }
/* S / M / L (#8): how many rows a list shows. The keys sit in the list's heading
   line, which clips and ellipses; there the words ellipse on their own, and the
   keys and their focus rings are never cut. */
.dk-queue__top .vz-mcell__l, .dk-lib__top .vz-mcell__l { overflow: visible; min-width: 0; }
.dk-queue__top .vz-srcw, .dk-lib__hn { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dk-sz { display: inline-flex; flex: 0 0 auto; gap: 2px; margin-left: .45rem; vertical-align: middle; }
.dk-sz__k { -webkit-appearance: none; appearance: none; width: 1.375rem; height: 1.375rem; padding: 0;
  font: 700 .55rem/1 'DM Sans', system-ui, sans-serif; letter-spacing: 0; color: var(--muted); background: none;
  border: 1px solid var(--line); border-radius: var(--cwd-r, 3px); cursor: pointer; }
.dk-sz__k:hover { border-color: var(--accent); color: var(--accent); }
.dk-sz__k:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.dk-sz__k[aria-checked="true"] { background: var(--ink); border-color: var(--ink); color: var(--paper); }
@media (pointer: coarse) { .dk-sz__k { width: 2.75rem; height: 2.75rem; font-size: .65rem; } }
/* minmax(0, 1fr), NOT THE IMPLICIT auto COLUMN. An auto track is as wide as
   the widest row's min-content, and a row is one line of nowrap text and keys —
   so a long name, or a skipped row's reason, widened the whole deck past a
   320px screen instead of ellipsing inside it. */
.dk-qlist { list-style: none; margin: .6rem 0 0; padding: 0; display: grid; gap: .3rem;
            grid-template-columns: minmax(0, 1fr); }
/* A LONG LINE SCROLLS INSIDE THE DECK. Two thousand queued tracks made the
   page 100,000px tall and pushed every control under it out of reach. Only a
   page of rows is drawn (app.js), and the list is a window onto them. */
/* position: relative is not decoration. Each E1-E4 tag carries a visually
   hidden " manifest" span, which is absolutely positioned; without a
   positioned scroller its containing block was above the box, so every drawn
   row's span escaped the clip and stretched the page by the rows' height. */
/* S / M / L (#8): the window is 5, 10 or 20 rows and the gaps between them, and
   a row is exactly --dk-q-rh (40px, 56px on touch) unless its content needs more.
   L never grows past the screen less 12rem, and never under M. */
.dk-qlist { position: relative; overflow-y: auto; overscroll-behavior: contain; align-content: start;
            --dk-q-n: 5; --dk-q-rh: 2.5rem;
            max-height: calc(var(--dk-q-rh) * var(--dk-q-n) + .3rem * (var(--dk-q-n) - 1)); }
.dk-qlist[data-sz="m"] { --dk-q-n: 10; }
.dk-qlist[data-sz="l"] { --dk-q-n: 20;
            max-height: min(calc(var(--dk-q-rh) * 20 + 5.7rem), max(calc(var(--dk-q-rh) * 10 + 2.7rem), calc(100svh - 12rem))); }
@media (pointer: coarse) { .dk-qlist { --dk-q-rh: 3.5rem; } }
.dk-qitem--more { counter-increment: none; border-style: dashed; }
.dk-qitem--more::before { content: '\2026'; }
.dk-qitem--more .dk-qitem__n { color: var(--muted); }
.dk-qitem {
    display: flex; align-items: center; gap: .5rem; min-width: 0;
    box-sizing: border-box; min-height: var(--dk-q-rh, 2.5rem);
    padding: .2rem .3rem .2rem .6rem;
    border: 1px solid var(--line); border-radius: var(--cwd-r, 3px);
    counter-increment: q;
}
/* on a phone the reason a track was skipped takes its own line under the name,
   so neither is cut to nothing */
@media (max-width: 480px) {
    .dk-qitem--skip { flex-wrap: wrap; row-gap: .15rem; }
    .dk-qitem--skip .dk-qitem__why { order: 9; flex: 1 1 100%; max-width: none; white-space: normal; padding-left: 1.6em; }
}
.dk-qitem::before {
    content: counter(q); flex: 0 0 auto;
    font-size: .6rem; font-weight: 700; color: var(--muted);
    font-variant-numeric: tabular-nums; min-width: 1.1em;
}
.dk-qitem__n {
    flex: 1 1 auto; min-width: 0; font-size: .8rem; color: var(--ink);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
    /* #11: a double-click or a double-tap plays it, and selects no words */
    -webkit-user-select: none; user-select: none; touch-action: manipulation; cursor: default;
}
.dk-qitem__b {
    -webkit-appearance: none; appearance: none; flex: 0 0 auto;
    font: inherit; font-size: .62rem; font-weight: 700;
    letter-spacing: .06em; text-transform: uppercase;
    padding: .3rem .55rem; border-radius: var(--cwd-r, 3px);
    border: 1px solid var(--line); background: none; color: var(--muted);
    cursor: pointer;
}
.dk-qitem__b:hover { border-color: var(--accent); color: var(--accent); }
.dk-qitem__b--x { font-size: .95rem; line-height: 1; padding: .2rem .5rem; }
.dk-qitem__b--g { display: inline-flex; align-items: center; justify-content: center; padding: .3rem .5rem; }
.dk-qitem__b--g svg { width: .72rem; height: .72rem; display: block; }
/* the one key on the queue row that DELETES something was also the smallest —
   26px wide, beside a 44px-tall neighbour */
@media (pointer: coarse) {
    .dk-qitem__b--x { min-width: 2.75rem; padding: .2rem .6rem; }
    .dk-qitem__b--g { min-width: 2.75rem; padding: 0; }
}
.dk-qhint { margin: .55rem 0 0; font-size: .72rem; color: var(--muted); }
/* THE QUEUE'S OWN DROP TARGET. The page-wide drop has to guess play-or-queue
   from whether something is already running; dropping on this cannot be
   ambiguous, which is the whole reason it is worth the room. */
.dk-qdrop {
    display: block; margin-top: .6rem; padding: .8rem .9rem;
    text-align: center; cursor: pointer;
    border: 1px dashed var(--line); border-radius: var(--cwd-r, 3px);
    transition: border-color .14s ease, background .14s ease;
}
.dk-qdrop b { display: block; font-size: .82rem; color: var(--ink); }
.dk-qdrop span { display: block; font-size: .7rem; color: var(--muted); margin-top: .15rem; }
.dk-qdrop:hover, .dk-qdrop[data-over] { border-color: var(--accent); }
.dk-qdrop[data-over] { background: color-mix(in srgb, var(--accent) 7%, transparent); }
.dk-qdrop[data-over] b { color: var(--accent); }
.dk-qlist { counter-reset: q; }
@media (pointer: coarse) {
    .dk-qitem__b { min-height: 2.75rem; display: inline-flex; align-items: center; }
}
/* WHERE A QUEUED TRACK CAME FROM. A dropped file needs no tag — it came from
   the reader's own disk — but a row from one of the four manifests says which,
   in the same small caps as the deck's other readouts. */
.dk-qitem__o {
    flex: 0 0 auto; font-size: .56rem; font-weight: 700; letter-spacing: .1em;
    padding: .1rem .3rem; border: 1px solid var(--line); border-radius: 3px;
    color: var(--muted); font-variant-numeric: tabular-nums; line-height: 1.3;
}
/* THE TRACK BEING FETCHED, AND THE ONES THAT COULD NOT BE. Neither is waiting,
   so neither takes a number: the counter is the running order and only the
   rows still in it count. */
.dk-qitem--load, .dk-qitem--skip { counter-increment: none; }
.dk-qitem--load { position: relative; overflow: hidden; border-style: dashed; }
.dk-qitem--load::before { content: '\2193'; color: var(--accent); }
.dk-qitem--skip::before { content: '\00d7'; color: var(--accent); font-size: .8rem; }
.dk-qitem--skip .dk-qitem__n { color: var(--muted); text-decoration: line-through; }
.dk-qitem__why {
    flex: 0 1 auto; min-width: 0; max-width: 46%;
    font-size: .64rem; color: var(--accent); font-variant-numeric: tabular-nums;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.dk-qitem--load .dk-qitem__why { color: var(--muted); }
.dk-qitem__bar {
    position: absolute; left: 0; bottom: 0; height: 2px; width: 0;
    background: var(--accent); transition: width .2s linear;
}
@media (prefers-reduced-motion: reduce) { .dk-qitem__bar { transition: none; } }

/* ================================================= THE LIBRARY ============= *
   The Audio Engine's four manifests, one at a time, on the same line of type
   as the queue above it: a heading with its count in the accent, four keys
   that latch, a search, and a list. The list is a fixed window onto however
   many names there are, so its height is set here rather than grown by its
   rows — nine rows at a desk, seven taller rows under a thumb.
   ========================================================================== */
.dk-lib { margin: .9rem 0 .2rem; padding-top: .85rem; border-top: 1px solid var(--line-soft); }
.dk-lib__top { display: flex; align-items: center; justify-content: space-between; gap: .6rem; flex-wrap: wrap; }
.dk-lib__top p { margin: 0; min-width: 0; }
.dk-lib__sets { flex-wrap: nowrap; }
.dk-lib__sets .cwd__b { flex: 0 0 auto; min-width: 2.75rem; position: relative; font-variant-numeric: tabular-nums; }
/* LOADING IS A LINE THAT RUNS, not a spinner: the key keeps its label, and the
   hairline under it says the press was heard. */
.dk-lib__sets .cwd__b[data-state="load"]::after,
.dk-keys .dk-dice[data-state="load"]::after {
    content: ''; position: absolute; left: 4px; right: 4px; bottom: 3px; height: 2px;
    background: var(--accent); transform-origin: left center;
    animation: dk-lib-run .9s ease-in-out infinite alternate;
}
@keyframes dk-lib-run { from { transform: scaleX(.08); } to { transform: scaleX(1); } }
@media (prefers-reduced-motion: reduce) {
    .dk-lib__sets .cwd__b[data-state="load"]::after,
    .dk-keys .dk-dice[data-state="load"]::after { animation: none; opacity: .75; }
}
.dk-lib__sets .cwd__b[data-state="err"] { border-style: dashed; border-color: var(--accent); color: var(--accent); }
.dk-lib__sets .cwd__b[data-state="err"]:hover { background: var(--accent); color: var(--paper); }
/* THE MUSIC DICE, last in the row: the stage dice's die on a list key. It
   lights while it is dealing — the queue is being kept topped up from it. */
.dk-lib__sets .dk-lib__dice[data-deal="on"],
.dk-keys .dk-dice[data-deal="on"] { color: var(--accent); border-color: var(--accent); }
.dk-lib__sets .dk-lib__dice[data-deal="on"]:hover,
.dk-keys .dk-dice[data-deal="on"]:hover { background: var(--accent); color: var(--paper); }
.dk-lib__tools { display: flex; align-items: center; gap: .3rem; flex-wrap: wrap; margin-top: .6rem; }
.dk-lib__q { flex: 1 1 12rem; width: auto; }
.dk-lib__q:disabled { opacity: .5; }
.dk-lib__tools .cwd__b { flex: 0 0 auto; padding: 0 .75rem; }
.dk-lib__tools .cwd__b:disabled { opacity: .45; cursor: default; }
.dk-lib__tools .cwd__b:disabled:hover {
    background: transparent; color: var(--ink);
    border-color: var(--cwd-edge, color-mix(in srgb, var(--ink) 34%, transparent));
}
.dk-lib__empty { margin: .55rem 0 0; font-size: .72rem; color: var(--muted); }
/* THE ROW HEIGHT IS IN rem, so a reader who sets larger text gets taller rows
   rather than names clipped inside a fixed 34px under contain: strict.
   crate.js measures it off .dk-lib__rh, a real box, not by parsing the token. */
.dk-lib__list {
    --dk-lib-rh: 2.125rem;
    position: relative; margin-top: .5rem;
    --dk-lib-n: 5;
    height: calc(var(--dk-lib-rh) * var(--dk-lib-n) + 2px);
    overflow-y: auto; overflow-x: hidden; overscroll-behavior: contain;
    border: 1px solid var(--line); border-radius: var(--cwd-r, 3px);
    contain: strict;
}
.dk-lib__list[hidden] { display: none; }
/* S / M / L (#8): 5, 10 or 20 rows; L never taller than the screen less 12rem */
.dk-lib__list[data-sz="m"] { --dk-lib-n: 10; }
.dk-lib__list[data-sz="l"] { --dk-lib-n: 20; max-height: max(calc(var(--dk-lib-rh) * 10 + 2px), calc(100svh - 12rem)); }
.dk-lib__list:focus { outline: none; }
.dk-lib__list:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.dk-lib__sp { width: 1px; pointer-events: none; }
.dk-lr {
    position: absolute; left: 0; right: 0; top: 0;
    height: var(--dk-lib-rh); box-sizing: border-box;
    display: flex; align-items: center; gap: .5rem;
    padding: 0 .3rem 0 .55rem;
    border-bottom: 1px solid var(--line-soft);
    cursor: default; user-select: none; -webkit-user-select: none;
}
.dk-lr[hidden] { display: none; }
.dk-lr:hover { background: color-mix(in srgb, var(--ink) 4%, transparent); }
/* 55%, not 40%: the box's edge is its only outline, and at 40% it measured
   2.4:1 on paper — under the 3:1 a control's boundary needs. */
.dk-lr__c {
    flex: 0 0 auto; width: .72rem; height: .72rem; box-sizing: border-box;
    border: 1px solid color-mix(in srgb, var(--ink) 55%, transparent); border-radius: 2px;
}
.dk-lr[aria-selected="true"] { background: color-mix(in srgb, var(--accent) 8%, transparent); }
.dk-lr[aria-selected="true"] .dk-lr__c {
    background: var(--accent); border-color: var(--accent);
    box-shadow: inset 0 0 0 2px var(--paper);
}
.dk-lr__n {
    flex: 1 1 auto; min-width: 0; font-size: .8rem; color: var(--ink);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.dk-lr__x { flex: 0 0 auto; font-size: .56rem; font-weight: 700; letter-spacing: .1em; color: var(--muted); }
.dk-lr__b {
    -webkit-appearance: none; appearance: none; flex: 0 0 auto;
    width: 1.75rem; height: 1.5rem; padding: 0;
    display: inline-flex; align-items: center; justify-content: center;
    border: 1px solid var(--line); border-radius: var(--cwd-r, 3px);
    background: none; color: var(--muted); cursor: pointer;
}
.dk-lr__b svg { width: .72rem; height: .72rem; display: block; }
.dk-lr__b:hover { border-color: var(--accent); color: var(--accent); }
/* THE ROW THE KEYBOARD IS ON. A bar down its left edge whenever it is the
   active row, and the accent outline only while the list has the focus — so
   the mouse user is not left staring at a ring they did not ask for. */
.dk-lr[data-active="1"] { box-shadow: inset 2px 0 0 var(--accent); }
.dk-lib__list:focus-visible .dk-lr[data-active="1"] {
    outline: 1px solid var(--accent); outline-offset: -1px;
    background: color-mix(in srgb, var(--accent) 10%, transparent);
}
/* AA ON PAPER. The EXT tag on a tinted row (selected, or the keyboard's row)
   measured 4.1:1 in --muted, and the search placeholder 4.4:1 in the shared
   62% ink — both under 4.5. Dark passed and is unchanged in effect. */
.dk-lr[aria-selected="true"] .dk-lr__x,
.dk-lib__list:focus-visible .dk-lr[data-active="1"] .dk-lr__x { color: var(--ink-77); }
.dk-lib__q::placeholder { color: color-mix(in srgb, var(--ink) 70%, transparent); opacity: 1; }
/* the ruler crate.js reads the row height from */
.dk-lib__rh { position: absolute; top: 0; left: 0; width: 1px; height: var(--dk-lib-rh); visibility: hidden; pointer-events: none; }
/* FORCED COLOURS flatten the accent tint and the inset ring to Canvas, and a
   selected row looked exactly like one that was not. System colours say it. */
@media (forced-colors: active) {
    .dk-lr[aria-selected="true"] .dk-lr__c {
        forced-color-adjust: none; background: Highlight; border-color: Highlight;
        box-shadow: inset 0 0 0 2px Canvas;
    }
    .dk-lr[data-active="1"] { outline: 2px solid Highlight; outline-offset: -2px; }
}
.dk-lib__keys { margin-top: .45rem; }
/* Play now, for the row the keyboard or a screen reader's double-tap is on,
   beside the line of keys that says the same thing for a keyboard. */
.dk-lib__foot { display: flex; align-items: center; gap: .3rem .7rem; flex-wrap: wrap; margin-top: .45rem; }
.dk-lib__foot .dk-lib__keys { margin: 0; }
.dk-lib__foot .cwd__b { flex: 0 0 auto; padding: 0 .75rem; }
.dk-lib__foot .cwd__b:disabled { opacity: .45; cursor: default; }
.dk-lib__foot .cwd__b:disabled:hover {
    background: transparent; color: var(--ink);
    border-color: var(--cwd-edge, color-mix(in srgb, var(--ink) 34%, transparent));
}
@media (pointer: coarse) {
    .dk-lib__list { --dk-lib-rh: 3rem; }
    .dk-lr__b { width: 2.75rem; height: 2.75rem; }
    .dk-lib__keys { display: none; }
}
@media (max-width: 480px) {
    .dk-lib__top { align-items: flex-start; }
    .dk-lib__tools .cwd__b { flex: 1 1 auto; }
}
/* THE QUEUE'S AND THE LIBRARY'S KEYS ARE SYMBOLS (#18): square at the control
   height, 44px on touch; Add selected widens only for its count. */
.cwd__b.dk-gk { flex: 0 0 auto; min-width: var(--cwd-h, 2.125rem); padding: 0 .4rem; gap: .25rem; }
.cwd__b.dk-gk svg { width: 1rem; height: 1rem; }
.dk-gk__n { font: 700 .62rem/1 'DM Sans', system-ui, sans-serif; letter-spacing: 0; font-variant-numeric: tabular-nums; }
.dk-gk__n[hidden] { display: none; }
@media (pointer: coarse) { .cwd__b.dk-gk { min-width: 2.75rem; } }

/* ============================================ THE WORD CONTROLS ============ *
   A checkbox and a right-click. The word belongs to one mode out of
   twenty-eight and the reader who typed it usually wants to see it over
   whatever is drawing — so the checkbox promotes it, and the face is a
   right-click on the field itself, which is where you are when you decide it
   looks wrong.
   ========================================================================== */
.vz-chk {
    display: inline-flex; align-items: center; gap: .3rem;
    margin-left: auto; cursor: pointer;
    font-size: .55rem; font-weight: 700; letter-spacing: .1em;
    text-transform: uppercase; color: var(--muted);
}
.vz-chk input {
    -webkit-appearance: none; appearance: none;
    width: 13px; height: 13px; margin: 0; flex: 0 0 auto;
    border: 1px solid var(--line); border-radius: 2px;
    background: none; cursor: pointer; position: relative;
}
.vz-chk input:checked { background: var(--accent); border-color: var(--accent); }
/* A 13px BOX IS A MOUSE TARGET. On a touch screen the whole label is made the
   target instead of the box: the box stays 13px so the row still looks like a
   line of type, and the tappable area becomes the label around it, which is
   what a <label> is for and what everybody aims at anyway. */
@media (pointer: coarse) {
    .vz-chk { min-height: 2.75rem; padding: .35rem 0; }
    .vz-chk input { width: 18px; height: 18px; }
    .vz-chk input:checked::after { left: 5.5px; top: 2px; width: 5px; height: 10px; }
}
/* the tick is drawn, not a glyph — a character would sit differently in every
   face the reader might have installed */
.vz-chk input:checked::after {
    content: ''; position: absolute; left: 3.5px; top: 0.5px;
    width: 4px; height: 8px; border: solid var(--paper);
    border-width: 0 2px 2px 0; transform: rotate(42deg);
}
.vz-chk input:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.vz-chk:hover { color: var(--accent); }
/* each face previews itself in the picker, which is the only way to choose one */
.vz-menu__i--font b { font-size: .95rem; font-weight: 600; }

/* ============================================== THE TILE MENU ============== *
   Right-click on a tile. It names the tile it is about before any verb, because
   a menu that opens over twelve near-identical pictures and does not say which
   one it belongs to is a menu you have to guess at.
   ========================================================================== */
/* A FIXED ELEMENT DOES NOT SCROLL WITH THE PAGE, so anything below the fold of
   a fixed menu can never be reached — and the tile menu is ~760px tall at
   pointer:coarse, on a phone whose viewport is ~745px. Everything from the
   sensitivity keys down was unreachable at the SHIPPED DEFAULT of one tile.
   It scrolls inside itself now, which is the only thing a fixed panel can do. */
.vz-menu {
    position: fixed; z-index: 1200; min-width: 15rem; max-width: 19rem;
    max-height: calc(100vh - 16px);
    max-height: calc(100dvh - 16px);
    overflow-y: auto; -webkit-overflow-scrolling: touch;
    overscroll-behavior: contain;
    padding: .3rem; margin: 0;
    background: var(--paper); color: var(--ink);
    border: 1px solid var(--line);
    border-radius: 6px;
    box-shadow: 0 10px 34px rgba(0, 0, 0, .30), 0 2px 6px rgba(0, 0, 0, .18);
}
.vz-menu__h {
    margin: 0; padding: .5rem .6rem .45rem;
    font-size: .58rem; font-weight: 700; letter-spacing: .13em;
    text-transform: uppercase; color: var(--accent);
    border-bottom: 1px solid var(--line-soft);
}
.vz-menu__s { height: 1px; background: var(--line-soft); margin: .25rem .3rem; }
.vz-menu__i {
    -webkit-appearance: none; appearance: none;
    display: block; width: 100%; text-align: left;
    font: inherit; padding: .42rem .6rem;
    background: none; border: 0; border-radius: 3px;
    color: var(--ink); cursor: pointer;
}
.vz-menu__i b { display: block; font-size: .82rem; font-weight: 500; }
.vz-menu__i span { display: block; font-size: .68rem; color: var(--muted); margin-top: .05rem; }
.vz-menu__i:hover, .vz-menu__i:focus-visible {
    background: var(--accent); outline: none;
}
/* the only literal colour in this block was #fff on the accent, which is
   2.80:1 on dark. --paper is the token that inverts with the edition. */
.vz-menu__i:hover b, .vz-menu__i:focus-visible b,
.vz-menu__i:hover span, .vz-menu__i:focus-visible span { color: var(--paper); }
@media (pointer: coarse) {
    .vz-menu { min-width: 17rem; }
    .vz-menu__i { padding: .7rem .7rem; }
}

/* ================================================ TOUCH TARGETS ============ *
   FORTY-FOUR PIXELS, GATED ON THE POINTER AND NOT ON THE WIDTH.

   The .cwd recipe already carries a coarse bump — `.cwd { --cwd-h: 2.75rem }` —
   and it has never once fired on this page, because the bump is declared on
   .cwd and this page has no element with that class. Every key therefore
   shipped at its 34px desktop height on every phone, against Apple's 44px
   floor. .cwd__b sets `height` outright, so a `min-height` cannot beat it: the
   height itself has to be raised, and it has to be raised by a rule that wins
   on specificity rather than by repointing a token nothing here reads.

   THE GATE IS (pointer: coarse), NEVER A WIDTH. A phone held sideways is 844px
   and still needs 44px keys; a 500px browser window on a desktop does not. And
   the input's font-size rides the same gate for the same reason — under 16px
   iOS Safari zooms on focus and carries the panel off the screen.
   ========================================================================== */
@media (pointer: coarse) {
    .console .cwd__b,
    .console .cwd__i,
    .console .vz-fx__b,
    .console .vz-gchip,
    .console .vz-mkey { height: 2.75rem; min-height: 2.75rem; }
    .console .cwd__i { font-size: 16px; }
    .console .vz-fx__b { padding-left: .7rem; padding-right: .7rem; }
    /* THE DECK IS PART OF THE PAGE TOO. Its sliders measured 18px tall and its
       preset chips 26px — a range input is the hardest control on any page to
       hit with a thumb, because the target is the THUMB and not the track, and
       an 18px track puts that thumb inside a 12px circle. The track grows and
       the chips come up to the floor with everything else. */
    .console .dk-range { height: 2.75rem; }
    /* height AND width — the coarse block raised one and left the other, so the
       preset chips came out 38-40px wide against a 44px height */
    .console .dk-chip {
        min-height: 2.75rem; min-width: 2.75rem;
        display: inline-flex; align-items: center; justify-content: center;
    }
    .console .dk-h__k { height: 2.75rem; min-width: 2.75rem; }
}
@media (pointer: coarse) and (min-width: 641px) { .dk-h { min-height: 2.75rem; } }

/* ================================================== THE TEN SLOTS ========== *
   A rack of ten numbered keys, each holding an entire desk. They are RADIOS:
   at most one is lit, and it goes dark the moment any control moves, because
   at that point you are no longer in that slot — you are somewhere near it.
   A key that stayed lit while the settings drifted underneath it would be
   telling you something untrue.
   ========================================================================== */
.vz-slotbar {
    display: flex; align-items: center; gap: .5rem;
    flex-wrap: wrap; margin: 0;
}
.vz-fxl--in { margin: 0; }
/* THE RACK IS A GRID, AND THAT IS A BUG FIX, NOT A TIDY-UP.

   It was a flex row of ten RIGID 27.19px keys with 3px gaps — 298.88px that
   cannot shrink — living in a 249.2px panel at 320px. Flex happily overflows
   its container, the console carries overflow: hidden, and the surplus was
   silently eaten: measured at 320px the console's right edge is at x=300 while
   slot 9 sits at 276.9..304.1 and slot 10 at 307.1..334.3. TWO OF THE TEN
   SLOTS, and an FX key beside them, were off the page and untappable on every
   phone under about 370px — and the document reported no horizontal overflow
   at all, so nothing pointed at it.

   minmax(0, 1fr) tracks divide what is actually there and cannot do this by
   construction. Ten 44px columns need 10*44 + 9*6.4 = 497.6px of panel, so
   below that the rack folds to five and takes two rows. */
.vz-slots {
    display: grid; grid-template-columns: repeat(10, minmax(0, 1fr));
    gap: .4rem; flex: 1 1 auto; min-width: 0;
}
/* AND IT TAKES ITS OWN LINE. Sharing the bar with the SAVE label, the two
   keys and the status word left the rack 149.5px of a 249.2px panel — five
   columns of 24.8px, legible but not tappable. On its own line the same five
   columns measure 44.7px, which clears the 44px floor. */
@media (max-width: 620px) {
    .vz-slots { grid-template-columns: repeat(5, minmax(0, 1fr)); flex-basis: 100%; order: 2; }
    .vz-slotbar .vz-fxl--in { order: 1; }
    .vz-slot__k { order: 3; flex: 1 1 auto; }
    .vz-slot__w { order: 4; }
}
.vz-slot {
    -webkit-appearance: none; appearance: none;
    width: auto; min-width: 0; height: var(--cwd-h, 2.125rem); padding: 0;
    display: inline-flex; align-items: center; justify-content: center;
    font: 700 .72rem/1 var(--mono, ui-monospace, Menlo, monospace);
    letter-spacing: 0;
    color: var(--muted); background: none;
    border: 1px solid var(--line); border-radius: 3px;
    cursor: pointer; position: relative;
    transition: background .12s, border-color .12s, color .12s;
}
.vz-slot:hover { border-color: var(--accent); color: var(--accent); }
.vz-slot:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* A FILLED SLOT LOOKS DIFFERENT FROM AN EMPTY ONE even when it is not
   selected, so the rack tells you what you have without being pressed. */
.vz-slot[data-full="1"] { color: var(--ink); border-color: var(--ink); }
.vz-slot[data-full="1"]::after {
    content: ''; position: absolute; left: 50%; bottom: 3px; margin-left: -3px;
    width: 6px; height: 2px; border-radius: 1px; background: var(--accent);
}
.vz-slot[aria-checked="true"] {
    background: var(--ink); border-color: var(--ink); color: var(--paper);
}
.vz-slot[aria-checked="true"]::after { background: var(--paper); }
.vz-slot__k { font-size: .62rem; }
.vz-slot__w {
    margin: 0; font-size: .66rem; color: var(--muted);
    font-family: var(--mono, ui-monospace, Menlo, monospace);
}
/* KEY SIZE GATES ON POINTER, NEVER ON WIDTH. A phone held sideways is 844px
   wide and still reports a coarse pointer; a 640px browser window on a desktop
   has a mouse. Shrinking on width gets both backwards — it makes the phone's
   keys small exactly where there is room, and leaves the desktop's alone. */
@media (pointer: coarse) {
    .vz-slot { height: 2.75rem; font-size: .72rem; }
    .vz-slot__w { flex-basis: 100%; }
}
@media (max-width: 640px) { .vz-slot__w { flex-basis: 100%; } }

/* THE CHIP THAT HOLDS THE LIT MODE, when you are looking at another family.
   The rail used to jump to it; now it just says where the mode went, so the
   filter you chose is the filter you keep. */
.vz-gchip.is-home { position: relative; }
.vz-gchip.is-home::after {
    content: ''; position: absolute; top: 3px; right: 3px; width: 4px; height: 4px;
    border-radius: 50%; background: var(--accent);
}

/* ------------------------------------------------ TEMPO AND PITCH ----------
   The Audio Engine's two panels, drawn the way this desk draws everything:
   a hairline box at the house radius, the label recipe for the heading, one
   big tabular reading between two keys, the house rail, and the house keys
   under it. The accent is spent on two things only — the heading, and the
   reading while it is OFF normal, so a deck left at 0.92x says so from across
   the room and a deck at 1.00x is quiet.
   -------------------------------------------------------------------------- */
.dk-tpw { display: grid; gap: .45rem; }
.dk-tp { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: .8rem; }
.dk-tp__p {
    min-width: 0; margin: 0;
    padding: .75rem .9rem .85rem;
    border: 1px solid color-mix(in srgb, var(--ink) 13%, transparent);
    border-radius: var(--cwd-r, 3px);
}
/* THE HEADING LINE: the heading and its tagline, at every width. The presets
   live in the feet since 483, so nothing else competes for this line. */
.dk-tp__h { display: flex; align-items: baseline; gap: .6rem; margin: 0 0 .25rem; }
.dk-tp__h b {
    flex: 0 0 auto;
    font-size: .625rem; font-weight: 700; letter-spacing: .14em;
    text-transform: uppercase; color: var(--accent);
}
.dk-tp__sub {
    min-width: 0; overflow: hidden; white-space: nowrap;
    font-size: .625rem; font-weight: 600; letter-spacing: .14em;
    text-transform: uppercase; color: var(--muted);
}
/* THE READING AND ITS TWO KEYS, AS ONE CENTRED GROUP. The reading's cell has a
   fixed width (shrinking only when the panel is narrower than the group), so
   the keys sit close to the number and do NOT move when it changes length —
   1.00 to 1.005 is a digit wider, and a key that jumps a digit's width between
   two clicks is a key the second click misses. */
.dk-tp__read {
    display: grid; grid-template-columns: auto minmax(0, 10rem) auto;
    justify-content: center; align-items: center;
    gap: .35rem; margin: .1rem 0 .5rem;
}
.dk-tp__v {
    display: flex; align-items: baseline; justify-content: center; gap: .18rem;
    min-width: 0; white-space: nowrap;
    font-family: 'Orbitron', ui-monospace, SFMono-Regular, Menlo, monospace;
    font-variant-numeric: tabular-nums; line-height: 1.1; color: var(--ink);
}
.dk-tp__v b { font-size: clamp(1.55rem, 1.15rem + 1.3vw, 2.1rem); font-weight: 700; letter-spacing: -.01em; }
.dk-tp__v i { font-style: normal; font-size: .8rem; font-weight: 600; color: var(--ink-55); }
.dk-tp__v[data-off="1"] b { color: var(--accent); }
.dk-tp__n {
    -webkit-appearance: none; appearance: none;
    width: 2.5rem; height: 2.5rem; padding: 0; flex: 0 0 auto;
    display: inline-flex; align-items: center; justify-content: center;
    background: transparent; color: var(--muted); cursor: pointer;
    border: 1px solid transparent; border-radius: var(--cwd-r, 3px);
    touch-action: manipulation; -webkit-tap-highlight-color: transparent;
    -webkit-user-select: none; user-select: none; -webkit-touch-callout: none;
    transition: color .15s var(--ease), border-color .15s var(--ease);
}
.dk-tp__n:hover { color: var(--ink); border-color: color-mix(in srgb, var(--ink) 34%, transparent); }
.dk-tp__n:active, .dk-tp__n[data-held] { color: var(--accent); border-color: var(--accent); }
.dk-tp__n:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.dk-tp__n svg { display: block; }
.dk-tp__r { display: block; width: 100%; }
/* THE END LABELS SIT ON A 1fr / auto / 1fr GRID so the middle one is centred
   under the middle of the rail whatever the two end labels measure — which is
   where 1.00x and 0 actually are. */
.dk-tp__sc {
    display: grid; grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr); gap: .4rem;
    margin: .3rem 0 0;
    font-size: .625rem; font-weight: 600; letter-spacing: .1em; text-transform: uppercase;
    font-variant-numeric: tabular-nums; color: var(--muted);
}
.dk-tp__sc span:first-child { text-align: left; }
.dk-tp__sc span:nth-child(2) { text-align: center; color: var(--ink-55); }
.dk-tp__sc span:last-child { text-align: right; }
.dk-tp__warn {
    margin: .45rem 0 0; padding-left: .45rem;
    border-left: 2px solid var(--accent);
    font-size: .66rem; line-height: 1.4; color: var(--ink);
}
/* THE FEET (483): Reset, then three of the six presets, and in the pitch
   foot the dice. One line at every width: Reset gives way, the keys do not.
   Dashed = still the random pair it was born with; accent edge and underline
   = the deck is at this preset now (a radio, aria-checked); the fill that
   rises while a key is held is the 600 ms count to a store, and the solid
   accent after it is the store. */
.dk-tp__foot { display: flex; align-items: center; gap: .4rem; margin-top: .7rem; }
.dk-tp__reset { flex: 1 1 auto; min-width: 2.75rem; }
.dk-pre { display: flex; gap: 3px; flex: 0 0 auto; }
.dk-pre__k {
    position: relative; overflow: hidden; flex: 0 0 auto;
    width: var(--cwd-h, 2.125rem); padding: 0;
    font-family: 'Orbitron', ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: .66rem; font-weight: 700; font-variant-numeric: tabular-nums;
    touch-action: manipulation; -webkit-tap-highlight-color: transparent;
    -webkit-user-select: none; user-select: none; -webkit-touch-callout: none;
}
.dk-pre__k span { position: relative; pointer-events: none; }
.dk-pre__k::before { content: ''; position: absolute; inset: 0; pointer-events: none;
    background: color-mix(in srgb, var(--accent) 36%, transparent); transform: scaleY(0); transform-origin: 50% 100%; }
/* not the house key's inverted hover: a dashed key would read muted on ink */
.dk-pre__k:hover { background: transparent; border-color: var(--ink); color: var(--ink); }
.dk-pre__k[data-random="1"] { border-style: dashed; color: var(--muted); }
.dk-pre__k[aria-checked="true"] { border-style: solid; border-color: var(--accent); color: var(--accent); box-shadow: inset 0 -2px 0 var(--accent); }
.dk-pre__k[data-hold="1"] { border-color: var(--accent); }
.dk-pre__k[data-hold="1"]::before { transform: scaleY(1); transition: transform .6s linear; }
.dk-pre__k[data-done="1"], .dk-pre__k[data-done="1"]:hover { background: var(--accent); border-style: solid; border-color: var(--accent); color: var(--paper); }
.dk-pre__dice svg { display: block; width: 15px; height: 15px; }
@media (pointer: coarse) { .console .dk-pre__k { width: 2.75rem; } }
@media (max-width: 22.5rem) { .dk-tp__p { padding-left: .6rem; padding-right: .6rem; } }
@media (max-width: 640px) { .dk-tp { grid-template-columns: minmax(0, 1fr); } }
@media (pointer: coarse) { .dk-tp__n { width: 2.75rem; height: 2.75rem; } }
/* Reduced motion: no rising fill. The hold still counts to 600 ms and stores;
   the key shows it is being held with a still inner edge instead. */
@media (prefers-reduced-motion: reduce) {
    .dk-tp__n, .dk-pre__k { transition: none; }
    .dk-pre__k[data-hold="1"]::before { transform: scaleY(0); transition: none; }
    .dk-pre__k[data-hold="1"] { box-shadow: inset 0 0 0 1px var(--accent); }
}

/* ----------------------------------------------------------------- SHELF ---
   The music folder, as a compact panel. It sits over the stage rather than
   pushing it: a browser that reflows the instrument every time you open it is
   a browser you stop opening.
   -------------------------------------------------------------------------- */
.vz-shelf {
    position: absolute; z-index: 40; right: .6rem; top: .6rem;
    width: min(22rem, calc(100% - 1.2rem));
    background: var(--paper); border: 1px solid var(--line); border-radius: 10px;
    box-shadow: 0 18px 44px rgba(20, 24, 30, .18), 0 2px 6px rgba(20, 24, 30, .08);
    overflow: hidden;
}
html[data-dark] .vz-shelf { box-shadow: 0 18px 44px rgba(0, 0, 0, .6), 0 2px 6px rgba(0, 0, 0, .45); }
.vz-shelf[hidden] { display: none; }
.vz-shelf__bar {
    display: flex; align-items: center; gap: .4rem;
    padding: .4rem .5rem; border-bottom: 1px solid var(--line-soft, rgba(30,38,43,.07));
}
.vz-shelf__t { font-size: .62rem; font-weight: 800; letter-spacing: .14em; text-transform: uppercase; }
.vz-shelf__q {
    flex: 1 1 auto; min-width: 0;
    /* 16px on a touch screen or iOS Safari zooms the page and carries the
       panel off with it — the estate has been here before. */
    font: inherit; font-size: 16px; padding: .22rem .4rem;
    background: transparent; border: 1px solid var(--line); border-radius: 6px; color: var(--ink);
}
@media (pointer: fine) { .vz-shelf__q { font-size: .72rem; } }
.vz-shelf__n { font-size: .58rem; color: var(--ink-55); white-space: nowrap; font-variant-numeric: tabular-nums; }
.vz-shelf__x {
    appearance: none; -webkit-appearance: none; background: transparent; border: 0;
    font: inherit; font-size: 1rem; line-height: 1; color: var(--ink-55); cursor: pointer; padding: 0 .2rem;
}
.vz-shelf__x:hover { color: var(--accent); }
.vz-shelf__gate { display: flex; align-items: center; gap: .35rem; padding: .5rem; flex-wrap: wrap; }
.vz-shelf__gate[hidden] { display: none; }
.vz-shelf__pw {
    flex: 1 1 6rem; min-width: 0; font: inherit; font-size: 16px;
    padding: .3rem .45rem; background: transparent;
    border: 1px solid var(--line); border-radius: 6px; color: var(--ink);
}
@media (pointer: fine) { .vz-shelf__pw { font-size: .78rem; } }
.vz-shelf__go {
    appearance: none; -webkit-appearance: none; cursor: pointer;
    font: inherit; font-size: .7rem; font-weight: 700; padding: .32rem .6rem;
    background: var(--ink); color: var(--paper); border: 0; border-radius: 6px;
}
.vz-shelf__err { flex: 1 1 100%; font-size: .64rem; color: var(--accent); min-height: .8rem; }
.vz-shelf__list { max-height: 15rem; overflow-y: auto; overscroll-behavior: contain; padding: .25rem; }
.vz-shelf__list[hidden] { display: none; }
.vz-shelf__r {
    appearance: none; -webkit-appearance: none; cursor: pointer;
    display: flex; align-items: baseline; justify-content: space-between; gap: .5rem;
    width: 100%; text-align: left; padding: .3rem .4rem; border: 0; border-radius: 5px;
    background: transparent; font: inherit; color: var(--ink);
}
.vz-shelf__r:hover, .vz-shelf__r:focus-visible { background: var(--line-soft, rgba(30,38,43,.07)); outline: 0; }
.vz-shelf__r b {
    font-size: .72rem; font-weight: 600; min-width: 0;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.vz-shelf__r span { font-size: .58rem; color: var(--ink-55); white-space: nowrap; font-variant-numeric: tabular-nums; }
.vz-shelf__none { padding: .6rem .4rem; font-size: .68rem; color: var(--ink-55); }

/* THE MODE PAGER. Same key shape as the family chips, one row under the grid. */
.vz-modepg { display: flex; gap: 3px; margin-top: .3rem; }
.vz-modepg[hidden] { display: none; }
.vz-pg {
    appearance: none; -webkit-appearance: none; cursor: pointer;
    min-width: 22px; height: 20px; padding: 0 .3rem;
    background: transparent; border: 1px solid var(--line); border-radius: 5px;
    font: inherit; font-size: .58rem; font-weight: 800;
    font-variant-numeric: tabular-nums; color: var(--ink-55);
}
.vz-pg[aria-selected="true"] { color: var(--accent); border-color: var(--accent); }
/* A PAGE THAT HOLDS A LIT KEY says so, with the family chips' dot (484): with All
   selected and twelve modes on the stage, three outlined keys sat behind an
   unmarked "2". Absolute, so the button keeps its width and the row never shifts. */
.vz-pg.is-home { position: relative; }
.vz-pg.is-home::after {
    content: ''; position: absolute; top: 2px; right: 2px; width: 4px; height: 4px;
    border-radius: 50%; background: var(--accent);
}
.vz-pg:hover { color: var(--ink); }
@media (pointer: coarse) { .vz-pg { min-width: 34px; height: 30px; } }

/* -------------------------------------------------- THE PLATTER, UP TOP ----
   Tempo and pitch beside the transport: a reading with a key either side. Same
   height as the transport keys so the bar stays one line.
   -------------------------------------------------------------------------- */
.vz-plat { display: inline-flex; align-items: center; gap: .45rem; }
.vz-plat__c {
    display: inline-flex; align-items: center; gap: .15rem;
    border: 1px solid var(--line); border-radius: var(--cwd-r, 3px);
    padding: 0 .2rem; height: var(--cwd-h, 2.125rem);
}
.vz-plat__b {
    -webkit-appearance: none; appearance: none; cursor: pointer;
    width: 17px; height: 100%; padding: 0; flex: 0 0 auto;
    display: inline-flex; align-items: center; justify-content: center;
    background: none; border: 0; color: var(--muted);
    font: inherit; font-size: .8rem; font-weight: 700; line-height: 1;
}
.vz-plat__b {
    touch-action: manipulation; -webkit-tap-highlight-color: transparent;
    -webkit-user-select: none; user-select: none; -webkit-touch-callout: none;
}
.vz-plat__b svg { display: block; }
.vz-plat__b:hover, .vz-plat__b[data-held] { color: var(--accent); }
.vz-plat__b:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
/* THE READING IS THE POINT OF THE CLUSTER, so it is the only part set in the
   ink colour and in tabular figures — it changes on every press and must not
   jitter the two keys either side of it. */
.vz-plat__v {
    min-width: 2.9rem; text-align: center;
    font-variant-numeric: tabular-nums; font-size: .7rem; font-weight: 700;
    color: var(--ink); cursor: help;
}
/* var(--ink-55), not var(--muted): muted measured 4.47:1 on the bar's #f5f5f5 */
.vz-plat__l { font-style: normal; font-size: .55rem; font-weight: 700;
              color: var(--ink-55); padding-right: .15rem; }
@media (max-width: 900px) { .vz-plat { display: none; } }

/* ------------------------------------------------------- THE LIVE BAR ------
   One line, and deliberately thin: it sits between the desk and the stage and
   must not take height from either.
   -------------------------------------------------------------------------- */
/* .vz-live, NOT .vz-bar — that name is already the meter's 4px level bar in
   this same sheet, and taking it handed this strip `display: block`, a
   four-pixel height and `overflow: hidden`. The collision is silent: nothing
   errors, the strip just collapses to a grey capsule with its own text cut off
   inside it, which reads as a padding bug and is not one. The sheet had a
   comment warning about exactly this, two thousand lines up.

   NO GROUND OF ITS OWN. It sits between the desk and the stage and is a line of
   type, not a panel — a filled pill there is a third box competing with the two
   real ones either side of it. Transparent, one hairline under it, and the grid
   gap taken out so it belongs to the stage it describes rather than floating
   between them. */
.vz-live {
    display: flex; align-items: center; gap: .5rem; flex-wrap: wrap;
    margin: -.95rem 0 -.6rem; padding: .26rem .1rem;
    background: transparent;
    border: 0; border-bottom: 1px solid var(--line-soft, rgba(30,38,43,.07));
    border-radius: 0;
    font-size: .66rem; line-height: 1.3; color: var(--muted);
}
.vz-live__dot { width: 6px; height: 6px; border-radius: 50%; flex: 0 0 auto; background: var(--muted); }
.vz-live.is-live .vz-live__dot { background: #15803D; animation: vz-pulse 1.8s ease-in-out infinite; }
html[data-dark] .vz-live.is-live .vz-live__dot { background: #4ADE80; }
@keyframes vz-pulse { 0%,100% { opacity: 1; } 50% { opacity: .35; } }
@media (prefers-reduced-motion: reduce) { .vz-live.is-live .vz-live__dot { animation: none; } }
.vz-live__m { font-size: .7rem; font-weight: 800; letter-spacing: -.01em; color: var(--ink); }
.vz-live__d { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1 1 8rem; }
.vz-live__d[hidden] { display: none; }
.vz-live__n { font-variant-numeric: tabular-nums; white-space: nowrap; }
.vz-live__src { margin-left: auto; opacity: .8; white-space: nowrap; }
@media (max-width: 620px) { .vz-live__d { display: none; } .vz-live__src { margin-left: 0; } }

/* THE MIDDLE OF THE HEAD ROW. Mode at one end, transport at the other, and
   what is playing plus what it is playing at between them — the two readings
   you want while actually watching the stage. */
.vz-mid { display: flex; align-items: center; gap: .5rem; margin: 0 auto; min-width: 0; }
.vz-mid__t {
    max-width: 15rem; min-width: 0;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    font-size: .68rem; font-weight: 700; letter-spacing: -.01em; color: var(--ink);
}
@media (max-width: 1100px) { .vz-mid__t { max-width: 9rem; } }
@media (max-width: 900px)  { .vz-mid { margin: 0; } .vz-mid__t { display: none; } }

/* THE NOW-PLAYING PLATE (#30), THE LOWER LEFT OF THE HEAD ROW. Row 1 stays
   Mode, the middle and the tile picker; row 2 always exists where the plate
   shows, with the plate from the left edge all the way to the transport's
   rewind key and the transport tail at the right. The song's name in red
   Orbitron caps, elapsed and remaining in grey, a thin seek line along the
   bottom — Songbuilder's plates in this desk's flat 3px corners. Tokens only,
   so the dark edition needs nothing of its own. */
.vz-row2 { flex: 1 1 100%; display: flex; align-items: center; gap: .4rem; min-width: 0; }
.vz-np {
    position: relative; display: flex; flex-direction: column; justify-content: center; gap: 3px;
    flex: 1 1 auto; min-width: 11rem; max-width: none;
    height: var(--cwd-h, 2.125rem); box-sizing: border-box; padding: 0 .55rem 3px;
    border: 1px solid var(--line); border-radius: var(--cwd-r, 3px); overflow: hidden;
    cursor: pointer; touch-action: pan-y; -webkit-user-select: none; user-select: none;
}
.vz-np__t { font: 600 .6rem/1 'Orbitron', ui-monospace, Menlo, monospace; letter-spacing: .09em;
    color: var(--accent); text-transform: uppercase; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.vz-np__c { display: flex; justify-content: space-between; gap: .5rem;
    font: 600 .5rem/1 'Orbitron', ui-monospace, Menlo, monospace; letter-spacing: .09em;
    color: var(--ink-55); font-variant-numeric: tabular-nums; text-transform: uppercase; }
.vz-np__c i { font-style: normal; }
.vz-np__line { position: absolute; left: 0; right: 0; bottom: 0; height: 2px; background: var(--line); transition: height .12s ease; }
.vz-np__line::after { content: ''; position: absolute; inset: 0; background: var(--accent);
    transform-origin: 0 50%; transform: scaleX(var(--np-p, 0)); }
.vz-np:hover .vz-np__line, .vz-np:focus-visible .vz-np__line, .vz-np[data-drag] .vz-np__line { height: 4px; }
.vz-np:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.vz-np:not([data-state="file"]) { cursor: default; }
.vz-np[data-state="none"] .vz-np__t, .vz-np[data-state="live"] .vz-np__t { color: var(--ink-55); }
/* nothing loaded: the name line alone, centred; no caption, no line */
.vz-np[data-state="none"] .vz-np__c { display: none; }
.vz-np[data-state="none"] .vz-np__line, .vz-np[data-state="live"] .vz-np__line { visibility: hidden; }
.vz-np[data-paused] .vz-np__line::after, .vz-np[data-state="loading"] .vz-np__line::after { background: var(--ink-55); }
.vz-tail { display: inline-flex; align-items: center; gap: .4rem; flex: 0 0 auto; margin-left: auto; }
.vz-tail .vz-transport, .vz-tail .vz-shuf { margin-left: 0; }
@media (max-width: 900px) {
    .vz-row2 { flex-wrap: wrap; row-gap: .5rem; }
    .vz-np { flex-basis: 100%; }
    .vz-np[data-state="none"] { display: none; }
}
/* the tail has to wrap on a phone: unwrapped it measured 369px in a 319px row,
   and Repeat and Shuffle went outside the console's clip */
@media (max-width: 620px) {
    .vz-tail { flex-basis: 100%; margin-left: 0; flex-wrap: wrap; row-gap: .5rem; }
    .vz-tail .vz-transport { flex: 1 1 100%; }
}
@media (pointer: coarse) {
    .vz-np { height: 2.75rem; }
    .vz-np__t { font-size: .68rem; }
    .vz-np__c { font-size: .56rem; }
    .vz-np__line { height: 3px; }
}
@media (prefers-reduced-motion: reduce) { .vz-np__line { transition: none; } }
/* ROW 1 STAYS ONE LINE WHERE IT CAN. Row 2 is always there now, so a row 1 that
   wrapped made the head three lines: with twelve tiles the name, the tempo and
   pitch minis and the EQ came to more than 1184px has room for (measured
   1058px in 1031.6), and a long track name did it at every width. A wrapping
   row counts each item at its full content width before anything is allowed to
   shrink, so the middle is counted at the width of the minis and the EQ (their
   readings have a fixed width: 279.6px) and takes whatever the line leaves; the
   track name is what gives way, with an ellipsis — the plate directly under it
   carries the whole name. Its content stays centred, as the auto margins kept
   it before. */
.vz-mid { flex: 1 1 0; min-width: 17.5rem; justify-content: center; }
.vz-mid > .vz-plat, .vz-mid > .vz-eq { flex: 0 0 auto; }
@media (max-width: 1000px) { .vz-mid { min-width: 13.5rem; } }
@media (max-width: 900px)  { .vz-mid { min-width: 0; } }
/* ON A PHONE THE SIX TRANSPORT KEYS SHARE THEIR LINE, as the deck's five do. At
   44px each they came to 271px against a 249px line at 320px wide, and Repeat
   sat 15.8px outside the console's clip — there, but not on the screen. Shared,
   they are 40px wide at 320 (44 tall) and 50-53px at 375-390. */
@media (max-width: 620px) {
    .vz-tail .vz-transport .vz-tk { flex: 1 1 0; width: auto; min-width: 0; }
}

/* ------------------------------------------------- THE DESK, TIGHTENED -----
   The console body is a grid with a 1.15rem gap, which is right between the
   big blocks and far too much between the parts of one control. The mode
   panel, its pager and the strip under it are ONE thing and now read as one.
   -------------------------------------------------------------------------- */
.vz-modepg { margin-top: .25rem; }
/* the pager was the last row of the mode panel and the grid gap put a band of
   nothing under it before the stage */
.vz-modep { margin-bottom: -.35rem; }

/* THE TRANSPORT AS ONE OBJECT. Six keys, a repeat, a dice and Shuffle were
   nine separate outlines with air between them, which reads as nine unrelated
   controls rather than one transport. They are grouped: the moving keys in a
   single bordered cluster with hairlines between them, the two toggles beside
   it, Shuffle last. */
.vz-transport {
    display: inline-flex; align-items: center; gap: 0;
    border: 1px solid var(--line); border-radius: var(--cwd-r, 3px);
    overflow: hidden;
}
.vz-transport .vz-tk {
    border: 0; border-right: 1px solid var(--line); border-radius: 0;
    margin: 0;
}
.vz-transport .vz-tk:last-child { border-right: 0; }
/* the cluster clips its corners, so a ring drawn outside a key was clipped
   away with them: keyboard focus draws it inside (483 review) */
.vz-transport .vz-tk:focus-visible { outline-offset: -3px; }
.vz-transport .vz-tk:hover { background: var(--line-soft, rgba(30,38,43,.05)); }

/* THE SMALL EQ. Eight bars, the height of the controls beside it. */
.vz-eq {
    display: block; width: 56px; height: var(--cwd-h, 2.125rem);
    border: 1px solid var(--line); border-radius: var(--cwd-r, 3px);
}
@media (max-width: 1000px) { .vz-eq { display: none; } }
/* THE DECK'S SMALL EQ (483): the same eight bars, right of the Deck title, at
   every width. The title's auto margin moves to it, so the status stays right. */
.dk-eq {
    display: block; flex: 0 0 auto; width: 48px; height: 20px;
    border: 1px solid var(--line); border-radius: var(--cwd-r, 3px); margin-right: auto;
}
#deck .console__head .console__title { margin-right: 0; }

/* ---------------------------------------------------------------------------
   THE CLEAR KEY — the mark you see is now the key you press  (v468)
   ---------------------------------------------------------------------------
   Two things were wrong and they were the same thing.

   `.menu__search svg` was written for ONE svg, the magnifier, and given the
   magnifier's geometry: position absolute, left 2.05rem, pointer-events none.
   But it is a descendant selector, and there is a second svg in that strip —
   the X inside the clear button. It was taking the magnifier's rule too. So
   the X was drawn 2.05rem from its BUTTON's left edge (the button is itself
   positioned, so it became the containing block), which put the mark about
   three pixels PAST the field's right border, sitting in the gutter.

   The button never moved: it is 22px wide, inset 1.9rem, comfortably inside
   the field. Which is why pressing the X did nothing at all. The mark a reader
   could see was ten pixels right of the control, and it carried
   pointer-events: none, so the click fell straight through it to the strip
   behind. A control that is in the wrong place AND cannot be pressed reads as
   a rendering fault; it was one selector reaching one element too far.

   The glyph is put back in its button's flow — the button already centres it —
   and the tap target is grown to 40px underneath, without moving a pixel of
   the drawing.
   ------------------------------------------------------------------------ */
.menu__clear svg {
    position: static; left: auto; top: auto; right: auto; transform: none;
    width: 10px; height: 10px;
}
.menu__clear::after {
    content: ''; position: absolute;
    top: -9px; right: -8px; bottom: -9px; left: -9px;
}

/* ================================================== SYMBOLS ON THE KEYS ==== *
   (483) ONE PREFERENCE, ON UNLESS THIS BROWSER WAS TOLD OTHERWISE. A script at
   the top of the stage console stamps data-keys="sym" or "text" before the keys
   are painted; without JavaScript it is never set and every key keeps its word.
   Words stay in the DOM in both states and every key carries an aria-label, so
   a key's name never depends on which of the two is showing.

   WHAT TURNS INTO A SYMBOL, AND WHERE. The family rail, the mode keys, the FX
   and None, and Shuffle: at every width. Sensitivity and Store/Clear: on a
   phone only (700px and under) — on a desk they fit, and there a word is
   clearer.
   ========================================================================== */
.console :is(.vz-gchip__g, .vz-mkey__g, .vz-fx__g, .vz-g) { display: none; }
.console[data-keys="sym"] :is(.vz-gchip__t, .vz-mkey__t, .vz-fx__t, .vz-msplit__w, .vz-msplit__n) { display: none; }
.console[data-keys="sym"] :is(.vz-gchip__g, .vz-mkey__g) { display: block; width: 14px; height: 14px; flex: 0 0 auto; }
.console[data-keys="sym"] .vz-fx__g { display: block; width: 16px; height: 16px; flex: 0 0 auto; }
/* the sixteen FX cells are the mode strip's cells, so they carry its 14px glyph;
   None keeps 16, the size of the Symbols key it stands beside */
.console[data-keys="sym"] .vz-fx .vz-fx__g { width: 14px; height: 14px; }
.console[data-keys="sym"] .vz-shuf .vz-t { display: none; }
.console[data-keys="sym"] .vz-shuf .vz-g { display: block; width: 16px; height: 16px; }
.console[data-keys="sym"] .vz-shuf { width: var(--cwd-h, 2.125rem); padding: 0; justify-content: center; }
@media (max-width: 700px) {
    .console[data-keys="sym"] :is(.vzs, .vz-slot__k) .vz-t { display: none; }
    .console[data-keys="sym"] :is(.vzs, .vz-slot__k) .vz-g { display: block; width: 16px; height: 16px; }
    .console[data-keys="sym"] .vzs { display: inline-flex; align-items: center; justify-content: center; }
    .console[data-keys="sym"] .vz-slot__k { justify-content: center; padding: 0; }
}

/* THE SAVE RACK, AT THE FX RAIL'S PITCH (500).

   In symbols mode the ten slots were minmax(0,1fr) tracks — 85px each at 1400,
   841px of rack, the widest object in the console and the least information per
   pixel on the whole desk: a slot carries one digit. The FX rail beside it says
   sixteen things in 545px. On the same 34px pitch the rack says its ten in
   ~400px, and the row stops being the reason the console is as tall as it is.

   The keys follow: Store and Clear were already glyph-only under 700px, which
   is the same argument the 700px gate was making — in symbols mode they are
   glyph-only at every width, because that is what the mode means. Their titles
   and aria-labels are untouched, so the words are still one hover or one screen
   reader away.

   The status word leaves the row for the screen reader. It is 102px that reads
   "nothing recalled" in the state it is in almost all the time, and the lit
   slot already says which desk is recalled. role="status" still announces it. */
.console[data-keys="sym"] .vz-slots {
    grid-template-columns: repeat(10, var(--slot-w, 2.125rem));
    flex: 0 0 auto;
}
.console[data-keys="sym"] .vz-slot { padding: 0; justify-content: center; }
.console[data-keys="sym"] .vz-slot__k {
    width: var(--cwd-h, 2.125rem); padding: 0; justify-content: center;
}
.console[data-keys="sym"] .vz-slot__k .vz-t { display: none; }
.console[data-keys="sym"] .vz-slot__k .vz-g { display: block; width: 16px; height: 16px; flex: 0 0 auto; }
.console[data-keys="sym"] .vz-slot__w {
    position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
    overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; border: 0;
}

/* AND THE SMALL PRINT GOES WITH IT (500). Symbols mode is a statement that the
   desk is known: the words came off the keys, so the line explaining the keys
   underneath them is the same words in a smaller size. Every shortcut it lists
   is still on the control it belongs to — the stage digits, space, the bracket
   pair and F are in their keys' own titles, and right-click still opens the
   menu it mentions. Words mode keeps the line exactly as it was.

   The coarse-pointer line goes too: a touch desk in symbols mode has the same
   long-press it always had, and it is discovered by pressing, not by reading. */
.console[data-keys="sym"] .console__body > .vz-hint,
.console[data-keys="sym"] .vz-hint { display: none; }

/* AND THE GAPS CLOSE. The body's 1.15rem row gap is sized for panels with a
   heading each; with the words gone these two rows are one instrument, and the
   space between them was reading as a seam. .72rem keeps them apart without
   pretending they are separate sections. */
.console[data-keys="sym"] .console__body { gap: .72rem; }
.console[data-keys="sym"] .vz-slotbar { gap: .4rem; }
/* A COARSE POINTER KEEPS ITS TARGET. The mode strip and the FX rail already go
   to 2.75rem under (pointer: coarse) in this mode; the rack and its two keys
   are the same kind of target and take the same floor. */
@media (pointer: coarse) {
    .console[data-keys="sym"] .vz-slots { grid-template-columns: repeat(10, minmax(2.75rem, 1fr)); }
    .console[data-keys="sym"] .vz-slot { height: 2.75rem; }
    .console[data-keys="sym"] .vz-slot__k { width: 2.75rem; height: 2.75rem; }
}

/* THE MODE KEYS AS ONE STRIP OF CELLS. symLayout() writes --sym-cols and
   --sym-w from the panel's width and the number of keys showing: one row
   wherever a cell of 20px (40 on a touch screen) fits, otherwise balanced rows
   with no widow. Shared hairlines instead of gaps, so 49 cells read as one
   instrument rather than 49 buttons. */
.console[data-keys="sym"] .vz-modek,
.console[data-keys="sym"] .vz-fxhead > .vz-fx {
    grid-template-columns: repeat(var(--sym-cols, 9), var(--sym-w, 34px));
    gap: 0; justify-content: start; width: max-content; max-width: 100%;
    border-top: 1px solid var(--line); border-left: 1px solid var(--line);
    border-radius: var(--cwd-r, 3px); overflow: hidden;
}
.console[data-keys="sym"] .vz-mkey,
.console[data-keys="sym"] .vz-fx > .vz-fx__b {
    height: var(--cwd-h, 2.125rem); padding: 0; min-width: 0; border: 0; border-radius: 0;
    box-shadow: inset -1px -1px 0 var(--line); position: relative; background: none;
}
.console[data-keys="sym"] .vz-mkey:hover,
.console[data-keys="sym"] .vz-fx > .vz-fx__b:hover { color: var(--accent); box-shadow: inset 0 0 0 1px var(--accent); }
.console[data-keys="sym"] .vz-mkey:focus-visible,
.console[data-keys="sym"] .vz-fx > .vz-fx__b:focus-visible { outline-offset: -2px; }
/* clear of the 1.5px lit ring at 0, which the -2px focus ring sat straight on top of;
   it matters now that focus walks a row where several keys are lit (484) */
.console[data-keys="sym"] .vz-mkey[aria-pressed="true"]:focus-visible,
.console[data-keys="sym"] .vz-fx > .vz-fx__b[aria-pressed="true"]:focus-visible { outline-offset: -4px; }
.console[data-keys="sym"] .vz-mkey[aria-pressed="true"],
.console[data-keys="sym"] .vz-fx > .vz-fx__b[aria-pressed="true"] { color: #15803D; box-shadow: inset 0 0 0 1.5px #15803D; background: none; z-index: 1; }
html[data-dark] .console[data-keys="sym"] .vz-mkey[aria-pressed="true"],
html[data-dark] .console[data-keys="sym"] .vz-fx > .vz-fx__b[aria-pressed="true"] { color: #4ADE80; box-shadow: inset 0 0 0 1.5px #4ADE80; background: none; }
/* a split key's parts become plain cells, marked as one group by a tick; the
   box and its generated word leave with display: contents — but a box's own
   ::before still renders under display: contents, so the word is removed too */
.console[data-keys="sym"] .vz-msplit:not([hidden]) { display: contents; }
.console[data-keys="sym"] .vz-msplit::before { content: none; }
.console[data-keys="sym"] .vz-msplit > .vz-mkey,
.console[data-keys="sym"] .vz-msplit > .vz-mkey + .vz-mkey { flex-direction: row; border: 0; border-radius: 0; padding: 0; background: none; }
.console[data-keys="sym"] .vz-msplit > .vz-mkey + .vz-mkey:hover:not([aria-pressed="true"]) { background: none; box-shadow: inset 0 0 0 1px var(--accent); }
.console[data-keys="sym"] .vz-mkey[data-split]::after {
    content: ''; position: absolute; left: 3px; right: 3px; bottom: 3px; height: 1.5px;
    background: currentColor; opacity: .55; pointer-events: none;
}
.console[data-keys="sym"] .vz-modepg { display: none; }

/* THE RAIL: nine squares, and the words/symbols key at the end of the row */
.vz-moderail { display: flex; align-items: center; gap: var(--cwd-gap, .4rem); min-width: 0; }
.vz-moderail > .vz-modeg { flex: 1 1 auto; min-width: 0; }
.console[data-keys="sym"] .vz-modeg { flex-wrap: nowrap; align-items: center; }
.console[data-keys="sym"] .vz-gchip { width: var(--cwd-h, 2.125rem); padding: 0; position: relative; flex: 0 0 auto; }
.console[data-keys="sym"] .vz-gchip.is-home::after { position: absolute; top: 3px; right: 3px; margin: 0; }

/* THE KEY THAT SWITCHES THEM. Three instances of one state, each shown only
   where it belongs: at the end of the rail on a desk, at the end of the mode
   heading's tail on a phone, and in the FX heading beside None. */
.vz-symk {
    -webkit-appearance: none; appearance: none; flex: 0 0 auto;
    width: var(--cwd-h, 2.125rem); height: var(--cwd-h, 2.125rem); padding: 0;
    display: inline-flex; align-items: center; justify-content: center;
    color: var(--muted); background: none; cursor: pointer;
    border: 1px solid var(--line); border-radius: var(--cwd-r, 3px);
}
.vz-symk svg { width: 16px; height: 16px; display: block; }
.vz-symk:hover { color: var(--accent); border-color: var(--accent); }
.vz-symk:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.vz-symk[aria-pressed="true"] { color: #15803D; border-color: #15803D; box-shadow: inset 0 0 0 1px #15803D; }
html[data-dark] .vz-symk[aria-pressed="true"] { color: #4ADE80; border-color: #4ADE80; box-shadow: inset 0 0 0 1px #4ADE80; }
.vz-symk--rail, .vz-symk--fx, .vz-symk--head { margin-left: auto; }
@media (min-width: 701px) { .vz-symk--head { display: none; } }

/* THE FX RAIL (490): the mode strip's cells — the same grid, hairlines, pitch
   and green ring, from the shared rules above — on ONE row with its label, the
   Symbols key and None. fxLayout() in app.js writes --sym-cols and --sym-w the
   way symLayout() does for the modes, and data-fxrow="wrap" on the head when
   sixteen cells at the minimum pitch will not fit beside the label: then the
   label grows to fill its line, which sends the strip to the next one, dealt
   into balanced rows. It was sixteen 1fr tracks, and eight on a phone —
   stretched boxes 63px wide at 1400. */
.console[data-keys="sym"] .vz-fxhead { align-items: center; }
.console[data-keys="sym"] .vz-fxhead > .vz-fx { display: grid; flex: 0 0 auto; grid-template-columns: repeat(var(--sym-cols, 16), var(--sym-w, 34px)); }
.console[data-keys="sym"] .vz-fxhead[data-fxrow="wrap"] .vz-fxl { flex: 1 0 auto; }
.console[data-keys="sym"] .vz-fxhead[data-fxrow="wrap"] > .vz-fx { order: 3; }
.console[data-keys="sym"] .vz-fx__b { padding: 0; }
.console[data-keys="sym"] .vz-fx__b--none { width: var(--cwd-h, 2.125rem); padding: 0; justify-content: center; }

@media (pointer: coarse) {
    .console[data-keys="sym"] .vz-mkey,
    .console[data-keys="sym"] .vz-fx > .vz-fx__b,
    .console[data-keys="sym"] .vz-gchip { height: 2.75rem; }
    .console[data-keys="sym"] .vz-gchip { width: 2.75rem; }
    .console[data-keys="sym"] :is(.vz-mkey__g, .vz-gchip__g) { width: 18px; height: 18px; }
    .console[data-keys="sym"] .vz-fx__b { padding: 0; }
    .console[data-keys="sym"] .vz-fx__b--none { width: 2.75rem; }
    .console[data-keys="sym"] .vz-fx__g,
    .console[data-keys="sym"] .vz-fx .vz-fx__g { width: 18px; height: 18px; }
    .console[data-keys="sym"] .vz-shuf { width: 2.75rem; }
    .vz-symk { width: 2.75rem; height: 2.75rem; }
    .vz-symk svg { width: 18px; height: 18px; }
}
@media (max-width: 700px) {
    .console[data-keys="sym"] .vz-modeg { display: grid; grid-template-columns: repeat(9, minmax(0, 1fr)); gap: 0; }
    .console[data-keys="sym"] .vz-modeg .vz-gchip { width: auto; border-radius: 0; margin-left: -1px; }
    .console[data-keys="sym"] .vz-modeg .vz-gchip:first-child { margin-left: 0; border-radius: var(--cwd-r, 3px) 0 0 var(--cwd-r, 3px); }
    .console[data-keys="sym"] .vz-modeg .vz-gchip:nth-child(9) { border-radius: 0 var(--cwd-r, 3px) var(--cwd-r, 3px) 0; }
    .vz-symk--rail { display: none; }
}
/* NINE RAIL CELLS FALL UNDER 33px BELOW 368px (27.7 at 320, 32.1 at 360), so
   the rail takes C21's fallback there: five and four. (The FX cells' own
   fallback, six, six and four under 356px, is fxLayout()'s since 490.) (483 review) */
@media (max-width: 367px) {
    .console[data-keys="sym"] .vz-modeg { grid-template-columns: repeat(5, minmax(0, 1fr)); }
    .console[data-keys="sym"] .vz-modeg .vz-gchip:first-child { border-radius: var(--cwd-r, 3px) 0 0 0; }
    .console[data-keys="sym"] .vz-modeg .vz-gchip:nth-child(5) { border-radius: 0 var(--cwd-r, 3px) 0 0; }
    .console[data-keys="sym"] .vz-modeg .vz-gchip:nth-child(n+6) { margin-top: -1px; }
    .console[data-keys="sym"] .vz-modeg .vz-gchip:nth-child(6) { margin-left: 0; border-radius: 0 0 0 var(--cwd-r, 3px); }
    .console[data-keys="sym"] .vz-modeg .vz-gchip:nth-child(9) { border-radius: 0 0 var(--cwd-r, 3px) 0; }
}
/* FORCED COLOURS flatten the fill, the tint and the inset ring a lit key is
   told apart by, so a checked key looked like every other one: system colours
   say it (483 review; the library rows do the same above) */
@media (forced-colors: active) {
    .vz-mkey[aria-pressed="true"], .vz-tsel__k[aria-checked="true"], .vzs[aria-checked="true"],
    .vz-fx__b[aria-pressed="true"], .vz-tk[aria-pressed="true"], .vz-symk[aria-pressed="true"],
    .dk-sz__k[aria-checked="true"], .dk-pre__k[aria-checked="true"], .dk-chip[aria-pressed="true"] {
        forced-color-adjust: none; background: Highlight !important;
        color: HighlightText !important; border-color: Highlight !important;
    }
}

/* THE STAGE DICE turns a quarter on each press; not under reduced motion */
.vz-sdice svg { transition: transform .18s ease; }
@media (prefers-reduced-motion: reduce) { .vz-sdice svg { transition: none; } }

/* THE NAME OF A SYMBOL: on hover after 250ms, at once on keyboard focus, and
   after a 450ms press on a touch screen, which names the key without pressing it */
.vz-tip {
    position: fixed; left: 0; top: 0; z-index: 1200; pointer-events: none;
    font: inherit; font-size: .66rem; font-weight: 600; line-height: 1.2;
    letter-spacing: .06em; text-transform: uppercase; white-space: nowrap;
    max-width: calc(100vw - 16px); overflow: hidden; text-overflow: ellipsis;
    color: var(--ink); background: var(--paper);
    border: 1px solid var(--line); border-radius: 3px; padding: 4px 6px;
}
.vz-tip[hidden] { display: none; }

/* DETAILED CARDS (484). A Vizzi card's value may carry a second line after a
   newline: the headline says what changed from what to what, the line under
   it the figures (deck.js, slots.js, app.js). The shared card
   (../assets/css/dmt-toast.css, ../assets/js/dmt-toast.js) is left alone,
   since clockwork, moon and RINSE use it too; this sheet is Vizzi's only. */
#toast-host .toast__v, #toast-host .toast__list li > span { white-space: pre-line; }

/* =============================================================================
   THE CODE DASHBOARD (488) — Songbuilder's serial desk, in the empty run of the
   family rail. Two rows of three squares (S, save, open / link, record, T), the
   red-framed code box with its clear, back and go, and the O, S, HR chips under
   it. The whole thing is the rail's own height, so the rail gains no row.
   489: the squares were 16px targets. Two rows of 32px targets cannot fit a 34px
   rail without reaching over the transport keys above and the mode keys below
   (measured), so on a desk they stand in ONE row of six: each button is a 32px
   target holding a 20px drawn square (::before), the rail stays 34px. Touch
   screens get 44px targets (the row may grow there).
   ========================================================================== */
.vz-moderail > .vz-modeg { flex: 1 1 auto; }
.vz-code { display: flex; align-items: center; gap: 2px; flex: 0 0 auto; margin-left: auto;
    height: var(--cwd-h, 2.125rem); }
.vz-code__sq { display: grid; grid-template-columns: repeat(6, 32px); grid-template-rows: 32px; gap: 0; }
.vz-sqb {
    -webkit-appearance: none; appearance: none; box-sizing: border-box; position: relative;
    width: 32px; height: 32px; padding: 0; margin: 0;
    display: inline-flex; align-items: center; justify-content: center;
    background: none; border: 0; border-radius: 3px;
    color: var(--muted); cursor: pointer; font: inherit;
    transition: color .16s;
}
.vz-sqb::before {
    content: ''; position: absolute; left: 50%; top: 50%; width: 20px; height: 20px;
    transform: translate(-50%, -50%); box-sizing: border-box; pointer-events: none;
    border: 1px solid var(--line); border-radius: 3px; background: none;
    transition: border-color .16s, background .16s;
}
.vz-sqb > svg, .vz-sqb > b, .vz-sqb > .vz-recdot { position: relative; }
.vz-sqb svg { width: 12px; height: 12px; }
.vz-sqb--t > b { font: 700 .62rem/1 'Orbitron', ui-monospace, Menlo, monospace; }
.vz-sqb:hover { color: var(--accent); }
.vz-sqb:hover::before { border-color: var(--accent); }
.vz-sqb:focus-visible { outline: 2px solid var(--accent); outline-offset: -4px; }
.vz-sqb--t[aria-pressed="true"] { color: var(--accent); }
.vz-sqb--t[aria-pressed="true"]::before { border-color: var(--accent); }
.vz-recdot { width: 7px; height: 7px; border-radius: 50%; background: currentColor; }
.vz-sqb--rec { color: var(--accent); }
.vz-sqb--rec[aria-pressed="true"] { color: #fff; }
.vz-sqb--rec[aria-pressed="true"]::before { background: var(--accent); border-color: var(--accent); }
/* Narrow desks (701-1180px): the row of six is 136px wider than the old block, so
   where the family rail cannot spare that, the dashboard takes the next line
   instead of squeezing the family chips out of their boxes (measured at 760). */
@media (min-width: 701px) and (max-width: 1180px) {
    .vz-moderail { flex-wrap: wrap; row-gap: 6px; }
    .console[data-keys="sym"] .vz-moderail > .vz-modeg { min-width: max-content; }
}
@media (pointer: coarse) and (min-width: 701px) {
    .vz-code { height: auto; }
    .vz-code__sq { grid-template-columns: repeat(6, 44px); grid-template-rows: 44px; }
    .vz-sqb { width: 44px; height: 44px; }
    .vz-sqb::before { width: 28px; height: 28px; }
}
.vz-sqb--rec[aria-pressed="true"] .vz-recdot { animation: vzRecPulse 1.4s infinite; }
@keyframes vzRecPulse { 50% { opacity: .25; } }
@media (prefers-reduced-motion: reduce) { .vz-sqb--rec[aria-pressed="true"] .vz-recdot { animation: none; } }
.vz-code__stack { display: flex; flex-direction: column; justify-content: space-between; height: 100%; }
.vz-codebox { position: relative; display: block; }
.vz-codebox__i {
    box-sizing: border-box; width: 8.4rem; height: 21px; margin: 0; padding: 0 2.7rem 0 .45rem;
    font: 600 .62rem/1 'Orbitron', ui-monospace, Menlo, monospace; letter-spacing: .12em;
    text-transform: uppercase; font-variant-numeric: tabular-nums;
    color: var(--accent); background: var(--paper); cursor: pointer;
    border: 1px solid var(--accent); border-radius: 4px;
    transition: color .25s, border-color .25s;
}
.vz-codebox__i:focus { cursor: text; outline: none; box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 25%, transparent); }
.vz-codebox__i::placeholder { color: var(--accent); opacity: .45; }
.vz-codebox.is-copied .vz-codebox__i { color: var(--free); border-color: var(--free); }
.vz-codebox__b {
    position: absolute; top: 50%; transform: translateY(-50%);
    background: none; border: 0; padding: 0 .1rem; cursor: pointer;
    font: inherit; font-size: .64rem; line-height: 1; color: var(--accent); opacity: .7;
}
.vz-codebox__b:hover { opacity: 1; }
.vz-codebox__b:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.vz-codebox__x  { right: 1.85rem; font-size: .78rem; }
.vz-codebox__r  { right: 1.02rem; }
.vz-codebox__go { right: .25rem; }
.vz-recopts { display: flex; justify-content: flex-end; gap: 3px; height: 11px; }
.vz-recopt { position: relative; display: inline-flex; align-items: center; cursor: pointer; user-select: none;
    border: 1px solid var(--line); border-radius: 999px; padding: 0 6px;
    font: 700 .44rem/9px 'Orbitron', ui-monospace, Menlo, monospace; letter-spacing: .06em; color: var(--muted); }
.vz-recopt input { position: absolute; inset: 0; width: 100%; height: 100%; opacity: 0; margin: 0; cursor: pointer; }
.vz-recopt:hover { color: var(--ink); }
.vz-recopt:has(input:checked) { background: var(--accent); border-color: var(--accent); color: #fff; }
.vz-recopt:has(input:focus-visible) { outline: 2px solid var(--accent); outline-offset: 1px; }
/* PHONES: the rail is the family grid edge to edge there, with no run to spare
   and no rail key; the dashboard takes one slim line under it instead, squares
   in a single row of six at a touch size, box and chips beside them. */
@media (max-width: 700px) {
    .vz-moderail { flex-wrap: wrap; row-gap: 6px; }
    .vz-moderail > .vz-modeg { flex: 1 1 100%; }
    .vz-code { margin-left: 0; width: 100%; height: auto; gap: 0 5px; flex-wrap: wrap; }
    /* 44px targets on a phone: the six squares take the full width on their own line
       (about 53px each at 390), the box and chips the line under them. */
    .vz-code__sq { flex: 1 1 100%; grid-template-columns: repeat(6, minmax(44px, 1fr)); grid-template-rows: 44px; gap: 0; }
    .vz-sqb { width: 100%; height: 44px; border-radius: var(--cwd-r, 3px); }
    .vz-sqb::before { width: 30px; height: 30px; border-radius: var(--cwd-r, 3px); }
    .vz-sqb svg { width: 14px; height: 14px; }
    .vz-sqb--t > b { font-size: .7rem; }
    .vz-recdot { width: 8px; height: 8px; }
    .vz-code__stack { flex: 1 1 auto; min-width: 0; flex-direction: row; align-items: center; gap: 4px; height: 28px; }
    .vz-codebox { flex: 1 1 auto; min-width: 0; }
    .vz-codebox__i { width: 100%; height: 28px; padding: 0 3.3rem 0 .35rem;
        font: 600 16px/1 ui-monospace, 'SF Mono', Menlo, monospace; letter-spacing: .02em; }
    .vz-codebox__b { font-size: .8rem; padding: .3rem .12rem; }
    .vz-codebox__x { right: 2.2rem; font-size: .95rem; }
    .vz-codebox__r { right: 1.22rem; }
    .vz-codebox__go { right: .3rem; }
    .vz-recopts { height: 28px; flex-direction: column; justify-content: space-between; gap: 1px; }
    .vz-recopt { font-size: .42rem; line-height: 7px; padding: 0 4px; justify-content: center; }
}
