/* WaxCubed theme: warm grays, light geometric type, pill controls and a single amber accent.
   index.html sets data-theme="light" | "dark" on <html> (from Settings, or the OS when on System). */
/* LCD by Fredrick Brennan (github.com/ctrlcctrlv/lcd-font, SIL OFL 1.1, fonts/LCD14-OFL.txt): the 14-segment display face
   (--font-lcd), only ever in a real display (SegmentDisplay, .lcd); every other figure is the theme's (--font-display). Cut down to digits, the punctuation numbers use and the dashes (pyftsubset
   --unicodes="U+0020-0040,U+00A0,U+2013,U+2014,U+2212" --flavor=woff2), 1.6 KB in place of the whole font's 35. */
@font-face {
    font-family: "LCD";
    src: url("../fonts/lcd14-digits.woff2") format("woff2");
    font-display: swap;
}

/* The theme's three faces, kept here rather than fetched from Google Fonts: a stylesheet from another site held up every
   script after it, so the app waited on Google to start, and on a bad signal hung. These are Google's own files (SIL OFL
   1.1, fonts/Figtree-OFL.txt, BricolageGrotesque-OFL.txt and Manrope-OFL.txt) with Google's own unicode-ranges, so the
   browser fetches only the part a page uses: latin for nearly everything, latin-ext only for a name with ł, ő or the like.
   Figtree and Bricolage Grotesque are variable files (Bricolage's optical size too, which the browser sets from the font
   size); each weight range is the one the app asked Google for, so a lighter or heavier weight in the CSS draws at the
   nearest of them, as it did. Manrope (--font-label, the small spaced-out labels) is the one weight, bold. */
@font-face {
    font-family: "Figtree";
    src: url("../fonts/figtree-latin.woff2") format("woff2");
    font-weight: 400 700;
    font-display: swap;
    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: "Figtree";
    src: url("../fonts/figtree-latin-ext.woff2") format("woff2");
    font-weight: 400 700;
    font-display: swap;
    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-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
    font-family: "Bricolage Grotesque";
    src: url("../fonts/bricolage-grotesque-latin.woff2") format("woff2");
    font-weight: 500 700;
    font-display: swap;
    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: "Bricolage Grotesque";
    src: url("../fonts/bricolage-grotesque-latin-ext.woff2") format("woff2");
    font-weight: 500 700;
    font-display: swap;
    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-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
    font-family: "Manrope";
    src: url("../fonts/manrope-latin.woff2") format("woff2");
    font-weight: 700;
    font-display: swap;
    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: "Manrope";
    src: url("../fonts/manrope-latin-ext.woff2") format("woff2");
    font-weight: 700;
    font-display: swap;
    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-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

:root {
    color-scheme: light;

    --canvas: #d6d4cf;          /* kept for the skins, which paint it; the page itself is one --surface edge to edge */
    --surface: #ecebe7;         /* the page, edge to edge, and light panels */
    --card: #ffffff;
    --ink: #1c1c1e;
    --ink-soft: #3a3a3c;
    --muted: #6b675f;           /* about 4.6:1 on the surface and 5.4:1 on a card: small text on one flat surface has nothing else to lean on */
    --indicator: #c27c0e;       /* the bar over the active tab: the amber validated for 3:1 on the light card (--viz-mark); the accent itself in dark */
    --line: rgba(28, 28, 30, 0.08);
    --dark: #1d1d1f;            /* surfaces that stay dark in both themes: dark cards, cube bars */
    --emphasis: #1d1d1f;        /* selected pills (view switcher, nav): dark here, light in dark mode */
    --emphasis-hover: #000;
    --on-emphasis: #fff;
    --accent: #f5a524;          /* amber: actions (primary buttons), highlights */
    --accent-hover: #e8950f;
    --accent-soft: rgba(245, 165, 36, 0.16);
    --on-accent: #1d1d1f;       /* text on amber, both themes */
    --danger: #c62828;          /* the meter's red, deepened so words in it read on white */
    --code-hit: #1a9e5a;        /* a code search's matched characters: a green that pops on white, whatever the accent */
    --code-hit-ink: #ffffff;
    --code-miss: #ffe1dc;       /* and the rest of the code: a soft coral box beside it */
    --code-miss-ink: #b42318;
    --danger-hover: #a51d1d;
    --online: #16a34a;          /* the "active now" dot */
    --recent: #eab308;          /* the "seen in the last 12 hours" dot: a step down from active, a caution light's yellow */
    --earlier: var(--meter-red); /* the "seen 12–24 hours ago" dot: a stop light's red, the meter's, which each theme and skin sets */
    --star: #f2b400;            /* a watched artist's star: always a star's yellow, never the accent, which may be anything */
    --on-danger: #fff;
    --tint-faint: rgba(28, 28, 30, 0.03);   /* row hovers, expanded rows */
    --tint: rgba(28, 28, 30, 0.06);         /* chips, small buttons */
    --tint-strong: rgba(28, 28, 30, 0.12);  /* their hover, tracks */
    --viz-mark: #c27c0e;        /* chart bars/lines: amber validated for >= 3:1 on the light card */
    /* A TickBar's lit ticks, like a stereo's level meter: green most of the way, yellow near the top, red at the end. A shade
       deeper than a meter's LEDs, so each holds on the light card. */
    --meter-green: #2b9a48;
    --meter-yellow: #e0a800;
    --meter-red: #e03a2f;
    --meter-off: 0.2;           /* how much of its color an unlit tick keeps, like an LED that's off */
    --meter-glow: 0.4;          /* how strongly a lit tick glows: faint on the light card, where a glow barely shows */
    /* Hi-fi hardware (the .metal-plate: SplitBar's Balance, EqFader's equalizers, a skin's RangePill), after a Marantz's: brushed aluminium, its engraving, the slot and the chrome knob.
       The brushed plate is a skin's; WAX³'s own look draws it matte in the theme's colors (the --plate-* tokens below). */
    --balance-plate-hi: #eceae4;
    --balance-plate: #cfccc4;
    --balance-plate-lo: #aeaaa1;
    --balance-brush: rgba(255, 255, 255, 0.22);
    --balance-ink: #2e2c28;
    --balance-slot: #0d0d0d;
    --balance-knob-hi: #fbfbfa;
    --balance-knob: #c9c9c6;
    --balance-knob-lo: #8a8a86;
    --balance-ink-soft: #57544d;   /* what sits on the plate beside the slot, quieter: icons, the bands' edges, typical's legend */
    --balance-led: #ff4a2a;         /* a receiver's red LED: RankColumns' lamp over your band, and a skin's fader LEDs to start from */
    --balance-led-glow: rgba(255, 74, 42, 0.7);
    --balance-led-off: #5a2a22;
    /* A row of LEDs (TickBar's LitTicks: Through the years): the meter's green, lit or dark glass, so it follows the theme's
       green, and a skin's own LED color. */
    --meter-led: var(--meter-green);
    --meter-led-glow: color-mix(in srgb, var(--meter-green) 70%, transparent);
    --meter-led-off: color-mix(in srgb, var(--meter-green) 22%, black);
    /* A receiver's pilot lamps (RankColumns: where you are and the typical collection): jewels of colored glass, lit from
       behind with a hot spot and a deeper rim, and dark glass when off. Yours is the LED's red; typical's is a lamp's amber,
       not the accent, which may be anything. */
    --balance-lamp-off-hi: #6b675f;
    --balance-lamp-off: #2a2824;
    --balance-lamp-red-hi: #ffd2c4;
    --balance-lamp-red-lo: #9b1e08;
    --balance-lamp-amber-hi: #fff0c8;
    --balance-lamp-amber: #ffb02e;
    --balance-lamp-amber-lo: #8a5a06;
    --balance-lamp-amber-glow: rgba(255, 176, 46, 0.6);
    /* A display window (.vfd-window: the rank charts' figures), after a tuner's vacuum fluorescent
       display: black-green glass sunk into the plate, its figures a glowing blue-green. Dark in both themes, whatever the plate. */
    --balance-vfd: #0f1412;
    --balance-vfd-ink: #7cf2d0;
    --balance-vfd-glow: rgba(124, 242, 208, 0.6);
    --balance-vfd-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.8), 0 1px 0 var(--balance-plate-hi);
    /* ...put together once, so the Stats balance (SplitBar) and the equalizers (EqFader) are the same metal and knob. */
    --balance-plate-face:
        repeating-linear-gradient(180deg, var(--balance-brush) 0 1px, transparent 1px 3px),
        linear-gradient(180deg, var(--balance-plate-hi), var(--balance-plate) 55%, var(--balance-plate-lo));
    --balance-plate-edge: inset 0 1px 0 var(--balance-plate-hi), inset 0 -1px 0 var(--balance-plate-lo);
    --balance-slot-shadow: inset 0 2px 3px rgba(0, 0, 0, 0.6), 0 1px 0 var(--balance-plate-hi);
    --balance-knob-face:
        radial-gradient(circle at 50% 30%, var(--balance-knob-hi) 0 18%, transparent 55%),
        conic-gradient(from 200deg, var(--balance-knob), var(--balance-knob-hi), var(--balance-knob), var(--balance-knob-lo),
            var(--balance-knob), var(--balance-knob-hi), var(--balance-knob));
    --balance-knob-shadow: 0 3px 5px rgba(0, 0, 0, 0.45), inset 0 -2px 3px rgba(0, 0, 0, 0.25), inset 0 1px 1px var(--balance-knob-hi);
    /* A line engraved across the plate (between the rows of a PlateRack: the equalizers, ranked lists): its dark cut, over
       the plate's lit edge; and the slot cut in a screw head holding it down. */
    --balance-groove: rgba(0, 0, 0, 0.3);
    --balance-screw-slot: rgba(0, 0, 0, 0.45);
    /* WAX³'s own look draws that hardware matte instead (a skin brushes it): bead-blasted aluminium in the light theme and
       black anodized in the dark, a fine even grit rather than streaks, with satin knobs and caps and no sheen. Its ink, lines
       and edges are the theme's own, set on .metal-plate. */
    --plate: #d8d6d1;
    --plate-hi: rgba(255, 255, 255, 0.8);
    --plate-lo: #b5b2ab;
    --plate-ink: #2e2c28;
    --plate-ink-soft: #5d5a53;
    --plate-line: rgba(0, 0, 0, 0.14);
    --plate-tint: rgba(0, 0, 0, 0.06);
    --plate-knob-hi: #f7f6f3;
    --plate-knob: #dddcd8;
    --plate-knob-lo: #a9a7a1;
    --plate-groove: rgba(0, 0, 0, 0.26);
    --plate-screw-slot: rgba(0, 0, 0, 0.45);
    --plate-grit: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='g' x='0' y='0' width='100%25' height='100%25'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' seed='3' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.22 0 0 0 -0.08'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23g)'/%3E%3C/svg%3E");
    --source-wax: #f5a524;                      /* Wantlist records kept in WAX³: its amber */
    --source-wax-soft: rgba(245, 165, 36, 0.12);
    --source-discogs: #5b7fa6;                  /* ...and those from Discogs: a cool slate, so neither reads as the other */
    --source-discogs-soft: rgba(91, 127, 166, 0.10);
    --lent: #a35a06;                            /* a record lent out (LoanChip): a deep amber, the app's own, readable on its tint */
    --lent-soft: rgba(245, 165, 36, 0.16);
    --lent-faint: rgba(245, 165, 36, 0.08);     /* a whole Catalog row that's out: half the chip's tint, so its words stay plain to read */
    --borrowed: #4f6f94;                        /* ...and one borrowed from a friend: slate, so the two never read as each other */
    --borrowed-soft: rgba(91, 127, 166, 0.14);
    --borrowed-faint: rgba(91, 127, 166, 0.07);
    --viewing-admin: #4f6f94;                   /* an admin's View as stripe: the same slate, solid, so it's never a friend's amber */
    --on-viewing-admin: #fff;
    /* Streaming services' own colors, for their Listen buttons; a shade deeper than the brand's so they hold on the light card. */
    --brand-spotify: #1aa34a;
    --brand-apple-music: #e8213a;
    --brand-amazon-music: #0e9aa3;
    --brand-bandcamp: #1a8aa8;
    /* Maps apps' marks (MapMark): each sits on its own tile, as on a phone's home screen, so one set serves both themes. */
    --brand-apple-maps: #0a84ff;
    --brand-apple-maps-land: #d4f0c4;
    --brand-apple-maps-road: #ffffff;
    --brand-apple-maps-line: #f6c544;
    --brand-google-maps-tile: #ffffff;
    --brand-google-blue: #4285f4;
    --brand-google-red: #ea4335;
    --brand-google-yellow: #fbbc04;
    --brand-google-green: #34a853;
    /* Discogs' record (IconName.Discogs) in the colors their brand guidelines allow it: their dark gray on a light page. */
    --discogs-ink: #333333;

    /* Cube locations (Room page pills, the Catalog's Where chips): one dot color each, in order, twelve before they repeat
       so a dozen locations still tell apart. The record-label colors read on both themes' chips, so they're shared rather
       than given dark versions; the last five are mid-tones in the same family, picked to sit apart from those. */
    --location-1: var(--record-label-1);
    --location-2: var(--record-label-5);
    --location-3: var(--record-label-6);
    --location-4: var(--record-label-3);
    --location-5: var(--record-label-4);
    --location-6: var(--record-label-2);
    --location-7: var(--record-label-7);
    --location-8: #5fb4b0;  /* teal */
    --location-9: #9aa7f0;  /* periwinkle */
    --location-10: #d98bd6; /* orchid */
    --location-11: #e5c14a; /* gold */
    --location-12: #c9744f; /* terracotta */

    /* Record labels for releases with no cover (BlankRecord). The record is black in both themes, so these
       don't change with it; all light enough for --on-accent text. */
    --record-label-1: #f5a524;  /* amber, like the WAX³ label */
    --record-label-2: #f4845f;  /* coral */
    --record-label-3: #f28dbb;  /* pink */
    --record-label-4: #b9a3f5;  /* lilac */
    --record-label-5: #7cc4f2;  /* sky */
    --record-label-6: #6fd3a8;  /* mint */
    --record-label-7: #c5e06b;  /* lime */
    --record-label-8: #f1e3c2;  /* cream */

    /* Record spines picked in a cube (SpineColors, the same order): the colors vinyl usually comes in, the same in both
       themes as sleeves are. Deep ones (teal on) take white text. Black lifts a little in dark mode so it isn't lost on the card. */
    --spine-1: #f5a524;   /* amber */
    --spine-2: #f4845f;   /* coral */
    --spine-3: #f28dbb;   /* pink */
    --spine-4: #b9a3f5;   /* lilac */
    --spine-5: #7cc4f2;   /* sky */
    --spine-6: #6fd3a8;   /* mint */
    --spine-7: #c5e06b;   /* lime */
    --spine-8: #ecdcb4;   /* cream: a shade deeper than the label's, so it shows on the white card */
    --spine-9: #2f8f8a;   /* teal */
    --spine-10: #2d4a7a;  /* navy */
    --spine-11: #8a2b2b;  /* oxblood */
    --spine-12: #1c1c1e;  /* black */
    --spine-gloss: rgba(255, 255, 255, 0.22);   /* the sliver of light down a spine's edge on a RecordShelf */

    /* A shelf cube (RecordShelf): walnut, its back a darker shade into shadow, and the shadow its top casts in. A shade
       deeper in dark mode, so it sits on the dark card without glowing. */
    --cube-wall: #6b4429;
    --cube-wall-edge: #573520;
    --cube-back: #3e2717;
    --cube-shadow: rgba(0, 0, 0, 0.16);

    /* Christmas lights (HolidayLights): bright as the real bulbs on a dark green wire, the same in both themes. */
    --lights-red: #ff3b30;
    --lights-green: #34d27b;
    --lights-amber: #ffb020;
    --lights-blue: #3d8bff;
    --lights-pink: #ff5ec4;
    --lights-wire: #1e5a32;
    --lights-socket: #24372a;
    --lights-glint: rgba(255, 255, 255, 0.75);

    /* Halloween's ghosts (HalloweenGhosts, drawn by js/ghosts.js): the 1980 arcade's own colors, the same in both themes. */
    --ghost-blinky: #ff0000;
    --ghost-pinky: #ffb8ff;
    --ghost-inky: #00ffff;
    --ghost-clyde: #ffb852;
    --ghost-eye: #dedeff;
    --ghost-pupil: #2121de;

    /* The broken record on the crash overlay: black vinyl in both themes, like the real thing. */
    --crash-vinyl: #161617;
    --crash-groove: #2c2c2e;
    --crash-edge: transparent;  /* the pieces' outline; only needed where the card is nearly as dark as they are */

    /* The camera while scanning a barcode (BarcodeScanner): black around the picture in both themes, as a camera app is, and
       its frame turning green once it has read one, like a shop's scanner beeping. */
    --camera: #000;
    --camera-read: #34d27b;

    /* Walnut behind those records: the page's dark grays would hide a black record, so this stays the same in both themes. */
    --walnut: #6b4428;

    /* The JBL L100 on announcements (L100Speaker) is drawn as the real thing, so it's the same in both themes. */
    --speaker-baffle: #151515;
    --speaker-edge: #3a3a3a;        /* hairline round the cabinet and the label's plate */
    --speaker-frame: #1f1f1f;       /* woofer basket, knobs */
    --speaker-surround: #0a0a0a;
    --speaker-port: #030303;
    --speaker-foam: #2a2a2a;        /* the tweeter's diamond */
    --speaker-foam-hi: #353535;
    --speaker-cone-hi: #f8f5ec;     /* white paper cones */
    --speaker-cone: #ebe5d6;
    --speaker-cone-shade: #c4b9a2;
    --speaker-cap-hi: #9a9080;      /* bronze dust caps and tweeter dome */
    --speaker-cap: #4f483e;
    --speaker-metal-hi: #dcdee1;    /* the midrange's frame */
    --speaker-metal: #7d8086;
    --speaker-label: #ecebe6;
    --speaker-print: #8b8a86;
    --speaker-peg: #efe7cf;         /* cream grille pegs */

    /* The rest of the drawings posts wear (PostMark: phones, a turntable, a 45, a sleeve, stage lights), drawn as the real
       things are, so they're the same in both themes. Only --mark-edge changes: a hairline round the black ones, which
       would melt into the dark card without it. */
    --mark-vinyl: #161616;
    /* What a record's center label is made of in a drawing of it (RecordFigures: Birthdays' anniversaries), the same in
       either theme, as real metal and stone are: each material's light, body and shade for its turned sheen, and the ink
       printed on it. */
    --material-black-hi: #4a4a4c;
    --material-black-mid: #232325;
    --material-black-lo: #0e0e0f;
    --material-black-on: #f2f1ed;
    --material-silver-hi: #f4f6f8;
    --material-silver-mid: #b9bec6;
    --material-silver-lo: #7d838c;
    --material-silver-on: #2b2f35;
    --material-pearl-hi: #fffdf7;
    --material-pearl-mid: #ece4d6;
    --material-pearl-lo: #d9e7f0;
    --material-pearl-on: #4a4136;
    --material-ruby-hi: #ff6b8a;
    --material-ruby-mid: #b3123a;
    --material-ruby-lo: #5e0a20;
    --material-ruby-on: #fff4f6;
    --material-gold-hi: #fff1b8;
    --material-gold-mid: #d4a72c;
    --material-gold-lo: #8a6512;
    --material-gold-on: #3d2a00;
    --material-diamond-hi: #ffffff;
    --material-diamond-mid: #cfe9f5;
    --material-diamond-lo: #8fb7c9;
    --material-diamond-on: #20414f;
    --mark-groove: #262626;
    --mark-label-hi: #f7b544;       /* a plain record's amber label, its light and dark sides */
    --mark-label-lo: #d98c0f;
    --mark-edge: transparent;
    --mark-black: #000;             /* a phone's camera, shadows laid over a label or a sleeve */
    --mark-glint: #fff;             /* highlights, and the words on a phone's screen, at their own opacities */
    --mark-screen-hi: #2b2b30;
    --mark-screen: #0e0e10;
    --mark-phone: #1c1c1e;          /* the iPhone's black glass */
    --mark-titanium: #8f8a82;       /* ...and its natural titanium edge */
    --mark-android: #2c2f33;
    --mark-android-glow: #14301f;   /* the green the robot casts up its screen */
    --mark-android-green: #3ddc84;
    --mark-walnut-hi: #84573a;      /* the turntable's plinth, either side of --walnut */
    --mark-walnut-lo: #563620;
    --mark-plinth: #1b1b1c;
    --mark-steel-hi: #e6e7ea;       /* platter rim, tonearm */
    --mark-steel: #8b8e94;
    --mark-arm-base: #2a2a2c;
    --mark-pitch: #3a3a3c;
    --mark-adapter: #f2c230;        /* the 45's yellow spider adapter */
    --mark-adapter-lo: #c99a12;
    --mark-single-label: var(--record-label-2);
    --mark-sleeve: #0b0b0e;         /* the prism cover's black, its glass and its spectrum */
    --mark-prism-glass: #b9dbe8;
    --mark-prism-red: #e8352e;
    --mark-prism-orange: #f39129;
    --mark-prism-yellow: #f6d93e;
    --mark-prism-green: #4fb84a;
    --mark-prism-blue: #43b6e6;
    --mark-prism-violet: #a067bb;
    --mark-stage-sky-top: #0a0614;  /* stage lights: a light show only reads on black, so it has its own night sky */
    --mark-stage-sky: #1b0f3a;
    --mark-stage-haze: #6b3cff;
    --mark-stage-lavender: #c7a8ff;
    --mark-stage-violet: #8f5bff;
    --mark-stage-blue: #5b8cff;
    --mark-stage-mint: #7ff0c0;
    --mark-stage-green: #3fd99a;
    --mark-stage-floor: #2a1d52;
    --mark-stage-crowd: #06030d;

    --radius-xl: 28px;
    --radius-lg: 20px;
    --radius-md: 14px;
    --radius-sm: 12px;          /* covers in dialogs and cards, tiles */
    --radius-xs: 10px;          /* small covers in rows, row highlights, chart tips */
    --radius-2xs: 6px;          /* the tiniest covers, the checkbox */
    --radius-cube: 6px;         /* a cube wherever one is drawn to aim at: Layout, Organize, the phone's drop tray */
    /* Every control's corner (pills, buttons, chips, fields, Dropdowns, Segmented, a Switch's track): softly squared rather
       than a capsule, so controls sit flush with the rows on one surface. Anything round is a 50% rule and stays round. */
    --radius-pill: 8px;
    /* The icon-over-word buttons (Catalog's views and orders, Organize's toolbar): the same corner, a choice's a little tighter inside it. */
    --radius-labeled-pill: 8px;
    --radius-labeled-choice: 6px;
    /* The text inset on one flat surface: tight, since no frame or card adds its own. MainLayout's frame pads the page by it,
       and what runs edge to edge (Home's hero, a strip to swipe, Room's window) steps past it with .full-bleed below. */
    --gutter: 16px;
    /* The space between rows stacked at the top of a page (PageHeader's title, its line, the search, the buttons wrapped
       under it, and a page's own bar under the header, as the Catalog's view bar): one rhythm on every page, a touch roomier
       than the rows' own gaps, so the head doesn't look crammed. 16px on a phone (below). */
    --stack-gap: 18px;
    /* How tall an icon-over-word pill on a toolbar is (Organize's, its 46px choices in 3px of padding), and its More beside it:
       Room's header is this tall in both views, so the room under it doesn't move as the tabs switch. */
    --labeled-height: 52px;
    /* A button over cover art (a heart, a star, a deal's •••, Friends’ Picks' ×): dark glass with a white glyph, a fine light
       ring and a deeper shadow, the same in both themes, so it stands out on pale sleeves and dark ones alike. A heart or star
       that's on keeps its own color; a sent ••• its accent. */
    --on-cover-fill: rgba(16, 16, 18, 0.72);
    --on-cover-fill-hover: rgba(16, 16, 18, 0.86);
    --on-cover-ink: #fff;
    --on-cover-edge: inset 0 0 0 1px rgba(255, 255, 255, 0.3), 0 2px 8px rgba(0, 0, 0, 0.35);
    --shadow-soft: 0 1px 2px rgba(0, 0, 0, 0.04), 0 8px 24px rgba(0, 0, 0, 0.06);
    /* Menus and panels that float over the page (bell, user menu, suggestions): a hairline edge plus a deeper
       shadow, so they read as a separate layer even where the card color matches what's behind them. */
    --shadow-pop: 0 0 0 1px var(--line), 0 12px 32px rgba(0, 0, 0, 0.18);
    /* Dialogs (ModalDialog) over the dimmed, blurred page; toasts that float over it. */
    --shadow-dialog: 0 24px 60px rgba(0, 0, 0, 0.25);
    --shadow-toast: 0 12px 32px rgba(0, 0, 0, 0.25);
    /* Focus from the keyboard, the same ring on every control: a thin accent line at its edge in a soft halo, following
       its corners. Inset, the same ring drawn inward, for something a plate, a list or a card would cut it off in. Text
       fields (an accent border and the halo), menu rows (a tint) and controls on a dark surface keep their own. */
    --focus-ring: 0 0 0 3px var(--accent-soft), 0 0 0 1px var(--accent);
    --focus-ring-inset: inset 0 0 0 3px var(--accent-soft), inset 0 0 0 1px var(--accent);
    --backdrop: rgba(20, 20, 22, 0.45);
    --backdrop-light: rgba(0, 0, 0, 0.12);  /* behind a popover (help), which doesn't take the whole screen */

    /* Text and controls on surfaces that stay dark in both themes (--dark, Home's hero, Organize's cube bars). */
    --on-dark: #fff;
    --on-dark-soft: rgba(255, 255, 255, 0.8);
    --on-dark-muted: rgba(255, 255, 255, 0.6);
    --on-dark-faint: rgba(255, 255, 255, 0.4);
    --on-dark-tint: rgba(255, 255, 255, 0.1);
    --on-dark-tint-strong: rgba(255, 255, 255, 0.2);
    --on-dark-danger: #ff5a4c;  /* over a max: the dark theme's brighter red, since the light one is too dim on dark */

    /* ...and on Home's search, which stays white on that hero in both themes. */
    --on-light: #1c1c1e;
    --on-light-soft: #3a3a3c;
    --on-light-muted: #8b8a86;
    --on-light-tint: rgba(28, 28, 30, 0.07);
    --on-light-tint-strong: rgba(28, 28, 30, 0.12);

    /* Text sizes for the app's UI, in 0.05rem steps from the smallest uppercase label to body copy, so near-equal sizes
       don't drift apart page by page. Headings and display figures set their own. */
    --text-65: 0.65rem;
    --text-70: 0.7rem;
    --text-75: 0.75rem;
    --text-80: 0.8rem;
    --text-85: 0.85rem;
    --text-90: 0.9rem;
    --text-95: 0.95rem;
    --text-100: 1rem;
    --text-105: 1.05rem;

    /* Figtree for everything people read and tap; Bricolage Grotesque for headlines and big numbers. */
    --font: "Figtree", -apple-system, BlinkMacSystemFont, "Helvetica Neue", Arial, sans-serif;
    --font-display: "Bricolage Grotesque", "Figtree", -apple-system, BlinkMacSystemFont, "Helvetica Neue", Arial, sans-serif;
    --figure-char-width: 0.55;  /* about how wide a character of --font-display is, in ems, for a figure shrinking to fit (StatTile) */
    /* Manrope Bold only, for the lettering on a record with no cover: BlankRecord.Width is measured against it. */
    --font-label: "Manrope", -apple-system, BlinkMacSystemFont, "Helvetica Neue", Arial, sans-serif;
    /* A chart's counts beside its meters, as a stereo's segment display shows them (the LCD @font-face at the top); anything it lacks falls back. */
    --font-lcd: "LCD", "Bricolage Grotesque", -apple-system, BlinkMacSystemFont, "Helvetica Neue", Arial, sans-serif;
    /* A display window's figures and words (.vfd-window), in the device's own monospace: even-width like a display's cells,
       with the letters the LCD face is cut down without, and no web font to fetch. */
    --font-vfd: ui-monospace, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
}

/* On a phone the cubes are drawn small, so their corners tighten with them, and the text inset is tighter still. */
@media (max-width: 640px) {
    :root {
        --radius-cube: 3px;
        --gutter: 10px;
        --stack-gap: 16px;
    }
}

/* Dark: near-black canvas, charcoal frame, lifted cards, same amber. Dark cards are lifted above
   the surface so they still read as cards; emphasis flips to light-on-dark. */
:root[data-theme="dark"] {
    color-scheme: dark;

    --spine-12: #48484d;        /* black spines, lifted off the dark card */
    --spine-gloss: rgba(255, 255, 255, 0.14);
    --cube-wall: #5a3922;
    --cube-wall-edge: #6e4a2f;
    --cube-back: #2b1b10;
    --cube-shadow: rgba(0, 0, 0, 0.55);

    --canvas: #0d0d0e;
    --surface: #18181a;
    --card: #222225;
    --ink: #f2f1ed;
    --ink-soft: #d4d2cc;
    --muted: #8f8e89;
    --indicator: var(--accent); /* the accent reads on the dark card, so the active tab's bar is whatever accent they picked */
    --line: rgba(255, 255, 255, 0.08);
    --dark: #2c2c30;
    --emphasis: #f2f1ed;
    --emphasis-hover: #ffffff;
    --on-emphasis: #1c1c1e;
    --accent-hover: #ffb84a;
    --accent-soft: rgba(245, 165, 36, 0.22);
    --danger: #ff5a4c;          /* the meter's red, as the dark theme lights it */
    --code-hit: #3ddc84;        /* a code search's matched characters: a bright green on the dark card */
    --code-hit-ink: #06210f;
    --code-miss: #4a2024;       /* and the rest of the code: a deep coral box, its letters lit */
    --code-miss-ink: #ff9b8f;
    --danger-hover: #ff7b6f;
    --online: #22c55e;
    --recent: #facc15;
    --plate: #27272a;
    --plate-hi: rgba(255, 255, 255, 0.08);
    --plate-lo: #121214;
    --plate-ink: #e7e5df;
    --plate-ink-soft: #a29f97;
    --plate-line: rgba(255, 255, 255, 0.12);
    --plate-tint: rgba(255, 255, 255, 0.07);
    --plate-knob-hi: #d9d9d6;
    --plate-knob: #b3b3b0;
    --plate-knob-lo: #6e6e6b;
    --plate-groove: rgba(0, 0, 0, 0.7);         /* nearly black on the anodized plate, so the cut reads as cut into it */
    --plate-screw-slot: rgba(0, 0, 0, 0.6);
    --plate-grit: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='g' x='0' y='0' width='100%25' height='100%25'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' seed='3' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0.24 0 0 0 -0.095'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23g)'/%3E%3C/svg%3E");
    --star: #ffcc33;
    --on-danger: #1c1c1e;       /* white on this brighter red would be too faint */
    --tint-faint: rgba(255, 255, 255, 0.035);
    --tint: rgba(255, 255, 255, 0.07);
    --tint-strong: rgba(255, 255, 255, 0.14);
    --viz-mark: #c98500;        /* validated for the dark card (lightness band + >= 3:1) */
    --meter-green: #3ccf62;     /* the meter's LEDs, bright on the dark card */
    --meter-yellow: #ffd23f;
    --meter-red: #ff5a4c;
    --meter-off: 0.18;
    --meter-glow: 0.75;
    --source-wax-soft: rgba(245, 165, 36, 0.10);
    --source-discogs: #8fb0d6;
    --source-discogs-soft: rgba(143, 176, 214, 0.08);
    --lent: #f7b955;
    --lent-soft: rgba(245, 165, 36, 0.16);
    --lent-faint: rgba(245, 165, 36, 0.08);
    --borrowed: #9dbadc;
    --borrowed-soft: rgba(143, 176, 214, 0.14);
    --borrowed-faint: rgba(143, 176, 214, 0.07);
    --viewing-admin: #9dbadc;
    --on-viewing-admin: #1c1c1e;
    --brand-spotify: #1ed760;
    --brand-apple-music: #fa2d48;
    --brand-amazon-music: #25d1da;
    --brand-bandcamp: #1da0c3;
    --discogs-ink: #ffffff;     /* ...and their white on a dark one */
    --shadow-soft: 0 1px 2px rgba(0, 0, 0, 0.4), 0 8px 24px rgba(0, 0, 0, 0.35);
    /* A dark shadow barely shows on a near-black page, so in dark a faint light edge and glow mark the boundary. */
    --shadow-pop: 0 0 0 1px rgba(255, 255, 255, 0.12), 0 0 24px rgba(255, 255, 255, 0.07), 0 16px 40px rgba(0, 0, 0, 0.6);
    /* The black record on the dark card: a faint light edge round each piece, or the cracks between them vanish. */
    --crash-edge: rgba(255, 255, 255, 0.45);
    --mark-edge: rgba(255, 255, 255, 0.16);  /* ...and round the black drawings posts wear (PostMark) */
}

/* Their own accent color (Premium, AccentTheme): index.html's waxCubedTheme.accent sets --accent and --on-accent on <html>
   with data-accent, and the hover and the soft wash are worked out from it as the amber's are: a shade darker on the light
   theme and lighter on the dark, the wash a touch stronger on dark cards. */
:root[data-accent] {
    --accent-hover: color-mix(in srgb, var(--accent), #000 10%);
    --accent-soft: color-mix(in srgb, var(--accent) 16%, transparent);
}

:root[data-accent][data-theme="dark"] {
    --accent-hover: color-mix(in srgb, var(--accent), #fff 18%);
    --accent-soft: color-mix(in srgb, var(--accent) 22%, transparent);
}

html, body {
    font-family: var(--font);
    color: var(--ink);
    background: var(--canvas);
    -webkit-font-smoothing: antialiased;
}

/* Pan and tap only: no double-tap zoom (pinch zoom is off in index.html). Also drops the tap delay. */
html {
    touch-action: manipulation;
}

/* Bootstrap asks for smooth scrolling on the root, which turned the jump to the top on every new page into a long glide
   up through a page you'd just left, and put a page closed back to into a glide down too. Scrolls that should glide (back
   to top, a section brought into view) ask for it themselves. */
:root {
    scroll-behavior: auto;
}

/* Feels like an app, not a page: tapping, holding or dragging anywhere doesn't start selecting text, and holding a link
   or picture on iOS doesn't pop up Safari's menu. What people type in stays selectable, so they can still fix a typo or
   copy what they wrote. */
body {
    -webkit-user-select: none;
    user-select: none;
    -webkit-touch-callout: none;
}

input,
textarea,
[contenteditable="true"] {
    -webkit-user-select: text;
    user-select: text;
}

/* While a modal dialog is open (a record's details, a confirmation, …) the page behind it holds still, so
   there's one scrollbar, the dialog's. The gutter stays, so the page doesn't shift sideways when it locks. A menu's
   backdrop (the account menu, a record's Share, the bell) holds it still too: without it, a finger dragged over the
   account menu on a phone scrolled Home underneath it while the menu stayed put. */
html:has(dialog:modal, .menu-backdrop) {
    overflow: hidden;
    scrollbar-gutter: stable;
}

/* The same lock, held by js/scrolllock.js for every open dialog and sheet (ModalDialog), for a web view that doesn't know
   :has() (iOS before 15.4). */
html[data-scroll-locked] {
    overflow: hidden;
    scrollbar-gutter: stable;
}

/* On a touch screen the page is also pinned where it was: WebKit before iOS 16 still pans
   a page under a finger through overflow: hidden. scrolllock.js puts it back at the same spot when the last dialog closes.
   The body itself doesn't clip, so what's sticky (the Catalog's bar) still sticks to the top of the screen meanwhile. */
@media (pointer: coarse) {
    html[data-scroll-locked] body {
        position: fixed;
        top: var(--scroll-locked-top, 0);
        right: 0;
        left: 0;
    }
}

/* With viewport-fit=cover (and as an installed iOS app) the page runs under the status bar, notch and
   home indicator; keep the app clear of them. The insets are 0 everywhere else. The page reaches back out over the side
   ones (MainLayout's .page), so a phone held sideways still runs its bands to the screen's edges. */
body {
    padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left);
}

h1, h2, h3, h4 {
    font-family: var(--font-display);
    font-weight: 600;
    letter-spacing: -0.03em;
    color: var(--ink);
}

h1 {
    font-size: clamp(1.75rem, 3vw, 2.25rem);
}

h1:focus {
    outline: none;
}

a, .btn-link {
    color: var(--ink);
}

.text-muted {
    color: var(--muted) !important;
}

/* Pill buttons */
.btn {
    border-radius: var(--radius-pill);
    font-weight: 500;
    padding: 0.45rem 1.1rem;
}

/* Buttons are themed through Bootstrap's button variables, so hover, focus, active and disabled
   states all follow the theme rather than Bootstrap's own colors. */
.btn {
    --bs-btn-focus-shadow-rgb: 245, 165, 36;
    --bs-btn-disabled-opacity: 1;
    --bs-btn-font-size: var(--text-90); /* one size on every page's toolbars, forms and dialogs, not Bootstrap's 1rem */
}

/* A smaller button, for actions inside a list's rows, a toast, a banner or the bell. */
.btn-compact {
    --bs-btn-font-size: var(--text-80);
    padding: 0.3rem 0.85rem;
}

/* Button roles, used the same way on every page:
   - btn-primary: the main thing to do (amber). Usually one per area.
   - btn-secondary: other actions (amber outline): reload, show more, split…
   - btn-light: neutral: Cancel, Decline, toggles.
   - btn-danger: removes or clears something; always asks first.
   - btn-danger-outline: the way into something that can't be undone (Settings’ Remove), whose dialog's
     btn-danger then does it. Quieter than btn-danger, so it never looks like the next thing to press.
   Selected states (the view switcher, nav) use --emphasis instead, so they never look like actions. */
.btn-primary {
    --bs-btn-color: var(--on-accent);
    --bs-btn-bg: var(--accent);
    --bs-btn-border-color: var(--accent);
    --bs-btn-hover-color: var(--on-accent);
    --bs-btn-hover-bg: var(--accent-hover);
    --bs-btn-hover-border-color: var(--accent-hover);
    --bs-btn-active-color: var(--on-accent);
    --bs-btn-active-bg: var(--accent-hover);
    --bs-btn-active-border-color: var(--accent-hover);
    --bs-btn-disabled-color: var(--muted);
    --bs-btn-disabled-bg: var(--accent-soft);
    --bs-btn-disabled-border-color: transparent;
}

.btn-secondary {
    --bs-btn-color: var(--ink);
    --bs-btn-bg: var(--button-fill, var(--surface));
    --bs-btn-border-color: var(--accent);
    --bs-btn-hover-color: var(--ink);
    --bs-btn-hover-bg: color-mix(in srgb, var(--accent) 18%, var(--button-fill, var(--surface)));
    --bs-btn-hover-border-color: var(--accent-hover);
    --bs-btn-active-color: var(--ink);
    --bs-btn-active-bg: color-mix(in srgb, var(--accent) 18%, var(--button-fill, var(--surface)));
    --bs-btn-active-border-color: var(--accent-hover);
    --bs-btn-disabled-color: var(--muted);
    --bs-btn-disabled-bg: var(--button-fill, var(--surface));
    --bs-btn-disabled-border-color: var(--tint-strong);
    border-width: 1.5px;
}

/* On a surface that stays dark in both themes (Organize's cube bars): the same amber outline, its words light. */
.btn-secondary.on-dark {
    --button-fill: var(--dark);
    --bs-btn-color: var(--on-dark);
    --bs-btn-hover-color: var(--on-dark);
    --bs-btn-active-color: var(--on-dark);
}

.btn-light {
    --bs-btn-color: var(--ink);
    --bs-btn-bg: var(--card);
    --bs-btn-border-color: var(--card);
    --bs-btn-hover-color: var(--ink);
    --bs-btn-hover-bg: color-mix(in srgb, var(--card), var(--ink) 6%);
    --bs-btn-hover-border-color: transparent;
    --bs-btn-active-color: var(--ink);
    --bs-btn-active-bg: color-mix(in srgb, var(--card), var(--ink) 10%);
    --bs-btn-active-border-color: transparent;
    --bs-btn-disabled-color: var(--muted);
    --bs-btn-disabled-bg: var(--tint);
    --bs-btn-disabled-border-color: transparent;
}

.btn-danger {
    --bs-btn-color: var(--on-danger);
    --bs-btn-bg: var(--danger);
    --bs-btn-border-color: var(--danger);
    --bs-btn-hover-color: var(--on-danger);
    --bs-btn-hover-bg: var(--danger-hover);
    --bs-btn-hover-border-color: var(--danger-hover);
    --bs-btn-active-color: var(--on-danger);
    --bs-btn-active-bg: var(--danger-hover);
    --bs-btn-active-border-color: var(--danger-hover);
    --bs-btn-disabled-color: var(--on-danger);
    --bs-btn-disabled-bg: var(--danger);
    --bs-btn-disabled-border-color: var(--danger);
}

.btn-danger:disabled {
    opacity: 0.55;
}

/* Not Bootstrap's btn-outline-danger, whose colors aren't the theme's. */
.btn-danger-outline {
    --bs-btn-color: var(--danger);
    --bs-btn-bg: var(--button-fill, var(--surface));
    --bs-btn-border-color: var(--danger);
    --bs-btn-hover-color: var(--on-danger);
    --bs-btn-hover-bg: var(--danger);
    --bs-btn-hover-border-color: var(--danger);
    --bs-btn-active-color: var(--on-danger);
    --bs-btn-active-bg: var(--danger-hover);
    --bs-btn-active-border-color: var(--danger-hover);
    --bs-btn-disabled-color: color-mix(in srgb, var(--danger) 55%, var(--button-fill, var(--surface)));
    --bs-btn-disabled-bg: var(--button-fill, var(--surface));
    --bs-btn-disabled-border-color: color-mix(in srgb, var(--danger) 55%, var(--button-fill, var(--surface)));
    border-width: 1.5px;
}

/* A button with an icon before its label. */
.btn-icon {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
}

.btn-icon svg {
    width: 16px;
    height: 16px;
    flex: none;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.8;
    stroke-linecap: round;
    stroke-linejoin: round;
}

/* Links are never underlined, at rest, pointed at or focused: Bootstrap underlines every <a> (and .btn-link), and a row or
   card that's a link read as cluttered text. A link reads by where it is and its color; focus has its own ring. No
   component underlines one of its own (ControlConsistencyTests). */
a,
a:hover,
a:focus,
a:focus-visible,
.btn-link,
.btn-link:hover {
    text-decoration: none;
}

/* A round icon button beside a page's title or tools (Favorites only, Lent only, help), the size of the segmented
   controls next to it. Every dark circle button outside the main menu is the same: btn-secondary's amber ring (its
   1.5px) filled with the shared --button-fill (the color it sits on, never see-through), so it reads the same as Reload
   or Refresh turned round on a phone, on a card or off it. */
.round-button {
    flex: none;
    display: grid;
    place-items: center;
    width: 38px;
    height: 38px;
    padding: 0;
    border: 1.5px solid var(--accent);
    border-radius: 50%;
    background: var(--button-fill, var(--surface));
    color: var(--ink);
    transition: background-color 150ms, color 150ms;
}

.round-button:hover {
    background: color-mix(in srgb, var(--accent) 18%, var(--button-fill, var(--surface)));
}

.round-button:focus-visible {
    outline: none;
    box-shadow: var(--focus-ring);
}

.round-button svg {
    width: 18px;
    height: 18px;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.8;
    stroke-linecap: round;
    stroke-linejoin: round;
}

/* A finger's width on a phone; the glyph stays as drawn. */
@media (max-width: 640px) {
    .round-button {
        width: 44px;
        height: 44px;
    }
}

/* The round × that closes a dialog, sheet, panel, the help bubble or a toast: the one close look, every round button's
   (.round-button: btn-secondary's amber ring on the shared --button-fill, never see-through), and as big as one (38px, a
   finger's 44px on a phone), so it matches the heart or the place picker beside it and a page's own close (CloseButton). */
.close-button {
    flex: none;
    display: grid;
    place-items: center;
    width: 38px;
    height: 38px;
    padding: 0;
    border: 1.5px solid var(--accent);
    border-radius: 50%;
    background: var(--button-fill, var(--surface));
    color: var(--ink);
    transition: background-color 120ms ease;
}

.close-button:hover {
    background: color-mix(in srgb, var(--accent) 18%, var(--button-fill, var(--surface)));
}

.close-button:focus-visible {
    outline: none;
    box-shadow: var(--focus-ring);
}

.close-button svg {
    width: 16px;
    height: 16px;
    fill: none;
    stroke: currentColor;
    stroke-width: 2;
    stroke-linecap: round;
}

/* A small one, for toasts. */
.close-button.small {
    width: 30px;
    height: 30px;
}

.close-button.small svg {
    width: 14px;
    height: 14px;
}

/* A close button that stays in reach however far what it closes is scrolled (a page, an ActionSheet, the record
   overlay): a line of no height, as wide as what scrolls and straight inside it, sticking --close-dock-top from its top,
   with the button hanging from its right end, --close-dock-drop down to where it sits at rest. The line, not the button,
   is what sticks, since a sticky box only stays in view within its parent, and a button's parent is usually a small
   header that scrolls away whole. Where the button sits at rest, its header keeps a .close-space as big as it. Things
   scroll under it, so it's never see-through: the close button's usual tint is laid over the card color. */
.close-dock {
    position: sticky;
    z-index: 20;
    top: var(--close-dock-top, 0.75rem);
    display: flex;
    justify-content: flex-end;
    height: 0;
}

.close-dock > * {
    margin-top: var(--close-dock-drop, 0);
}

.close-dock .close-button {
    box-shadow: var(--shadow-soft);
}

.close-dock .close-button:focus-visible {
    box-shadow: var(--focus-ring);
}

/* The docked button's place at rest, kept empty: a .close-button's size, or a round IconButton's with .small. */
.close-space {
    flex: none;
    width: 38px;
    height: 38px;
}

/* A finger's width on a phone, as every round button is. */
@media (max-width: 640px) {
    .close-button:not(.small),
    .close-space:not(.small),
    .icon-button.close-page {
        width: 44px;
        height: 44px;
    }
}

.close-space.small {
    width: 30px;
    height: 30px;
}

/* The heading over one part of a record's details (TrackList, CreditList, ReleaseNotes): the same size and gap above
   what it heads, whichever part it is. */
.detail-heading {
    margin: 0 0 0.6rem;
    font-size: var(--text-95);
    font-weight: 500;
}

/* What a page or list says when there's nothing to show (EmptyState): a quiet line straight on the surface, with anything
   that gets you going under it; no card round it. */
.empty-state {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 1rem;
    padding: 1.25rem 0;
    color: var(--muted);
}

.empty-state p {
    margin: 0;
}

.empty-state p a {
    color: var(--ink);
}

/* Whose collection you're viewing (FriendViewingNote). A friend's wantlist is shown under it, so it keeps a gap from the
   list rather than sitting flush on it. */
.empty-state.viewing-note {
    margin-bottom: 1rem;
}

.empty-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.6rem;
}

/* One setting (SettingRow): its label and what it does on the left, its control on the right, a hairline between rows. */
.setting-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    padding: 0.85rem 0;
    border-top: 1px solid var(--line);
}

/* Straight under a section's heading, the heading is the divide. */
:is(h2, h3, .section-heading) + .setting-row {
    border-top: 0;
}

/* A sub-option of the setting above it. */
.setting-row.nested {
    padding-left: 1rem;
    border-top-style: dashed;
}

/* Closer together, for a long list of switches in a card that should stay short. */
.setting-row.dense {
    min-height: 2.25rem;
    padding: 0.3rem 0;
    border-top: 0;
    border-bottom: 1px solid var(--line);
}

/* A wide control (several checkboxes) under the words rather than squeezed beside them. */
.setting-row.stacked {
    flex-direction: column;
    align-items: stretch;
    gap: 0.75rem;
}

/* A Premium setting the user doesn't have: the words stay readable, the control is dimmed (and inert). */
.setting-row.locked > :not(.setting-text) {
    opacity: 0.45;
}

.setting-text {
    display: flex;
    flex-direction: column;
    min-width: 0;
    margin: 0;
}

.setting-text strong {
    font-size: var(--text-90);
    font-weight: 500;
}

/* A mark before the label (SettingRow's LabelIcon), e.g. Apple Music's. */
.setting-text strong .service-mark {
    margin-right: 0.4rem;
    vertical-align: -0.15em;
}

.setting-text strong .premium-badge {
    margin-left: 0.4rem;
    vertical-align: middle;
}

.setting-note {
    color: var(--muted);
    font-size: var(--text-80);
}

/* A button under a setting's words (SettingRow's Note), e.g. a cube max's Use default: its own width, not the column's. */
.setting-text > .btn {
    align-self: flex-start;
    margin-top: 0.4rem;
}

.setting-row > .text-input {
    width: min(240px, 50%);
}

@media (max-width: 640px) {
    .setting-row {
        flex-direction: column;
        align-items: flex-start;
    }

    .setting-row > .text-input {
        width: 100%;
    }

    /* A switch or a color's dot is small: it stays beside its words, on the right and centered on them. */
    .setting-row:has(> .switch, > .color-dot-button) {
        flex-direction: row;
        align-items: center;
    }
}

/* A section on the surface that opens to show more (CollapsibleCard): a hairline over it, then its head, a line with an
   icon or avatar, title and summary, and a chevron that turns; no card round it. Straight under a section's heading the
   heading is the divide. */
.collapsible {
    overflow: hidden;
    border-top: 1px solid var(--line);
}

.section-heading + .collapsible {
    border-top: 0;
}

/* The head's hover tint reaches a little past its words either side, so the tint reads as a row lit up rather than a
   box drawn tight round the text; the head is still as wide as the section. */
.collapsible-head {
    display: flex;
    align-items: center;
    gap: 0.9rem;
    width: calc(100% + 1.2rem);
    margin-inline: -0.6rem;
    padding: 0.7rem 0.6rem;
    border: 0;
    border-radius: var(--radius-labeled-pill);
    background: transparent;
    color: var(--ink);
    text-align: left;
}

.collapsible-head:hover {
    background: var(--tint-faint);
}

/* Focused from the keyboard, the ring goes round the head itself: it follows the head's rounded corners, and the section's
   own overflow is kept from cutting it off by drawing it inside its edge. */
.collapsible-head:focus-visible {
    outline: none;
    box-shadow: var(--focus-ring-inset);
}

.collapsible-mark {
    display: grid;
    flex: none;
    place-items: center;
    width: 40px;
    height: 40px;
    border-radius: 50%;
    background: var(--accent-soft);
    color: var(--accent);
}

.collapsible-mark svg {
    width: 18px;
    height: 18px;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.8;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.collapsible-title {
    display: flex;
    flex: 1;
    flex-direction: column;
    min-width: 0;
}

.collapsible-name {
    font-size: var(--text-100);
    font-weight: 600;
}

.collapsible-summary {
    overflow: hidden;
    color: var(--muted);
    font-size: var(--text-80);
    text-overflow: ellipsis;
    white-space: nowrap;
}

.collapsible-head .chevron {
    flex: none;
    width: 18px;
    height: 18px;
    fill: none;
    stroke: var(--muted);
    stroke-width: 2;
    stroke-linecap: round;
    stroke-linejoin: round;
    transition: transform 150ms;
}

.collapsible.open .collapsible-head .chevron {
    transform: rotate(180deg);
}

.collapsible-body {
    padding: 0.25rem 0 1rem;
}

@media (prefers-reduced-motion: reduce) {
    .collapsible-head .chevron {
        transition: none;
    }
}

/* A row of cards on Home (RecordCards, ReleaseCardRow): 8 across, then 4, so 8 cards never leave one on its own row.
   Each row lays itself out on a phone. Its group (HomeGroup) spaces it from the rows around it, so a row (a .cards-section)
   has no margin of its own. */
.card-grid {
    display: grid;
    grid-template-columns: repeat(8, minmax(0, 1fr));
    gap: 0.75rem;
}

@media (max-width: 1200px) {
    .card-grid {
        grid-template-columns: repeat(4, minmax(0, 1fr));
    }
}

/* One chart on Stats (ChartCard): title, what's counted, the chart, a headed section straight on the surface with a
   hairline over it, so the charts' grid reads as rows split by hairlines. Wide takes the whole row of the charts' grid. */
.chart {
    min-width: 0;
    padding: 1rem 0 0.75rem;
    border-top: 1px solid var(--line);
}

.chart.wide {
    grid-column: 1 / -1;
}

.chart h2 {
    margin: 0;
    font-size: 1.3rem;
    font-weight: 700;
    letter-spacing: -0.01em;
}

.chart-sub {
    margin: 0.2rem 0 1.1rem;
    color: var(--muted);
    font-size: var(--text-80);
}

/* Every chart has a table view under it (Show as table), so no number is only reachable by hover or color, and it's where
   each part's share of the whole is read. The table sits in a soft well of its own: small capital headings, a hairline
   between rows, a row lit under the pointer, and the figures right-aligned on the segment display, as the charts' are. */
.chart details {
    margin-top: 0.75rem;
    border-top: 1px solid var(--line);
    padding-top: 0.6rem;
}

.chart summary {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    color: var(--muted);
    font-size: var(--text-80);
    list-style: none;
    cursor: pointer;
}

.chart summary::-webkit-details-marker {
    display: none;
}

/* A chevron that turns down as the table opens. */
.chart summary::before {
    content: "";
    width: 0.4rem;
    height: 0.4rem;
    border-right: 1.5px solid currentColor;
    border-bottom: 1.5px solid currentColor;
    transform: rotate(-45deg);
    transition: transform 150ms;
}

.chart details[open] > summary::before {
    transform: rotate(45deg) translate(-1px, -1px);
}

.chart summary:hover,
.chart details[open] > summary {
    color: var(--ink);
}

.chart table {
    width: 100%;
    margin-top: 0.6rem;
    border-collapse: separate;
    border-spacing: 0;
    overflow: hidden;
    border-radius: var(--radius-md);
    background: var(--tint-faint);
    box-shadow: inset 0 0 0 1px var(--line);
    font-size: var(--text-85);
}

.chart th {
    padding: 0.55rem 0.75rem 0.45rem;
    border-bottom: 1px solid var(--line);
    color: var(--muted);
    font-size: var(--text-70);
    font-weight: 600;
    letter-spacing: 0.07em;
    text-align: left;
    text-transform: uppercase;
}

.chart td {
    padding: 0.38rem 0.75rem;
    border-bottom: 1px solid var(--line);
    color: var(--ink-soft);
}

.chart tbody tr:last-child td {
    border-bottom: 0;
}

.chart tbody tr:hover td {
    background: var(--tint);
    color: var(--ink);
}

.chart .num {
    text-align: right;
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
}

/* A table's figures in the theme's figure face, their digits one width (.num) so the column lines up. */
.chart td.num {
    color: var(--ink);
    font-family: var(--font-display);
    font-size: var(--text-95);
    font-weight: 500;
}

/* The segment display has no percent sign worth reading, so shares are in the text face, figures lined up. */
.chart td.share {
    color: var(--muted);
    font-family: inherit;
    font-size: var(--text-85);
}

/* Where you are in a ranking's table, as the chart marks you. */
.chart tr.yours td {
    background: var(--accent-soft);
    color: var(--ink);
    font-weight: 600;
}

/* A page that's still loading (LoadingCard): the spinning record centered on the surface where the page will be, one
   height on every page so nothing jumps between them; no card round it. */
.loading-card {
    display: grid;
    place-items: center;
    min-height: 280px;
    padding: 2rem;
}

/* Bare: the same record where results will be, under a page's own controls, with room above and below so it doesn't
   crowd them, but not a page's height. */
.loading-card.bare {
    min-height: 0;
    padding: 3rem 1rem;
}

/* A text field, and a note box or drop-down list styled like one: a pill with a hairline edge and the amber focus ring,
   the same on every page. Card-colored on the page; on a card (a dialog's, or a card-soft) it would vanish, so there
   it takes the surface color, as btn-light does (.on-card marks any other card-colored container). Pages only place and size it. */
.text-input {
    min-width: 0;
    padding: 0.5rem 1rem; /* as tall as a segmented control beside it */
    border: 1px solid var(--line);
    border-radius: var(--radius-pill);
    background-color: var(--card);
    color: var(--ink);
    font: inherit;
    font-size: var(--text-90);
    font-weight: 400;
    outline: none;
}

.dialog-card .text-input,
.card-soft .text-input,
.on-card .text-input {
    background-color: var(--surface);
}

/* A number in a sentence or a toolbar ("Use 16 cubes", "8 × 4 grid"): short, centered, figures lined up. */
.text-input.number {
    flex: none;
    width: 3.4rem;
    padding: 0.35rem 0.4rem;
    font-variant-numeric: tabular-nums;
    text-align: center;
}

/* On a surface that stays dark in both themes (Organize's cube bars): a dark well. */
.text-input.on-dark {
    border-color: var(--on-dark-tint-strong);
    background-color: var(--dark);
    color: var(--on-dark);
    font-weight: 600;
}

/* Outlined in amber when it holds a value the user set rather than the default (.own, a cube's own max), on its bar or
   in its settings. */
.text-input.own {
    border-color: var(--accent);
}

.text-input::placeholder {
    color: var(--muted);
}

.text-input:focus {
    border-color: var(--accent);
    box-shadow: 0 0 0 3px var(--accent-soft);
}

.text-input:disabled {
    opacity: 0.6;
}

/* A date field. iOS draws its own date control at its natural width whatever width it's given, so in a dialog it ran out
   past the sheet's edge (the gift list's Buy by), with the day centered; without its native look it takes the width it's
   given and starts at the left like any other field, and the min-height stops it collapsing when empty. */
.text-input[type="date"] {
    -webkit-appearance: none;
    appearance: none;
    min-height: calc(1.5em + 1rem + 2px);
}

.text-input[type="date"]::-webkit-date-and-time-value {
    margin: 0;
    text-align: start;
}

textarea.text-input {
    border-radius: var(--radius-md);
    resize: vertical;
}

/* A field that narrows the page as you type (SearchField): a pill with a magnifier and, once there's something in it, an
   × to clear it. On the page, a card-colored pill with a hairline, like the segmented controls beside it; on a card,
   surface-colored with the same hairline, as .text-input is. */
.search-field {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    min-width: 0;
    padding: 0.3rem 0.35rem 0.3rem 0.9rem;
    border: 1px solid var(--line);
    border-radius: var(--radius-pill);
    background: var(--card);
    color: var(--ink);
    box-shadow: none;
    transition: box-shadow 150ms ease, border-color 150ms ease;
}

/* On a card, a hairline instead of the shadow, like .text-input. */
.dialog-card .search-field,
.card-soft .search-field,
.on-card .search-field {
    border-color: var(--line);
    background: var(--surface);
    box-shadow: none;
}

.search-field:focus-within {
    border-color: var(--accent);
    box-shadow: 0 0 0 3px var(--accent-soft);
}

.search-field > .icon {
    flex: none;
    width: 16px;
    height: 16px;
    fill: none;
    stroke: var(--muted);
    stroke-width: 1.8;
    stroke-linecap: round;
}

.search-field input {
    flex: 1;
    min-width: 0;
    /* As tall as the clear button, so the field doesn't grow when it appears. */
    min-height: 24px;
    padding: 0.2rem 0;
    border: 0;
    outline: none;
    background: transparent;
    color: var(--ink);
    font: inherit;
    font-size: var(--text-90);
}

.search-field input::placeholder {
    color: var(--muted);
}

/* The browser's own clear button; the field draws its own ×. */
.search-field input::-webkit-search-cancel-button {
    -webkit-appearance: none;
}

/* The faceplate for what's drawn as hi-fi hardware on it (a skin's RangePill, SplitBar's balance, the equalizer charts'
   EqFaders): brushed metal, after a Marantz's, which a skin starts from. Inside it the ink, its lines and tints are the
   plate's own, so whatever sits on it (counts, labels, IconButtons) reads on it whatever's around it. */
.metal-plate {
    --ink: var(--balance-ink);
    --ink-soft: var(--balance-ink);
    --muted: var(--balance-ink-soft);
    /* Its segment displays (.lcd) are its display windows' glass and ink (--balance-vfd-*, as .vfd-window's), for a skin to
       start from: every one lit full, the unlit segments a trace of the ink. WAX³'s own look lights them in the accent. */
    --display-ink: var(--balance-vfd-ink);
    --display-ghost: color-mix(in srgb, var(--balance-vfd-ink) 10%, transparent);
    --display-glow: 0 0 6px var(--balance-vfd-glow);
    /* Its faders' LEDs (EqFader), lit and off: the receiver's LED (--balance-led-*), for a skin to start from. WAX³'s own
       look lights them in the accent, as its displays. */
    --fader-led: var(--balance-led);
    --fader-led-glow: 0 0 4px 1px var(--balance-led-glow);
    --fader-led-off: var(--balance-led-off);
    --line: rgba(0, 0, 0, 0.18);
    --tint-faint: rgba(0, 0, 0, 0.05);
    --tint: rgba(0, 0, 0, 0.08);
    background: var(--balance-plate-face);
    box-shadow: var(--balance-plate-edge), var(--shadow-soft);
    color: var(--ink);
}

/* WAX³'s own look (no skin): the plate matte, light or dark with the theme (the --plate-* tokens), its grit even, its
   knobs and caps satin. What's drawn on it reads the --balance-* tokens, so they're the plate's here: its ink, the lit
   edge under a slot or a lamp's bezel, the knob's metal. The slot, the LEDs, the lamps and the display window are the
   same parts in either theme. */
:root:not([data-skin]) .metal-plate {
    --ink: var(--plate-ink);
    --ink-soft: var(--plate-ink);
    --muted: var(--plate-ink-soft);
    --line: var(--plate-line);
    --tint-faint: var(--plate-tint);
    --tint: var(--plate-tint);
    --balance-ink: var(--plate-ink);
    --balance-ink-soft: var(--plate-ink-soft);
    --balance-plate-hi: var(--plate-hi);
    --balance-plate-lo: var(--plate-lo);
    --balance-knob-hi: var(--plate-knob-hi);
    --balance-knob: var(--plate-knob);
    --balance-knob-lo: var(--plate-knob-lo);
    --balance-groove: var(--plate-groove);
    --balance-screw-slot: var(--plate-screw-slot);
    /* Its segment displays lit in the accent, whatever it's set to, on their dark glass in either theme, every one full:
       a touch of white so a deep accent still reads lit, a bloom of it (a tight glow and a wider halo, as an LED's), and the
       unlit segments a trace of it. */
    --display-ink: color-mix(in srgb, var(--accent) 85%, #fff);
    --display-ghost: color-mix(in srgb, var(--accent) 10%, transparent);
    --display-glow: 0 0 1px color-mix(in srgb, var(--accent) 90%, transparent), 0 0 4px color-mix(in srgb, var(--accent) 65%, transparent),
        0 0 12px color-mix(in srgb, var(--accent) 35%, transparent);
    /* Its faders' LEDs lit in the accent the same way, the displays' ink and bloom, and off a dark, unlit shade of it. */
    --fader-led: var(--display-ink);
    --fader-led-glow: var(--display-glow);
    --fader-led-off: color-mix(in srgb, var(--accent) 22%, black);
    --balance-knob-face: radial-gradient(circle at 50% 32%, var(--plate-knob-hi), var(--plate-knob) 55%, var(--plate-knob-lo));
    /* Written out again rather than inherited: :root worked them out with the brushed plate's light edge in them. */
    --balance-slot-shadow: inset 0 2px 3px rgba(0, 0, 0, 0.6), 0 1px 0 var(--plate-hi);
    --balance-vfd-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.8), 0 1px 0 var(--plate-hi);
    --balance-knob-shadow: 0 2px 4px rgba(0, 0, 0, 0.4), inset 0 -1px 2px rgba(0, 0, 0, 0.18), inset 0 1px 1px var(--plate-knob-hi);
    background: var(--plate-grit), var(--plate);
    box-shadow: inset 0 1px 0 var(--plate-hi), inset 0 0 0 1px var(--plate-line);
}

/* A figure on the segment display (SegmentDisplay): its numbers each in a window (.lcd), and whatever else it's written
   with (a currency, a unit, the dash between two years) printed beside them, as a faceplate's legend is, in the quiet bold
   of a fader's scale marks. */
.segment-display {
    display: inline-flex;
    align-items: center;
    gap: 0.18em;
    white-space: nowrap;
}

.metal-plate .segment-display > .unit {
    color: var(--muted);
    font-size: 0.8em;
    font-weight: 700;
}

/* The segment display (drawn by SegmentDisplay): a chart's counts and readouts, as a stereo's. Its two layers share one
   cell: the unlit segments (every digit an "8") and the lit figure over them. Off a plate it isn't a display, so it's only
   the figure, in the theme's figure face, the unlit segments hidden. */
.lcd {
    display: inline-grid;
    font-family: var(--font-display);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.lcd > span {
    grid-area: 1 / 1;
}

.lcd > .ghost {
    visibility: hidden;
    user-select: none;
}

/* On a plate (a PlateRack) it's a display set into the metal: a small window of dark smoked glass sunk into it, as
   .vfd-window is (the same glass, its shadow at the top and the metal's lit edge under it), with a faint sheen across
   the top of the glass; the unlit segments just showing, the figure lit full with its bloom, every one alike. */
.metal-plate .lcd {
    padding: 0.14em 0.32em 0.1em;
    font-family: var(--font-lcd); /* the segment face, only ever here: its digits all one width, so the unlit 8s line up */
    font-synthesis: none;
    border-radius: 3px;
    background: linear-gradient(180deg, rgba(255, 255, 255, 0.07), transparent 48%), var(--balance-vfd);
    box-shadow: var(--balance-vfd-shadow);
    line-height: 1.15;
}

.metal-plate .lcd > .ghost {
    visibility: visible;
    color: var(--display-ghost);
}

.metal-plate .lcd > .lit {
    color: var(--display-ink);
    text-shadow: var(--display-glow);
}

/* An LED blinking like a pilot lamp (TickBar's BlinkTicks: the busiest decade), slowly: about 1.2s lit, then 1.2s dark,
   fading between. It fades the LED's light, drawn once over the dark LED, so the blink runs on the compositor. */
@keyframes led-blink {
    0%, 42%, 100% {
        opacity: 1;
    }

    50%, 92% {
        opacity: 0;
    }
}

/* A display window set into the plate (the rank charts' figures), after a tuner's VFD: dark glass
   sunk in, with a light edge under it where the metal catches, and figures that glow. One look wherever hi-fi hardware shows
   a number. Never wider than what it's in: words that don't fit are cut off rather than pushing the plate out. */
.vfd-window {
    min-width: 0;
    padding: 4px 8px;
    border: 0;
    border-radius: 4px;
    background: var(--balance-vfd);
    box-shadow: var(--balance-vfd-shadow);
    color: var(--balance-vfd-ink);
    font-family: var(--font-vfd);
    font-variant-numeric: tabular-nums;
    line-height: 1.25;
    text-shadow: 0 0 6px var(--balance-vfd-glow);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* A small round button with just an icon (IconButton): clear a field, remove a thing from a list. The amber ring of
   every dark circle button; one that removes something is the delete button's filled orange (.danger, below). */
.icon-button {
    flex: none;
    display: grid;
    place-items: center;
    width: 30px;
    height: 30px;
    padding: 0;
    border: 1.5px solid var(--accent); /* the amber ring of every dark circle button (.round-button) */
    border-radius: 50%;
    background: var(--button-fill, var(--surface));
    color: var(--ink-soft);
    transition: background-color 120ms ease, border-color 120ms ease, color 120ms ease;
}

.icon-button:hover:not(:disabled) {
    background: color-mix(in srgb, var(--accent) 18%, var(--button-fill, var(--surface)));
    color: var(--ink);
}

.icon-button:focus-visible {
    outline: none;
    box-shadow: var(--focus-ring);
}

.icon-button:disabled {
    border-color: color-mix(in srgb, var(--accent) 45%, var(--button-fill, var(--surface)));
    color: color-mix(in srgb, var(--ink-soft) 45%, var(--button-fill, var(--surface)));
    cursor: default;
}

.icon-button svg {
    width: 13px;
    height: 13px;
    fill: none;
    stroke: currentColor;
    stroke-width: 2;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.icon-button.small {
    width: 24px;
    height: 24px;
}

.icon-button.small svg {
    width: 11px;
    height: 11px;
}

.icon-button.on-dark {
    --button-fill: var(--dark);
    color: var(--on-dark-soft);
}

.icon-button.on-dark:hover:not(:disabled) {
    background: color-mix(in srgb, var(--on-dark) 16%, var(--dark));
    color: var(--on-dark);
}

/* One that removes something (Danger: a cube, a wanted record, a filing) is the delete button's colors, as btn-danger is,
   on a card or a dark cube bar alike: a filled orange circle, darker as you point at it. After .on-dark, which it wins over. */
.icon-button.danger {
    border-color: var(--danger);
    background: var(--danger);
    color: var(--on-danger);
}

.icon-button.danger:hover:not(:disabled) {
    border-color: var(--danger-hover);
    background: var(--danger-hover);
    color: var(--on-danger);
}

/* The same ring in the danger red. */
.icon-button.danger:focus-visible {
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--danger) 20%, transparent), 0 0 0 1px var(--danger);
}

/* Every dialog (ModalDialog): a see-through <dialog> kept inside the screen's safe area (as an installed iPhone app the
   page runs under the status bar and home indicator), over the page dimmed and blurred, popping in. What's in it
   usually sits on a .dialog-card. */
.app-dialog {
    inset: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left);
    width: min(var(--dialog-width, 480px), calc(100vw - 2rem));
    max-height: calc(100dvh - 2rem - env(safe-area-inset-top) - env(safe-area-inset-bottom));
    padding: 0;
    border: 0;
    border-radius: var(--radius-xl);
    background: transparent;
    color: var(--ink);
    overflow: visible;
}

.app-dialog::backdrop {
    background: var(--backdrop);
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
}

.app-dialog[open] {
    animation: dialog-pop 180ms cubic-bezier(0.2, 0.8, 0.2, 1);
}

.app-dialog[open]::backdrop {
    animation: dialog-fade 180ms ease-out;
}

/* The card a dialog's contents sit on. As tall as the dialog may be, so a long one scrolls inside it. */
.dialog-card {
    max-height: inherit;
    padding: 1.5rem 1.5rem 1.25rem;
    border-radius: var(--radius-xl);
    background: var(--card);
    box-shadow: var(--shadow-dialog);
}

/* A dialog that's a form (recommend, lend, add to collection): the record on top, then its fields, then its buttons. */
.dialog-form {
    display: flex;
    flex-direction: column;
    gap: 1.1rem;
}

.dialog-form .text-input {
    width: 100%;
}

/* A labeled field in a dialog's form (recommend, lend): the label over its control, with "(optional)" quieter beside it. */
.dialog-field {
    position: relative;
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
    font-size: var(--text-80);
    font-weight: 600;
}

.dialog-field .optional {
    color: var(--muted);
    font-weight: 400;
}

.dialog-field .hint {
    margin: 0;
    color: var(--muted);
    font-weight: 400;
}

/* A dialog's buttons along its bottom, the one that does it last. */
.dialog-actions {
    display: flex;
    justify-content: flex-end;
    flex-wrap: wrap;
    gap: 0.6rem;
}

/* btn-light is card-colored, so on a card (a dialog's, a card-soft, or anything else card-colored marked .on-card) it
   would vanish into it: there it takes the surface color instead, so Cancel, Decline and the rest still look like buttons.
   In the dark theme the surface is darker than the card, so a 1px hairline keeps it reading as a button rather than a hole
   in the card. Every neutral control on a card has it: segmented pills and the Dropdown's pill too. */
/* What a ringed button is filled with (a round button, an IconButton, btn-secondary's and btn-danger-outline's outline):
   never see-through, so nothing passing behind (Halloween's ghosts, a cover) shows inside the ring. The color it sits on:
   the page's --surface where nothing says otherwise (read at the button, so a skin's box that sets its own surface is
   followed), a card's color on a card, and the dark of a bar that stays dark (.on-dark). Pointing at one tints it with the
   accent, solid too. */
.dialog-card,
.card-soft,
.on-card,
.menu,
.app-dialog.help-popover {
    --button-fill: var(--card);
}

.dialog-card .btn-light,
.card-soft .btn-light,
.on-card .btn-light {
    --bs-btn-bg: var(--surface);
    --bs-btn-border-color: var(--line);
    --bs-btn-hover-bg: color-mix(in srgb, var(--surface), var(--ink) 7%);
    --bs-btn-hover-border-color: var(--line);
    --bs-btn-active-bg: color-mix(in srgb, var(--surface), var(--ink) 12%);
    --bs-btn-active-border-color: var(--line);
    box-shadow: none;
}

@keyframes dialog-pop {
    from {
        opacity: 0;
        transform: translateY(8px) scale(0.97);
    }
}

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

@keyframes dialog-rise {
    from {
        transform: translateY(100%);
    }
}

/* Opened from a button on a wide screen (dialog.js places it): a popover just under it, like a Dropdown's list, over the
   page as it is rather than dimmed, since it's a small choice made beside what it changes. A tap outside still closes it. */
.app-dialog.anchored {
    inset: auto;
    margin: 0;
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-pop);
}

.app-dialog.anchored::backdrop {
    background: transparent;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
}

.app-dialog.anchored[open] {
    animation: drop 140ms cubic-bezier(0.2, 0.8, 0.2, 1);
}

.app-dialog.anchored[open]::backdrop {
    animation: none;
}

/* The help a ? opens (HelpButton): the dialog is its card, not a .dialog-card in it, so it stays the small help bubble,
   as wide as reads comfortably, scrolling on its own when it's long, over the page only lightly dimmed, under its button
   on a wide screen and in the middle on a phone. Its × on the .close-dock stays where it sits at rest, at the top of what
   the bubble scrolls, while the heading scrolls away under it, as every sheet's does. */
.app-dialog.help-popover {
    --close-dock-top: 0px;
    padding: 1.1rem 1.25rem 1.25rem;
    overflow-y: auto;
    border-radius: var(--radius-lg);
    background: var(--card);
    box-shadow: var(--shadow-pop);
}

.app-dialog.help-popover::backdrop {
    background: var(--backdrop-light);
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
}

@media (max-width: 640px) {
    .app-dialog {
        width: min(var(--dialog-width, 480px), calc(100vw - 1.5rem));
    }

    /* A sheet along the bottom edge; its card keeps clear of the home indicator itself. */
    .app-dialog.bottom-sheet {
        inset: 0;
        width: 100vw;
        max-width: 100vw;
        margin: auto 0 0;
        border-radius: var(--radius-xl) var(--radius-xl) 0 0;
    }

    .app-dialog.bottom-sheet[open] {
        animation: dialog-rise 220ms cubic-bezier(0.2, 0.8, 0.2, 1);
    }

    /* Its edge a 1px hairline along the top and sides, so a sheet rising over a page of cards reads as its own layer. */
    .app-dialog.bottom-sheet .dialog-card {
        padding-bottom: calc(1rem + env(safe-area-inset-bottom));
        border: 1px solid var(--line);
        border-bottom: 0;
        border-radius: var(--radius-xl) var(--radius-xl) 0 0;
    }
}

@media (prefers-reduced-motion: reduce) {
    .app-dialog[open],
    .app-dialog[open]::backdrop {
        animation: none;
    }
}

/* A dialog isn't a control: clicking inside one shouldn't ring the whole card. */
dialog:focus,
dialog:focus-visible {
    outline: none;
}

.btn:focus-visible, .form-control:focus, .form-check-input:focus {
    box-shadow: var(--focus-ring);
}

/* A page that fills the screen and never scrolls as a page (Room's Layout and Organize): its root takes the rest of the
   frame's column, however tall the screen (MainLayout makes the page the screen's height and the content a column), and
   what's in it scrolls in its own boxes. */
.fill-screen {
    flex: 1 1 0;
    min-height: 0;
}

/* A page that's one column of cards, top to bottom (Settings, and the pages there to be read through Prose: About and the
   Privacy Policy). It fills the frame on a tablet, where a narrower column left an empty gutter beside the cards, and stops
   only on a desktop-wide window, where a line of reading or a setting's words and its switch would drift too far apart. */
.page-column {
    max-width: 70rem; /* a little more than a landscape iPad's frame leaves (1180 px wide leaves 1092), so any tablet fills */
}

/* Cards: only for what floats over the page (dialogs, sheets, popovers, toasts, the bell's panel, the account menu). A
   signed-in page's own parts sit on the one surface under their headings, split by hairlines, never in a card. */
.card-soft {
    background: var(--card);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-soft);
}

/* Runs to the screen's edges: past the text gutter on a phone, and past the content column on a wide screen (Home's hero,
   Room's window). 50% is the column's half, so 50vw - 50% reaches from the column to the window's edges wherever the
   column sits; the same amount as padding puts what's on it back in the column, and a part adds the gutter it wants
   inside that. A phone held sideways has a safe area down a side or both (a notch, a camera, Android's buttons; body's
   padding, which MainLayout's .page reaches back out over), not always the same on both, so each side reaches past its
   own: --bleed-inset is the column's distance to the safe area, the same both sides, and --bleed-left and --bleed-right
   add that side's inset, so a band reaches the screen's very edges while what's on it keeps clear of the notch. The
   insets are 0 everywhere else, so elsewhere both sides are 50vw - 50%. MainLayout's .page clips what runs past the
   window.
   It's drawn as a layer of its own (will-change): Android's web view laid the band out to the screen's left edge but drew
   it only from the column's, so the gutter beside it showed the page and the words sat flush on the band's edge (an
   iPhone drew it whole). On a layer of its own the band is drawn wherever it's laid out. Opacity, not a transform: that
   would make the band the box anything fixed inside it is placed in. */
.full-bleed {
    will-change: opacity;
    --bleed-inset: calc(50vw - 50% - (env(safe-area-inset-left) + env(safe-area-inset-right)) / 2);
    --bleed-left: calc(var(--bleed-inset) + env(safe-area-inset-left));
    --bleed-right: calc(var(--bleed-inset) + env(safe-area-inset-right));
    margin-left: calc(-1 * var(--bleed-left));
    margin-right: calc(-1 * var(--bleed-right));
    padding-left: var(--bleed-left);
    padding-right: var(--bleed-right);
}

/* A record peeking in from a card's bottom right corner, behind what's on it: a StatTile's figure, a HeadlineCard's
   headline. Drawn like a real one, its middle just inside the corner so the label shows. From the middle out: the spindle
   hole, the label (tinted in the person's accent, so it follows their own color), a smooth run-out with one lead-out groove,
   then hairline grooves split into tracks by smooth gaps, with the light catching them across. The mask cuts the hole and
   every smooth band out, so the card's own background shows through, whatever color it is. A smaller card has a smaller
   record (--groove-scale, about its middle), so its grooves never crowd what's on it. */
.record-corner {
    --groove-scale: 1;
    position: relative;
    overflow: hidden;
}

:is(.card-soft, .dialog-card, .on-card) .record-corner {
    --groove-scale: 0.78;
}

.record-corner::after {
    --groove: color-mix(in srgb, var(--ink) 11%, transparent);
    --sheen: color-mix(in srgb, var(--ink) 9%, transparent);
    --label: color-mix(in srgb, var(--accent) 26%, transparent);
    --bands: radial-gradient(circle, transparent 0 2.6px, #000 3px 21.5px, transparent 21.5px 26px, #000 26px 27.8px,
        transparent 27.8px 30px, #000 30px 38px, transparent 38px 39.6px, #000 39.6px 47px, transparent 47px 48.6px,
        #000 48.6px 57px, transparent 57px 58.6px, #000 58.6px 64px, transparent 64px);
    content: "";
    position: absolute;
    right: -52px;
    bottom: -52px;
    width: 132px;
    height: 132px;
    border-radius: 50%;
    background:
        conic-gradient(from 200deg, transparent 0 8deg, var(--sheen) 22deg, transparent 40deg 188deg, var(--sheen) 202deg,
            transparent 220deg),                                                                                  /* sheen */
        radial-gradient(circle, transparent 0 19.5px, color-mix(in srgb, var(--accent) 45%, transparent) 19.5px 20.2px,
            transparent 20.7px),                                                                                  /* label's edge */
        radial-gradient(circle, transparent 0 13px, color-mix(in srgb, var(--accent) 35%, transparent) 13px 13.6px,
            transparent 14px),                                                                                    /* print ring */
        radial-gradient(circle, var(--label) 0 21px, transparent 21.5px),                                         /* label */
        radial-gradient(circle, transparent 0 26.5px, var(--groove) 26.5px 27.2px, transparent 27.7px),          /* lead-out groove */
        repeating-radial-gradient(circle, var(--groove) 0 0.6px, transparent 0.6px 1.7px);                       /* grooves */
    -webkit-mask: var(--bands);
    mask: var(--bands);
    transform: scale(var(--groove-scale));
    pointer-events: none;
}

.record-corner > * {
    position: relative;
    z-index: 1;
}

/* A row of a page's controls on one card-colored pill: what you're looking at on the left, what to do about it on the
   right (Room's Layout; Organize's switches, Spread and Fill, Copy from In-Order). Mark it .on-card too, so the neutral
   controls in it take the surface color. Its pieces stretch to one height. On one flat surface it's ringed by a hairline
   rather than lifted by a shadow, as every control sitting on the page is, and its corners are its controls' own, so the
   holder never looks rounder than what it holds. */
.toolbar {
    display: flex;
    align-items: stretch;
    gap: 8px;
    min-width: 0;
    padding: 8px;
    background: var(--card);
    border-radius: var(--radius-pill);
    box-shadow: inset 0 0 0 1px var(--line);
}

.card-dark {
    background: var(--dark);
    color: #fff;
    border-radius: var(--radius-lg);
}

/* Small rounded labels, e.g. catalog numbers */
.chip {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    padding: 0.15rem 0.6rem;
    border-radius: var(--radius-pill);
    background: var(--tint);
    color: var(--ink-soft);
    font-size: var(--text-75);
    font-weight: 500;
    white-space: nowrap;
}

.chip-accent {
    background: var(--accent);
    color: var(--on-accent);
}

.chip-dark {
    background: rgba(255, 255, 255, 0.1);
    color: rgba(255, 255, 255, 0.85);
}

/* Every album cover gets the same edge, wherever it is: a firm hairline drawn just inside it, so it follows each cover's
   own rounded corners without changing its size, and keeps a dark cover from melting into a dark card. Not on Cover
   Flow or the collage wall, where an edge would show in the reflections and the seams between covers. */
.cover-art {
    outline: 1px solid var(--tint-strong);
    outline-offset: -1px;
}

/* Segmented pill toggle (e.g. Table / Cover Flow): card-colored on the surface with a hairline ring, as every neutral
   control on one flat surface is, so it reads as a control rather than a shape floating over the page. Its choices are
   one control cut into parts, so only the pill's two ends are round (below); every corner between choices is square. */
.segmented {
    flex: none;
    display: inline-flex;
    padding: 4px;
    gap: 2px;
    background: var(--card);
    border-radius: var(--radius-pill);
    box-shadow: inset 0 0 0 1px var(--line);
}

.segmented a,
.segmented button {
    --choice-radius: var(--radius-pill); /* how round the pill's ends are; each kind of pill below says its own */
    border: 0;
    background: transparent;
    white-space: nowrap;
    padding: 0.35rem 0.95rem;
    border-radius: 0;
    color: var(--muted);
    font-size: var(--text-85);
    font-weight: 500;
    text-decoration: none;
}

.segmented a.active,
.segmented button.active {
    background: var(--emphasis);
    color: var(--on-emphasis);
}

/* The pill's ends: the first choice round on its outer (start) side, the last on its outer (end) side, so a lit choice
   at either end follows the pill's curve and one in the middle is a square block. A grouped pill's groups take no box of
   their own (.group, below), so its ends are the first group's first choice and the last group's last, and the corners
   either side of a .divider stay square. :where keeps these below a skin's own corners for the choices. */
.segmented > :is(a, button):first-child,
.segmented > :where(.group:first-child) > :is(a, button):first-child {
    border-start-start-radius: var(--choice-radius);
    border-end-start-radius: var(--choice-radius);
}

.segmented > :is(a, button):last-child,
.segmented > :where(.group:last-child) > :is(a, button):last-child {
    border-start-end-radius: var(--choice-radius);
    border-end-end-radius: var(--choice-radius);
}

/* On a card it would vanish in its usual card color, so there it takes the surface color, with a hairline for the shadow
   (as btn-light does there). */
.dialog-card .segmented,
.card-soft .segmented,
.on-card .segmented {
    background: var(--surface);
    box-shadow: inset 0 0 0 1px var(--line);
}

/* A cube location's color, as a dot before its name (Room pills, the Catalog's Where chips and filter). */
.location-dot {
    display: inline-block;
    flex: none;
    width: 8px;
    height: 8px;
    margin-right: 0.45rem;
    border-radius: 50%;
    background: var(--location-color, var(--muted));
    vertical-align: 0.05em;
}

/* The same color as a strip along the top of a cube's tile (Room's, Organize's), set by --location-color on the tile.
   The tile clips it to its corners. */
.location-strip {
    overflow: hidden;
}

.location-strip::before {
    content: "";
    position: absolute;
    inset: 0 0 auto;
    height: 4px;
    background: var(--location-color);
    pointer-events: none;
}

/* A grouped segmented control: two related choices in one pill (Records: view | collage order; Room: in order or
   free-for-all | by record or by letter). Each group lights its own choice; a full-height hairline separates them.
   The groups are only there for screen readers, so their buttons sit straight in the pill. */
.segmented .group {
    display: contents;
}

.segmented .divider {
    flex: none;
    align-self: stretch;
    width: 1px;
    margin: -4px 0.3rem; /* through the pill's padding, top to bottom */
    background: var(--tint-strong);
}

/* Many choices (a cube location each, on Room and Organize): each its own pill, wrapping onto another row rather than
   scrolling out of sight, so fifteen locations are all in view, and all somewhere to drop a cube or a record. With no
   pill round them, every choice is round all over. */
.segmented.wrapping {
    flex-wrap: wrap;
    gap: 0.4rem;
    max-width: 100%;
    padding: 0;
    background: none;
    box-shadow: none;
}

.segmented.wrapping a,
.segmented.wrapping button {
    border-radius: var(--choice-radius);
}

.segmented.wrapping a:not(.active),
.segmented.wrapping button:not(.active) {
    background: var(--card);
    box-shadow: var(--shadow-soft);
}

/* On a phone, a pill that spans the width with its choices sharing it, so it lines up with full-width rows around it. */
@media (max-width: 640px) {
    .segmented.phone-wide {
        display: flex;
        width: 100%;
    }

    .segmented.phone-wide a,
    .segmented.phone-wide button {
        flex: 1 1 auto;
        justify-content: center;
        padding-inline: 0.35rem;
        font-size: var(--text-80);
        text-align: center;
    }
}

/* A pill whose words don't fit on a phone's one line (Organize's modes and views, Shows' whose shows): each choice is an
   IconChoice, its word on a wide screen and its icon on a phone, the word still its name for screen readers. .icons draws
   them as icons at every width, for a pill in a row too narrow for its words anywhere (Free-for-all's mode switch beside
   Copy from In-Order, in Organize's cube column). .labeled-icons (the Catalog's views and orders) is for a pill with a line
   to itself where its words don't fit: there its choices share the whole line, each its icon over its word in small type,
   so every one still says what it is; .labeled-icons.always draws them that way at every width, for a pill sharing its
   toolbar's line (Organize's views and modes, beside the location and More). The icon is drawn the same wherever it
   shows, so only whether it shows is said again below. */
.segmented.icons .icon,
.segmented.phone-icons .icon,
.segmented.labeled-icons .icon {
    display: block;
    width: 18px;
    height: 18px;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.8;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.segmented.phone-icons .icon,
.segmented.labeled-icons .icon {
    display: none;
}

.segmented.icons {
    align-items: center;
    gap: 1px;
    padding: 3px;
}

.segmented.icons a,
.segmented.icons button {
    display: grid;
    place-items: center;
    width: 40px;
    height: 34px;
    padding: 0;
}

.segmented.icons .word {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}

@media (max-width: 640px) {
    .segmented.phone-icons {
        align-items: center;
        gap: 1px;
        padding: 3px;
    }

    /* Wide enough to hit with a thumb without landing on the next one. */
    .segmented.phone-icons a,
    .segmented.phone-icons button {
        display: grid;
        place-items: center;
        width: 40px;
        height: 34px;
        padding: 0;
    }

    .segmented.phone-icons .icon {
        display: block;
    }

    .segmented.phone-icons .word {
        position: absolute;
        width: 1px;
        height: 1px;
        overflow: hidden;
        clip-path: inset(50%);
        white-space: nowrap;
    }

    .segmented.phone-icons .divider {
        margin: 4px 3px;
    }
}

/* .labeled-icons on a phone: the pill across its line, each choice sharing it by its word's length (so “Cover Flow” has room
   that “Table” doesn't need), its icon over its word, cut short with "…" rather than wrapping on the narrowest phones. */
@media (max-width: 640px) {
    .segmented.labeled-icons {
        display: flex;
        width: 100%;
        gap: 1px;
        padding: 3px;
        border-radius: var(--radius-labeled-pill);
    }

    .segmented.labeled-icons a,
    .segmented.labeled-icons button {
        flex: 1 1 auto;
        min-width: 0;
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        gap: 2px;
        height: 46px;
        padding: 0 2px;
        --choice-radius: var(--radius-labeled-choice);
    }

    .segmented.labeled-icons .icon {
        display: block;
    }

    .segmented.labeled-icons .word {
        max-width: 100%;
        overflow: hidden;
        text-overflow: ellipsis;
        font-size: var(--text-65);
        font-weight: 600;
        letter-spacing: -0.01em;
        line-height: 1.1;
    }

    .segmented.labeled-icons .divider {
        margin: 4px 3px;
    }
}

/* The same on a toolbar too narrow for the pill's words beside the rest of its line, whatever the screen (Records' view bar,
   the view-bar container in Catalog.razor.css, beside the location and the cover size on an iPad, in Split View or zoomed
   in), where the bar goes to two lines and the pill has the first to itself. 48rem leaves room for the words, the location
   picker and a long location's name. Neither a media query nor a container query can be shared with a class, so the
   phone's rules are said again here, the same. */
@container view-bar (max-width: 48rem) {
    .segmented.labeled-icons {
        display: flex;
        width: 100%;
        gap: 1px;
        padding: 3px;
        border-radius: var(--radius-labeled-pill);
    }

    .segmented.labeled-icons a,
    .segmented.labeled-icons button {
        flex: 1 1 auto;
        min-width: 0;
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        gap: 2px;
        height: 46px;
        padding: 0 2px;
        --choice-radius: var(--radius-labeled-choice);
    }

    .segmented.labeled-icons .icon {
        display: block;
    }

    .segmented.labeled-icons .word {
        max-width: 100%;
        overflow: hidden;
        text-overflow: ellipsis;
        font-size: var(--text-65);
        font-weight: 600;
        letter-spacing: -0.01em;
        line-height: 1.1;
    }

    .segmented.labeled-icons .divider {
        margin: 4px 3px;
    }
}

/* .labeled-icons.always: icon over word at every width, for a pill that shares its toolbar's line with the location and an
   action in the same shape (Organize's By record | By letter and In-Order | FFA, then More, a .labeled-icon), so the line
   reads as one row of buttons and the room under it never moves between widths. The pill keeps its own width rather than
   taking the line, each choice as wide as its word; the rules are the phone's and the narrow bar's above, said again for
   every width (neither query can be shared with a class), with the room a wider screen has round each word. */
.segmented.labeled-icons.always {
    display: flex;
    width: auto;
    gap: 1px;
    padding: 3px;
    border-radius: var(--radius-labeled-pill);
}

.segmented.labeled-icons.always a,
.segmented.labeled-icons.always button {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 2px;
    height: 46px;
    padding: 0 10px;
    --choice-radius: var(--radius-labeled-choice);
}

.segmented.labeled-icons.always .icon {
    display: block;
}

.segmented.labeled-icons.always .word {
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    font-size: var(--text-65);
    font-weight: 600;
    letter-spacing: -0.01em;
    line-height: 1.1;
}

.segmented.labeled-icons.always .divider {
    margin: 4px 3px;
}

/* An action beside a .labeled-icons.always pill, in its shape (Organize's More): its icon over its word, as tall as the pill
   (its 46px choices in 3px of padding) with the same corners, in btn-secondary's amber ring, so it reads as a button of its
   own rather than another choice. */
.btn.labeled-icon {
    flex: none;
    display: inline-flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 2px;
    height: var(--labeled-height);
    min-width: var(--labeled-height);
    padding: 0 10px;
    border-radius: var(--radius-labeled-pill);
    white-space: nowrap;
}

.btn.labeled-icon svg {
    width: 18px;
    height: 18px;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.8;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.btn.labeled-icon .word {
    font-size: var(--text-65);
    font-weight: 600;
    letter-spacing: -0.01em;
    line-height: 1.1;
}

/* A phone's line has the location, the pill and More to fit side by side: each choice as narrow as its word allows, but
   never under 44px, Apple's smallest tap, so the short words (Record, Letter, FFA) get a little room round them rather than
   looking squeezed beside In-Order, the location's picker giving up the width. More hugs its word. */
@media (max-width: 640px) {
    .segmented.labeled-icons.always a,
    .segmented.labeled-icons.always button {
        min-width: 44px;
        padding: 0 2px;
    }

    .btn.labeled-icon {
        min-width: 40px;
        padding: 0 4px;
    }
}

/* Choices too many for a phone's line (Admin's five views, the kinds of Admin's Messages): there, one ‹ name › Dropdown of the
   same choices (a .segmented-menu, from TitleTabs' PhoneMenu or the page) in place of the .has-menu pill, which comes back
   on a wider screen. After the phone-wide and phone-icons rules, which it has to win over. */
.segmented-menu.picker {
    display: none;
}

@media (max-width: 640px) {
    .segmented.has-menu {
        display: none;
    }

    .segmented-menu.picker {
        display: block;
    }
}

/* What's waiting in a view (TitleTabs, and its phone menu): the bell's amber count, sitting inline after the label. */
.segmented a .count,
.tab-menu .count {
    display: inline-grid;
    place-items: center;
    min-width: 18px;
    height: 18px;
    margin-left: 0.4rem;
    padding: 0 5px;
    border-radius: 9px;
    background: var(--accent);
    color: var(--on-accent);
    font-size: var(--text-70);
    font-weight: 700;
    line-height: 1;
    font-variant-numeric: tabular-nums;
    vertical-align: 1px;
}

/* A view that's Premium for this user (TitleTabs): the nav's amber dot, small. */
.segmented a.locked {
    position: relative;
}

.segmented a.locked::after {
    content: "";
    position: absolute;
    top: 3px;
    right: 5px;
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: var(--accent);
}

/* The same dot after a locked view's name in the phone menu (TitleTabs' PhoneMenu), the title saying why. */
.tab-menu .locked-dot {
    display: inline-block;
    width: 7px;
    height: 7px;
    margin-left: 0.4rem;
    border-radius: 50%;
    background: var(--accent);
    vertical-align: 0.15em;
}

/* Room the phone tab bar (MainLayout's .docked) takes at the bottom; things pinned there sit above it. */
:root {
    --dock-height: 56px; /* the bar's slots, flush along the bottom; the safe area is added under them */
    --dock-tab: 44px; /* each tab's disc: a finger's width, centered in its 56px slot */
    --dock-space: 0px;
    --dock-z: 30;
    /* A tray that rises along the bottom while something is dragged (Shelf's and Room's drop targets) runs down to the
       screen's edge behind the tab bar, so no gap shows round the floating bar, and keeps its targets clear above it. */
    --dock-tray-z: calc(var(--dock-z) - 1); /* over the page, under the tab bar and the drag label */
    --dock-tray-bottom: max(calc(var(--dock-space) + 0.7rem), env(safe-area-inset-bottom));
}

/* Room the sticky top bar (MainLayout's .topbar) takes on wider screens; things that stick to the top sit below
   it. 3.5rem is its 40px nav pills plus 8px padding above and below. On a phone it scrolls away. */
:root {
    --topbar-space: calc(3.5rem + env(safe-area-inset-top));
}

@media (max-width: 640px) {
    :root {
        --topbar-space: env(safe-area-inset-top);
    }

    /* The bar sits flush on the screen's bottom edge, its slots over the safe area. */
    :root:has(.docked) {
        --dock-space: calc(var(--dock-height) + env(safe-area-inset-bottom));
    }
}

/* Home's rows the bell and a phone's notifications open at (FriendPicks.RowAnchor, OnThisDay's two): their heading lands a
   fifth of the screen below the top bar, so the row sits toward the middle rather than tucked under the top. It's the
   heading's own margin because two scrolls bring it there: RevealAtLink's, and the router's own on a link to the page
   already open (the bell's row while on Home), which goes straight to the element and tells .NET nothing. */
#friend-picks-title,
#on-this-day-title,
#on-this-day-artists-title {
    scroll-margin-top: calc(var(--topbar-space) + 20vh);
}

.segmented button:disabled {
    opacity: 0.45;
    cursor: not-allowed;
}

/* A page title with a switch between sibling views beside it (Room · Organize). */
.title-row {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.4rem 1rem;
}

/* The tabs sit at the right of the title's line, the same on every page and every view, so they stay put as you
   switch between views. On a phone too narrow for both, they drop under the title and stay on the right. */
.title-row h1 {
    margin: 0;
    margin-right: auto;
}

.page-head .title-row {
    justify-content: flex-end;
}

/* On a phone, tabs with long or many labels are drawn smaller so they can still sit beside the title (Circle's); when
   they can't, they wrap under it. Either way they keep to the right, and take the whole width only when they need it. */
@media (max-width: 640px) {
    .title-row > .segmented.phone-wide {
        width: auto;
        max-width: 100%;
    }
}

/* Views too many for a phone's line even when smaller (Admin's five) are one ‹ name › pill there instead (TitleTabs'
   PhoneMenu, shown in place of the tabs as any .segmented-menu is), beside the title, giving up width before it wraps. */
.title-row > .tab-menu.picker {
    flex: 0 1 12rem;
}

/* One round button that belongs with the title (where Stores and Shows look from): it stays on the title's line at the
   right, level with its middle, and a long title wraps beside it rather than pushing it onto a line of its own. */
.title-row.with-button {
    flex-wrap: nowrap;
}

.title-row.with-button > h1 {
    min-width: 0;
}

.title-row.with-button > .icon-button {
    flex: none;
}

/* Centring the boxes puts the tabs a little high: a title's letters sit low in its line, below the room kept for
   accents above them. Nudged down to the middle of the letters, without moving anything else. */
.title-row > .segmented,
.title-row > .tab-menu,
.title-row > .help-button,
.title-row.with-button > .icon-button {
    position: relative;
    top: 0.2rem;
}

/* The top of every page (PageHeader): title or tabs, the line under it, and any actions on the right. On one flat surface
   the head sits close under the top bar and close over the page, the title a size smaller than a heading on a card needed. */
.page-head {
    padding-top: 0.875rem;
    margin-bottom: var(--stack-gap);
}

.page-head h1 {
    font-size: 1.75rem;
}

@media (max-width: 640px) {
    .page-head {
        padding-top: 0.125rem;
        margin-bottom: var(--stack-gap); /* the phone's own rhythm (16px), not a tighter gap of its own */
    }

    .page-head h1 {
        font-size: 1.5rem;
    }
}

/* A page off the tab bar (PageHeader's Closable): its close button at the right end of the title's line, after whatever
   the title has there itself, and level with the title's letters, as the tabs beside a title are (see .title-row). */
.page-head-title {
    display: flex;
    align-items: center;
    gap: 0.75rem;
}

.page-head-title > :first-child {
    flex: 1 1 auto;
    min-width: 0;
}

/* A closable page's .close-dock, just above its header: stuck under the top bar (or the top of a phone's screen, where the
   bar scrolls away), its button hanging to the middle of a title's line (an h1's) and nudged down to its letters, where
   the title's line keeps its .close-space. A ringed IconButton with no fill of its own, so over the page it takes the
   frame's color. */
.page-close-dock {
    --title-line: calc(1.75rem * 1.2); /* .page-head h1's size times the heading line height */
    --close-dock-top: calc(var(--topbar-space) + 0.75rem);
    --close-dock-drop: calc((var(--title-line) - 38px) / 2 + 0.2rem);
}

.page-close-dock > .close-page {
    background: var(--surface);
    box-shadow: var(--shadow-soft);
}

/* As big as a dialog's ×, and its × as big (.close-button), so every close is the same button. */
.icon-button.close-page {
    width: 38px;
    height: 38px;
}

.icon-button.close-page svg {
    width: 16px;
    height: 16px;
}

/* Only above and below: a margin on the sides would undo the one that keeps TitleTabs on the right. */
.page-head h1 {
    margin-block: 0;
}

/* The line under the title, far enough below it to read as its own line rather than part of the heading: the page's real
   subtitle, one sentence, never an invented count line. */
.page-head .subtitle {
    margin: 0.5rem 0 0;
    color: var(--muted);
    font-size: var(--text-80);
}

/* Under a title line with more on it (tabs, a close button) a little more, so a long line that runs beneath them doesn't
   crowd them. */
.page-head :is(.title-row, .page-head-title) + .subtitle {
    margin-top: var(--stack-gap);
}

/* With actions: the title and its line take the full width, and the actions sit under them on the right, along the
   bottom, so they line up with the tabs' row wherever the title wraps. */
.page-head.with-actions {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: var(--stack-gap) 1rem;
}

.page-head.with-actions > .page-head-main {
    flex: 1 1 100%;
}

.head-actions {
    display: flex;
    align-items: center;
    gap: var(--stack-gap) 0.6rem;
    margin-left: auto;
}

/* A page's search among its actions (the Catalog, the Wantlist): room to type, without crowding the buttons. Its width
   rather than a flex-basis: Safari sizes the toolbar around a flex-basis as if the search were only as wide as its field's
   own width, so the last button (Reload, Search Discogs) hung off the end of the toolbar and was cut off at the frame's
   edge, at every width. */
.head-actions .search-slot {
    display: flex;
    flex: 0 1 auto;
    width: 300px;
    min-width: 160px;
}

.head-actions .search-slot > .search-field {
    flex: 1;
}

/* A page's search and its buttons on one line of the surface (Records, the Wantlist): at the right of the title, no card
   of their own, and on a phone the width of the page (below). The search keeps its full width on a wide screen, so its
   placeholder ("Search your collection") is never cut short: the line may shrink (min-width: 0), and without this the
   field is what would give. */
.search-bar {
    display: flex;
    align-items: center;
    gap: var(--stack-gap) 8px; /* rows apart by the page head's rhythm where it wraps (a phone's buttons under the search) */
    min-width: 0;
}

@media (min-width: 641px) {
    .search-bar .search-slot {
        flex-shrink: 0;
    }
}

/* A page's buttons beside its search (Search Discogs, Reload, Add a record), kept together: each an icon and its name.
   The colors come from btn-primary / btn-secondary; this sizes them to the search beside them. */
.head-buttons {
    display: flex;
    gap: 0.6rem;
}

.head-button {
    flex: none;
    white-space: nowrap;
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.5rem 0.95rem;
    font-size: var(--text-85);
}

.head-button svg {
    width: 16px;
    height: 16px;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.8;
    stroke-linecap: round;
    stroke-linejoin: round;
}

/* Discogs' record (IconName.Discogs) in Discogs' own ink, never the line icons' stroke or a page's color: their guidelines
   allow only their gray (black) or white. On an accent fill it takes the words' ink beside it, --on-accent, which is
   near-black, or white on a deep accent. A skin draws everything in its own inks, light and dark boxes on one page, so
   there it follows the words beside it too. */
.icon-discogs path {
    fill: var(--discogs-ink);
    stroke: none;
}

.btn-primary .icon-discogs path,
:root[data-skin] .icon-discogs path {
    fill: currentColor;
}

/* The round Open on Discogs and its labeled pill (DiscogsLink, a LinkButton, which leaves its picture's size to whoever
   passes it). */
.discogs-link .icon-discogs {
    width: 16px;
    height: 16px;
    flex: none;
}

/* A page's search line (Records, the Wantlist) spans the width under the title, still on the right, and is a container, so
   the toolbar knows how much room it has. Only a search's: a container is as wide as its line rather than its content,
   which would squash a lone button (Stats' heart, a friend's gifts) to nothing. */
.page-head.with-actions > .head-actions:has(> .search-bar) {
    flex: 1 1 100%;
    justify-content: flex-end;
    container: head-actions / inline-size;
}

/* Too narrow for every button's name beside the search (an iPad, Split View, a zoomed-in window), the buttons turn round
   as they do on a phone, rather than running off the page; the search keeps its width. 58rem is the widest line's room:
   the Wantlist's four buttons beside the search while it's refreshing (about 905px), with a little to spare. The phone's
   rules below are the same ones, for every page's buttons: a media query can't be shared with a container query. */
@container head-actions (max-width: 58rem) {
    .search-bar .head-button {
        justify-content: center;
        width: 44px;
        height: 44px;
        padding: 0;
        border-radius: 50%;
    }

    .search-bar .head-button svg {
        width: 18px;
        height: 18px;
    }

    .search-bar .head-button .label {
        position: absolute;
        width: 1px;
        height: 1px;
        overflow: hidden;
        clip-path: inset(50%);
        white-space: nowrap;
    }

    /* Except the one that names where it goes (Search Discogs, a .stretch): a pill with its mark and the short of its
       name, “Discogs”, the words it leaves out (.label-more) still read to screen readers. Room enough here beside the
       search; on a phone it takes the rest of its row (below). */
    .search-bar .head-button.stretch {
        width: auto;
        padding: 0 0.95rem;
        gap: 0.5rem; /* the mark's clear space, about its radius, before the word */
        border-radius: var(--radius-pill);
    }

    .search-bar .head-button.stretch .label {
        position: static;
        width: auto;
        height: auto;
        overflow: visible;
        clip-path: none;
    }

    .search-bar .head-button.stretch .label-more {
        position: absolute;
        width: 1px;
        height: 1px;
        overflow: hidden;
        clip-path: inset(50%);
        white-space: nowrap;
    }
}

/* Too narrow for its word beside the round buttons (a phone 320px wide, with every one of the Catalog's toggles), the
   stretching pill shows its mark alone rather than wrap or cut the word: the pill is a container of its own on a phone
   (below), so this is the room it was actually given. 5.25rem is the mark and “Discogs” with a little to spare. */
@container head-stretch (max-width: 5.25rem) {
    .search-bar .head-button.stretch .label {
        position: absolute;
        width: 1px;
        height: 1px;
        overflow: hidden;
        clip-path: inset(50%);
        white-space: nowrap;
    }
}

/* Something under way (reloading, syncing): its arrows turn. */
.spin {
    animation: spin 0.9s linear infinite;
}

@keyframes spin {
    to {
        transform: rotate(360deg);
    }
}

/* The WAX³ record (Components/VinylRecord) with a sheen that stays still while the record turns underneath it. Here
   rather than scoped to the component: the host pages' startup screen writes the same markup out before Blazor runs. */
.vinyl {
    position: relative;
    width: var(--size, 120px);
    height: var(--size, 120px);
    flex: none;
}

.vinyl-disc,
.vinyl-sheen {
    position: absolute;
    inset: 0;
    border-radius: 50%;
}

.vinyl-disc {
    display: block;
    width: 100%;
    height: 100%;
    box-shadow: 0 10px 30px rgba(0, 0, 0, 0.25);
    user-select: none;
}

.vinyl-disc.spinning {
    animation: spin var(--spin, 1.8s) linear infinite;
}

/* The lacquer's gloss, as the app icon has it (.design/wax3-app-icon.svg): a light from above caught on either side of
   the spindle, a narrow sharp highlight at 30° and 210° over a broad soft one around 32° and 212°, the icon's angles.
   It's a reflection of a light that doesn't move, so it lies over the turning record rather than on it: the records'
   own art has none (.design/record.py draws their vinyl). Kept to the grooves, off the run-out and label, which end at
   37.4% of the record's radius in every record's art. */
.vinyl-sheen {
    background:
        conic-gradient(transparent 0deg 24deg, rgba(255, 255, 255, 0.18) 28deg 32deg, transparent 36deg 204deg,
            rgba(255, 255, 255, 0.18) 208deg 212deg, transparent 216deg),
        /* Eased in and out, as the icon's blur softens its edges, so it doesn't read as a flat wedge. */
        conic-gradient(transparent 0deg 6deg, rgba(255, 255, 255, 0.06) 13deg, rgba(255, 255, 255, 0.17) 19deg,
            rgba(255, 255, 255, 0.2) 24deg 40deg, rgba(255, 255, 255, 0.17) 45deg, rgba(255, 255, 255, 0.06) 51deg,
            transparent 58deg 186deg, rgba(255, 255, 255, 0.06) 193deg, rgba(255, 255, 255, 0.17) 199deg,
            rgba(255, 255, 255, 0.2) 204deg 220deg, rgba(255, 255, 255, 0.17) 225deg, rgba(255, 255, 255, 0.06) 231deg,
            transparent 238deg);
    -webkit-mask: radial-gradient(circle closest-side, transparent 0 37.5%, #000 38%);
    mask: radial-gradient(circle closest-side, transparent 0 37.5%, #000 38%);
    pointer-events: none;
}

@media (prefers-reduced-motion: reduce) {
    .vinyl-disc.spinning {
        animation-duration: calc(var(--spin, 1.8s) * 4);
    }
}

/* On a phone they take their own full-width line and wrap within it. */
@media (max-width: 640px) {
    .page-head.with-actions > .head-actions {
        flex-wrap: wrap;
        width: 100%;
    }

    /* The search has a line to itself, so its placeholder ("Search your collection") reads whole, and the round buttons
       (a finger's width each, their names kept for screen readers) sit on the line under it, at the right, as the
       Catalog's are: three or four of them beside the search would leave it room for two words. */
    .head-actions > .search-bar {
        flex: 1 1 100%;
        flex-wrap: wrap;
        justify-content: flex-end;
    }

    .head-actions .search-slot {
        flex: 1 1 100%;
        min-width: 0;
    }

    .head-buttons {
        display: contents;
    }

    .head-button {
        justify-content: center;
        width: 44px;
        height: 44px;
        padding: 0;
        border-radius: 50%;
    }

    .head-button svg {
        width: 18px;
        height: 18px;
    }

    .head-button .label {
        position: absolute;
        width: 1px;
        height: 1px;
        overflow: hidden;
        clip-path: inset(50%);
        white-space: nowrap;
    }

    /* Except the one that names where it goes (Search Discogs, a .stretch): a pill with its mark and “Discogs”, as on a
       narrow search line (above). */
    .head-button.stretch {
        width: auto;
        padding: 0 0.95rem;
        gap: 0.5rem;
        border-radius: var(--radius-pill);
    }

    .head-button.stretch .label {
        position: static;
        width: auto;
        height: auto;
        overflow: visible;
        clip-path: none;
    }

    .head-button.stretch .label-more {
        position: absolute;
        width: 1px;
        height: 1px;
        overflow: hidden;
        clip-path: inset(50%);
        white-space: nowrap;
    }

    /* And here it takes whatever the round buttons leave of their line, so the row runs right across, as a container of
       its own, so it knows when its word won't fit (above). With .head-buttons as display: contents, the line is the
       search bar's. */
    .head-buttons > .head-button.stretch {
        flex: 1 1 0;
        min-width: 44px;
        container: head-stretch / inline-size;
    }

    /* A page's only button, with no search beside it (a friend's gifts, the gift list), has the room to keep its name, and
       a lone icon (•••, +) on its own line says too little. */
    .head-actions:not(:has(.search-slot)) .head-buttons > .head-button:only-child {
        width: auto;
        padding: 0.5rem 0.95rem;
        border-radius: var(--radius-pill);
    }

    .head-actions:not(:has(.search-slot)) .head-buttons > .head-button:only-child .label {
        position: static;
        width: auto;
        height: auto;
        overflow: visible;
        clip-path: none;
    }

    /* One round button (Stats' heart) beside the line under the title, rather than on a line of its own. */
    .page-head.compact-actions {
        display: grid;
        grid-template-columns: minmax(0, 1fr) auto;
        align-items: center;
        gap: 0.4rem 0.75rem;
    }

    .page-head.compact-actions > .page-head-main {
        display: contents;
    }

    .page-head.compact-actions .title-row {
        grid-column: 1 / -1;
    }

    /* In the right-hand column whether or not there's a line beside it: with none (Stats' error), it would otherwise take
       the first column and stop a gap short of the edge the tabs end at. */
    .page-head.compact-actions > .head-actions {
        grid-column: 2;
        width: auto;
    }
}

/* A section's icon in its nav pill (SectionIcon): drawn in strokes like the rest of the app's icons. */
.section-icon {
    fill: none;
    stroke: currentColor;
    stroke-width: 1.6;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.alert {
    border-radius: var(--radius-md);
}

/* Something that went wrong, in the app's warm red rather than Bootstrap's, in both themes. */
.alert-danger {
    --bs-alert-color: var(--danger);
    --bs-alert-bg: color-mix(in srgb, var(--danger) 10%, var(--card));
    --bs-alert-border-color: color-mix(in srgb, var(--danger) 25%, transparent);
}

/* A line saying what went wrong, beside or under the thing it's about (a form, a button). */
.inline-error {
    margin: 0;
    color: var(--danger);
    font-size: var(--text-85);
}

.valid.modified:not([type=checkbox]) {
    outline: 1px solid #26b050;
}

.invalid {
    outline: 1px solid red;
}

.validation-message {
    color: red;
}

/* The crash overlay (index.html): the page dims and blurs behind one card, like ConfirmHost's dialogs. Blazor
   shows it by setting display: block, so the centring is done by .crash-center inside it. */
#blazor-error-ui {
    display: none;
    position: fixed;
    inset: 0;
    z-index: 1000;
    overflow-y: auto;
    background: rgba(20, 20, 22, 0.45);
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
    animation: crash-fade 180ms ease-out;
}

html.crashed {
    overflow: hidden;
}

.crash-center {
    display: grid;
    place-items: center;
    min-height: 100%;
    padding: max(1rem, env(safe-area-inset-top)) max(1rem, env(safe-area-inset-right)) max(1rem, env(safe-area-inset-bottom)) max(1rem, env(safe-area-inset-left));
}

.crash-card {
    display: grid;
    justify-items: start;
    gap: 0.9rem;
    width: min(400px, 100%);
    padding: 1.6rem 1.6rem 1.3rem;
    border-radius: var(--radius-xl);
    background: var(--card);
    color: var(--ink);
    box-shadow: 0 24px 60px rgba(0, 0, 0, 0.25);
    animation: crash-pop 220ms cubic-bezier(0.2, 0.8, 0.2, 1);
}

.crash-card h2 {
    margin: 0;
    font-size: 1.55rem;
    font-weight: 700;
    font-style: italic;
    letter-spacing: 0.01em;
    transform: skewX(-6deg);
    /* The heading jolts sideways once, like a needle dragged across the groove. */
    animation: crash-scratch 380ms ease-out 120ms backwards;
}

.crash-card p {
    margin: 0;
    color: var(--muted);
    font-size: var(--text-90);
    line-height: 1.5;
}

.crash-card .crash-status {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    color: var(--ink-soft);
    font-size: var(--text-80);
    font-weight: 600;
}

    .crash-status::before {
        content: "";
        width: 7px;
        height: 7px;
        border-radius: 50%;
        background: var(--muted);
    }

    .crash-status[data-state="sent"]::before {
        background: var(--accent);
    }

    .crash-status[data-state="failed"]::before {
        background: var(--danger);
    }

.crash-card .crash-reference {
    font-size: var(--text-80);
}

    .crash-reference code {
        padding: 0.15rem 0.5rem;
        border-radius: var(--radius-pill);
        background: var(--tint);
        color: var(--ink-soft);
        font-size: var(--text-80);
    }

.crash-actions {
    display: flex;
    justify-content: flex-end;
    justify-self: stretch;
}

    .crash-actions .btn {
        padding-inline: 1.7rem;
    }

/* A shattered record: three pieces snap apart once when the card appears and a chip from the rim flies off, then
   they rest scattered. The record is black in both themes, like a real one, with a hairline so it still has an
   edge on the dark card (its colors are inline in index.html). The margin leaves room for the pieces to spread. */
.crash-record {
    width: 88px;
    height: 72px;
    margin: 0.3rem 0 0.2rem 0.4rem;
    overflow: visible;
}

    /* Each piece turns about its own middle and moves away from the cracks, so they part without overlapping. */
    .crash-record .shard {
        filter: drop-shadow(0 0 0.6px var(--crash-edge));
        transform-box: view-box;
        animation: 480ms cubic-bezier(0.3, 1.6, 0.5, 1) 120ms backwards;
        animation-name: crash-snap;
    }

    .crash-record .shard.left {
        transform-origin: 28px 36px;
        transform: translate(-6px, 2px) rotate(-7deg);
    }

    .crash-record .shard.upper {
        transform-origin: 62px 18px;
        transform: translate(6px, -5px) rotate(8deg);
    }

    .crash-record .shard.lower {
        transform-origin: 58px 54px;
        transform: translate(5px, 5px) rotate(5deg);
    }

    /* Lands furthest away, a beat after the rest. */
    .crash-record .shard.chip {
        transform-origin: 68px 56px;
        transform: translate(9px, 7px) rotate(28deg);
        animation-duration: 620ms;
    }

@keyframes crash-snap {
    from {
        transform: none;
    }
}

@keyframes crash-scratch {
    0% {
        opacity: 0;
        transform: skewX(-6deg) translateX(-14px);
    }

    35% {
        opacity: 1;
        transform: skewX(-14deg) translateX(6px);
    }

    60% {
        transform: skewX(-3deg) translateX(-3px);
    }
}

@keyframes crash-pop {
    from {
        opacity: 0;
        transform: translateY(8px) scale(0.97);
    }
}

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

@media (prefers-reduced-motion: reduce) {
    #blazor-error-ui,
    .crash-card,
    .crash-card h2,
    .crash-record .shard {
        animation: none;
    }
}

@media (max-width: 640px) {
    .crash-actions .btn {
        width: 100%;
    }
}

.blazor-error-boundary {
    background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
    padding: 1rem 1rem 1rem 3.7rem;
    color: white;
}

    .blazor-error-boundary::after {
        content: "An error has occurred."
    }

/* What signed-in people see while the app starts: the WAX³ record turning, as RecordSpinner shows it in the app (the
   host pages write out VinylRecord's markup). It covers the app, top bar and all, while the app starts underneath and
   until its first page is ready (StartupScreen), then fades straight into that page. Under the crash overlay (1000).
   It carries on from the phone app's launch screen: the record centered at the same size (128 points) on white, or black
   in dark mode, as the phone is set rather than WAX³'s theme, since the launch screen can only follow the phone. */
#startup {
    position: fixed;
    inset: 0;
    z-index: 999;
    display: grid;
    place-items: center;
    background: #fff;
    transition: opacity 0.2s ease-out;
}

@media (prefers-color-scheme: dark) {
    #startup {
        background: #000;
    }
}

#startup.done {
    opacity: 0;
    pointer-events: none;
}

/* WAX³ and its tagline 32px under the record (half its 8rem, plus the gap), and the credit 24px above the phone's home
   bar, where the launch screen has them (LaunchScreen.storyboard), at the pictures' own size. */
#startup > picture {
    position: absolute;
    left: 50%;
    transform: translateX(-50%);
}

#startup > picture > img {
    display: block;
}

#startup > .startup-title {
    top: calc(50% + 4rem + 32px);
}

#startup > .startup-credit {
    bottom: calc(24px + env(safe-area-inset-bottom));
}

/* index.html's plain-HTML landing copy, shown to signed-out visitors (and crawlers) until the app starts.
   Signed-in people get the record instead; signed-out people get the copy instead of the record. */
html[data-signed-in] .static-landing,
html:not([data-signed-in]) #startup {
    display: none;
}

/* Held back for a moment: when the app is cached it starts in well under a second, and flashing this copy just
   before the real home page replaces it looks like a glitch. Only a slow first load (or no JavaScript) shows it.
   It's still in the HTML from the start, so crawlers and link previews read it either way. */
.static-landing {
    max-width: 44rem;
    margin: 0 auto;
    padding: calc(2rem + env(safe-area-inset-top)) 1.25rem calc(2rem + env(safe-area-inset-bottom));
    color: var(--ink);
    line-height: 1.5;
    animation: static-landing-in 0.3s ease-out 1.5s both;
}

@keyframes static-landing-in {
    from {
        opacity: 0;
        visibility: hidden;
    }

    to {
        opacity: 1;
        visibility: visible;
    }
}

.static-landing-brand {
    margin: 0;
    font-family: var(--font-display);
    font-size: 2rem;
    font-weight: 700;
    letter-spacing: -0.035em;
}

.static-landing-brand sup {
    color: var(--accent);
}

.static-landing-brand span {
    margin-left: 0.4rem;
    color: var(--muted);
    font-size: var(--text-95);
    font-weight: 400;
    letter-spacing: 0;
}

.static-landing h1 {
    margin: 1rem 0 0.75rem;
    font-size: clamp(1.8rem, 6vw, 2.75rem);
    font-weight: 600;
    line-height: 1.1;
}

.static-landing h2 {
    margin: 1.5rem 0 0.5rem;
    color: var(--ink-soft);
    font-size: var(--text-75);
    font-weight: 700;
    letter-spacing: 0.09em;
    text-transform: uppercase;
}

.static-landing ul {
    margin: 0;
    padding-left: 1.1rem;
    color: var(--ink-soft);
}

.static-landing-actions {
    margin-top: 2rem;
}

code {
    color: #c02d76;
}

.form-floating > .form-control-plaintext::placeholder, .form-floating > .form-control::placeholder {
    color: var(--bs-secondary-color);
    text-align: end;
}

.form-floating > .form-control-plaintext:focus::placeholder, .form-floating > .form-control:focus::placeholder {
    text-align: start;
}
/* Shelf: floating chip that follows the pointer while dragging a new marker, and the hint shown
   while placing one. Appended to <body> by shelf.js, so they're styled here, not in Shelf.razor.css. */
.shelf-ghost {
    position: fixed;
    top: 0;
    left: 0;
    z-index: 1000;
    margin: -46px 0 0 -48px;
    padding: 0.4rem 0.9rem;
    border-radius: var(--radius-pill);
    background: var(--dark);
    color: #fff;
    font-size: var(--text-80);
    font-weight: 600;
    box-shadow: 0 10px 30px rgba(0, 0, 0, 0.25);
    pointer-events: none;
    white-space: nowrap;
    transition: opacity 120ms;
}

/* Over a square grown to a fingertip on Organize (freeshelf.js), which rises where the name is: the lit square says where it goes. */
.shelf-ghost.over-square {
    opacity: 0;
}

.shelf-ghost::before {
    content: "";
    display: inline-block;
    width: 8px;
    height: 8px;
    margin-right: 0.45rem;
    border-radius: 50%;
    background: var(--accent);
}

/* A short note at the bottom center (Toast; and Organize's hint while placing a cube, from shelf.js), above the phone's
   tab bar rather than behind it. */
.status-toast {
    position: fixed;
    left: 50%;
    bottom: calc(max(1.25rem, env(safe-area-inset-bottom)) + var(--dock-space));
    z-index: 1000;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: 0.4rem 1rem;
    max-width: calc(100vw - 2rem);
    padding: 0.7rem 1.1rem;
    border-radius: var(--radius-pill);
    background: var(--dark);
    color: var(--on-dark);
    font-size: var(--text-85);
    box-shadow: var(--shadow-toast);
    transform: translateX(-50%);
    text-align: center;
}

.status-toast a {
    color: var(--accent);
    font-weight: 600;
}

/* Two lines on a phone read better in a rounded box than in a pill. */
@media (max-width: 640px) {
    .status-toast {
        border-radius: var(--radius-lg);
    }
}

.shelf-toast {
    pointer-events: none;
}

/* "CUBE 7": where a record lives on the shelf. Label and number share one size, weight and face,
   centered in the pill; a minimum width keeps one- and two-digit badges the same size. */
/* Where a record lives: a filled tag with squarer corners, not an outlined pill, so it never reads as a button
   beside Recommend or Add (those are outlined pills). The border stays, transparent, so its size doesn't change. */
.cube-badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.35em;
    min-width: 6.7em;
    padding: 0.3rem 0.75rem;
    border: 1.5px solid transparent;
    border-radius: 6px; /* a tag, a step tighter than the 8px controls */
    background: var(--accent-soft);
    color: var(--ink);
    font-size: var(--text-80);
    font-weight: 600;
    line-height: 1;
    text-transform: uppercase;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.cube-badge .label,
.cube-badge strong {
    font: inherit;
    color: inherit;
}

/* An optional cube name after the number: "CUBE 3 · JAZZ". */
.cube-badge .name {
    max-width: 10em;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    letter-spacing: 0.04em;
}

.cube-badge .name::before {
    content: "· ";
}

/* Letter spacing on the label only: on the number it would add space after the last digit. */
.cube-badge .label {
    letter-spacing: 0.06em;
    margin-right: -0.06em;
}

/* A menu a button opens (the account menu, a record's Share): a .menu of .menu-items, with a .menu-backdrop under it that
   catches clicks outside to close it (the bell's panel sits over one too). Its button's wrapper is position: relative;
   the menu drops below it on the right. While a backdrop is up the page doesn't scroll (html:has(), above). */
/* Catches taps outside the menu to close it. A drag on it does nothing, rather than panning the page behind. */
.menu-backdrop {
    position: fixed;
    inset: 0;
    z-index: 40;
    touch-action: none;
}

.menu {
    position: absolute;
    top: calc(100% + 8px);
    right: 0;
    z-index: 41;
    display: flex;
    flex-direction: column;
    min-width: 190px;
    padding: 6px;
    border-radius: var(--radius-md);
    background: var(--card);
    box-shadow: var(--shadow-pop);
    animation: drop 140ms cubic-bezier(0.2, 0.8, 0.2, 1);
}

.menu-item {
    display: flex;
    align-items: center;
    gap: 0.65rem;
    width: 100%;
    padding: 0.5rem 0.7rem;
    border: 0;
    border-radius: calc(var(--radius-md) - 4px);
    background: transparent;
    color: var(--ink);
    font-size: var(--text-90);
    text-align: left;
    text-decoration: none;
}

.menu-item:hover,
.menu-item:focus-visible {
    background: var(--tint);
    outline: none;
}

.menu-item[aria-current="page"] {
    font-weight: 600;
}

.menu-item svg {
    flex: none;
    width: 18px;
    height: 18px;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.6;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.menu hr {
    margin: 4px 6px;
    border: 0;
    border-top: 1px solid var(--tint-strong);
    opacity: 1;
}

/* A two-line item: what it does, with a short note under it ("With your friends", "On their Home for a week"). */
.menu-item .item-text {
    display: flex;
    flex-direction: column;
}

.menu-item .item-text small {
    color: var(--muted);
    font-size: var(--text-75);
}

/* Names a group of items, like an eyebrow. */
.menu-label {
    padding: 0.35rem 0.7rem 0.15rem;
    color: var(--muted);
    font-size: var(--text-70);
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}

/* A line in place of items: still on its way, or why it isn't there. */
.menu-note {
    margin: 0;
    padding: 0.5rem 0.7rem;
    color: var(--muted);
    font-size: var(--text-85);
}

@keyframes rise {
    from {
        opacity: 0;
        transform: translateY(6px);
    }
}

@keyframes drop {
    from {
        opacity: 0;
        transform: translateY(-4px);
    }
}

@media (prefers-reduced-motion: reduce) {
    .menu {
        animation: none;
    }
}

/* What many components draw the same way, written once here, so a fix or a skin's change happens once: a component adds
   the class in its own markup rather than copying the declarations into its stylesheet. Each is a single class, so a
   component's own rule for the element still wins where it says otherwise. */

/* One line, cut short with "…": a name, an address, a meta line, so cards in a row line up. */
.truncate {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* The small capitals over something, in the label face: an index's heading, a headline's site. */
.eyebrow {
    color: var(--muted);
    font-family: var(--font-label);
    font-size: var(--text-70);
    font-weight: 700;
    letter-spacing: 0.12em;
    text-transform: uppercase;
}

/* A card that rises a little when pointed at (a headline, a store, a show). */
.lift {
    transition: transform 0.25s ease;
}

.lift:hover {
    transform: translateY(-2px);
}

/* A row tinted by where it's from (the Wantlist's sources, Home's search's In Your Collection and Discogs): a wash of its
   color with a strip of it down the left edge, so the tint reads as the row's own on the flat surface. The row sets --tone
   and --tone-soft to an app.css color and its -soft (Tones.Style). */
.toned {
    background: var(--tone-soft);
    box-shadow: inset 3px 0 0 var(--tone);
}

/* A small frosted disc over cover art, so what's on it reads on any artwork (the cube a record's in, on its card). */
.on-cover-disc {
    background: color-mix(in srgb, var(--card) 88%, transparent);
    backdrop-filter: blur(6px);
    box-shadow: var(--shadow-soft);
}

/* On a phone, a page's main row of controls (Room's toolbar, the Catalog's views and orders, the Wantlist's sources) runs
   off both edges of the screen as a band: past the page's gutter, a hairline over and under it and none down its ends,
   square, what's in it kept in by the gutter. Written for the controls it's on, after their own phone rules, so it wins
   their corners and padding; a skin's rules still win it. */
@media (max-width: 640px) {
    :is(.segmented, .toolbar).phone-band {
        width: calc(100% + 2 * var(--gutter));
        margin-inline: calc(-1 * var(--gutter));
        padding-inline: var(--gutter);
        border-radius: 0;
        box-shadow: inset 0 1px 0 var(--line), inset 0 -1px 0 var(--line);
    }
}

/* On a phone, a row of cards is one strip to swipe through, edge to edge past the page's text gutter, its first card
   starting at the gutter and the rest running off the screen's edge. The row says it's a flex row there, its gap and its
   cards' widths. Not .strip, which is StatTiles' dark band and ReleaseCards' own, both drawn by the skins. */
@media (max-width: 640px) {
    .swipe-strip {
        margin: 0 calc(-1 * var(--gutter));
        padding: 0.15rem var(--gutter) 0.5rem;
        overflow-x: auto;
        overscroll-behavior-x: contain;
        scroll-snap-type: x mandatory;
        scroll-padding-inline: var(--gutter);
        scrollbar-width: none;
    }

    .swipe-strip::-webkit-scrollbar {
        display: none;
    }
}

@media (prefers-reduced-motion: reduce) {
    .lift,
    .lift:hover {
        transition: none;
        transform: none;
    }
}

/* A room's square (RoomView), registered as a length so roomview.js can read the size it is in px, to hold the squares at it
   while a SplitPane's handle is dragged. */
@property --square {
    syntax: "<length>";
    inherits: true;
    initial-value: 0px;
}

/* Ways to send a link as a row of round buttons labeled underneath (ShareItems in a sheet: a deal's or a story's Share). */
.elsewhere-items {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 0.25rem;
}

.elsewhere-items .menu-item {
    flex-direction: column;
    justify-content: flex-start;
    gap: 0.35rem;
    padding: 0.35rem 0.15rem;
    color: var(--ink-soft);
    font-size: var(--text-75);
    text-align: center;
}

.elsewhere-items .menu-item:hover,
.elsewhere-items .menu-item:focus-visible {
    background: none;
    color: var(--ink);
}

.elsewhere-items .menu-item:focus-visible svg {
    box-shadow: var(--focus-ring);
}

.elsewhere-items .menu-item svg {
    box-sizing: border-box;
    width: 46px;
    height: 46px;
    padding: 13px;
    border-radius: 50%;
    background: var(--tint);
    color: var(--ink);
    stroke-width: 1.8;
}

.elsewhere-items .menu-item:hover svg {
    background: var(--tint-strong);
}
