/* GENERATED by scripts/build-css.mjs from css/_panelware.css. Do not edit.
   Edit the sources under css/ and run `npm run generate`. */

/**
 * The kit.
 *
 *   import "@josueavalosjim/panelware/css";
 *
 * ── The layer order is the API ────────────────────────────────────────────
 * Later layers win regardless of specificity, which is what kills the
 * specificity arms race before it starts: no !important anywhere in this
 * kit, and no selector written longer than it needs to be just to out-rank
 * another one.
 *
 * pw.overrides is last and empty. It is there for the consumer:
 *
 *   @layer pw.overrides { .pw-button { --pw-elev: none; } }
 *
 * wins over anything shipped here with one class and no fight.
 *
 * The layer statement legally precedes the imports. @import must come before
 * other rules, and a bare @layer statement is one of the two things allowed
 * in front of it.
 *
 * ── A note for anyone adding a taste-check scope ──────────────────────────
 * @layer is transparent to taste-check's parser: css.mjs treats grouping
 * at-rules as always applying and only makes conditional ones (@media,
 * @supports, @container, @scope) opt in by name. So a :root inside
 * layer(pw.tokens) resolves exactly as a bare :root would, and the config
 * does not name the layer. Leaving it unnamed is deliberate, so renaming a
 * layer later cannot silently turn a scope into an unmatched one.
 *
 * ── pw.skin, and what it may not do ─────────────────────────────────
 * Every component in this kit was drawn for the chrome skin, and the other
 * two are that same structure recoloured. A skin could not fix that, because
 * pw.components sorts after pw.treatment and a treatment can only fill slots
 * a component left. pw.skin sorts after pw.components, so a skin can restyle
 * what a component painted rather than only what it left blank.
 *
 * What it buys: a skin may change an element's box, its order within a flex
 * or grid parent, its background, its border, and its visibility.
 *
 * What it may not do, and these are not style preferences:
 *
 *   Hide or unhide anything focusable. The obvious first use of this layer
 *   was hiding the window's minimise, maximise, and close cluster under the
 *   cyber skin, which has no title bar. Those are real buttons carrying real
 *   accessible names, conditional on a handler being passed, so hiding them
 *   leaves three named controls in the tab order and invisible on screen.
 *   Replacing them with a corner label needs an element, and a skin does not
 *   get to add one. That case is why this rule exists.
 *
 *   Paint over the surface stack, or write box-shadow. Winning a declaration
 *   the shared slot made is the whole point of this layer, so the ban is
 *   narrower than the one on pw.components and it is about two properties
 *   rather than all of them. background and background-image carry the
 *   ornament, the elevation fill, and the texture, so a skin that writes
 *   either without splicing --pw-surface-layers back in erases its own
 *   elevation. box-shadow carries --pw-elev and --pw-focus-halo together, so
 *   writing it drops the focus ring. Both have slots: assign --pw-elev,
 *   --pw-elev-fill, --pw-ornament, or --pw-texture instead. Borders, radii,
 *   colours, and layout are a skin's to take.
 *
 *   Take a control below --pw-control-h, or reach a member of the focus-ring
 *   list with clip-path. Both are measured, not promised.
 *
 *   Name a class no component renders.
 *
 * A test holds every file under css/skins/ to those. The layer is a comment
 * until something enforces it.
 */
@layer pw.reset, pw.tokens, pw.treatment, pw.components, pw.skin, pw.overrides;

@layer pw.tokens {
/**
 * Every token, and nothing that paints.
 *
 * Exported as "@josueavalosjim/panelware/css/tokens" so a consumer can adopt
 * the contract without the skin: a design system that wants panelware's
 * palette and scale under its own components imports this file alone.
 *
 * theme-auto.css is deliberately not here. See its own header.
 */
/**
 * PRIMITIVE: the chrome skin's raw ramp.
 *
 * Nothing outside css/tokens/ reads these. They exist so the semantic layer
 * has somewhere to point and so a value moves in one place. A component that
 * names a rung directly has skipped the part of the system that makes a
 * second skin possible.
 *
 * The ramp is not per theme. Light and dark are two different readings of the
 * same rungs, which is why several rungs below carry two roles: silver-700 is
 * the boundary line in light and the disabled label in light, silver-600 is
 * both of those in dark. Every one of those double duties was measured, not
 * assumed, and the number is on the rung.
 *
 * Every value here is a literal. taste-check's parser lists color-mix() and
 * color() in KNOWN_UNSUPPORTED (taste-check/src/color.mjs:28) and rejects
 * relative colour syntax, so a derived colour is not a colour the gate can
 * check. It would be skipped, and a skipped pair reports as a pass. DaisyUI
 * derives its whole button treatment this way; panelware cannot.
 */

:root {
  /* ── Silver ────────────────────────────────────────────────────────────
     The chrome itself. Neutral with a trace of warmth, so it reads as
     painted metal rather than as a grey UI. Ratios are against the ground
     each rung sits on in the semantic layer. */
  --pw-silver-000: #ffffff;   /* bevel highlight. 1.47:1 on the face, and
                                 deliberately so: see --pw-bevel-light. */
  --pw-silver-100: #eceae4;   /* light page. Dark label: 13.10:1 on 900. */
  --pw-silver-200: #e5e3dc;   /* the light face with the gloss flattened in.
                                 Not painted directly; it is what contrast
                                 measures a glossed button against. */
  --pw-silver-300: #d8d5cd;   /* light control face. Label 11.88:1. */
  --pw-silver-400: #c2bfb6;   /* light well: slider track, sunken cell. */
  --pw-silver-500: #9a968b;   /* light bevel shade. Decoration, no floor. */
  --pw-silver-600: #8a857a;   /* dark boundary 4.29:1, dark disabled 3.49:1 */
  --pw-silver-700: #6b675d;   /* light boundary 4.69:1, light disabled 3.85:1 */
  --pw-silver-750: #56544d;   /* dark bevel highlight. Decoration. */
  --pw-silver-800: #403f3a;   /* the dark face with the gloss flattened in. */
  --pw-silver-850: #33322e;   /* dark control face. Label 10.67:1. */
  --pw-silver-900: #232320;   /* dark page. */
  --pw-silver-950: #1a1a18;   /* light label 14.49:1, and the dark well. */

  /* ── Blue ──────────────────────────────────────────────────────────────
     The one accent, and it is a citation. These are the real anchors from
     the archived Windows XP Visual Guidelines (Microsoft, August 2001).
     Colour is the last thing to add and the first to cut, so there is one
     hue here and it carries meaning: the primary action, and the focus ring.

     The XP control-blue ramp is not usable for either. Its lightest usable
     anchor, #2178E0, measures 2.97:1 on the light control face and misses
     the 3:1 that WCAG 1.4.11 asks of a focus indicator. #003399 is the
     anchor that clears it, at 7.41:1. */
  --pw-blue-300: #9dc0ff;     /* dark primary + dark focus. 6.98:1 on 850. */
  --pw-blue-700: #1b3fa8;     /* primary with the gloss flattened in. */
  --pw-blue-800: #003399;     /* light primary + light focus. 7.41:1 on 300. */

  /* ── Phosphor ──────────────────────────────────────────────────────────
     The readout, and the only place in the kit where a saturated colour
     covers a whole surface. It does not follow the page theme in either
     direction, for the same reason the portfolio's --media-plate does not:
     a lit display is a light source, not a surface we paint, and a readout
     that went pale in light theme would stop being a readout. 11.26:1. */
  --pw-phos-300: #3ff08a;
  --pw-phos-800: #1d3329;
  --pw-phos-950: #12201a;

  /* ── Status ────────────────────────────────────────────────────────────
     Three hues, each a light cell and a dark cell, all measured at their
     own pairing. Colour never carries the state on its own here: every
     badge renders a glyph and a word, so these only reinforce. */
  --pw-green-100: #d8ead6;  --pw-green-800: #0d5c0a;  /* 6.53:1 */
  --pw-green-300: #7fdc86;  --pw-green-900: #16301a;  /* 8.48:1 */
  --pw-amber-100: #f7e9c2;  --pw-amber-800: #6b4e00;  /* 6.41:1 */
  --pw-amber-300: #e6c463;  --pw-amber-900: #332a10;  /* 8.41:1 */
  --pw-red-100:   #f2d6d3;  --pw-red-800:   #8c1a10;  /* 6.75:1 */
  --pw-red-300:   #f0a09a;  --pw-red-900:   #3a1a16;  /* 7.62:1 */
}

/**
 * SEMANTIC: the roles, and the whole reason a second skin is possible.
 *
 * Names follow DaisyUI v5 (base-100/200/300, base-content, primary,
 * primary-content, success/warning/error and their -content) with a --pw-
 * prefix. Borrowing the taxonomy rather than inventing one is deliberate:
 * anyone who has themed DaisyUI already knows what base-200 means, and the
 * mapping in css/tokens/interop is then a rename rather than a translation.
 * The prefix is not optional. DaisyUI v5 and Tailwind v4 both own the bare
 * --color-* namespace, and a kit that expects to be dropped into either
 * cannot squat it too.
 *
 * Everything not a colour role follows the house vocabulary instead
 * (--pw-space-lg, --pw-duration-fast, --pw-radius-control), because that is
 * the system this is being built inside and a second naming scheme in one
 * file would be a decision nobody gets anything for.
 *
 * ── The rule that governs this file ────────────────────────────────────────
 * Each skin x theme block declares the COMPLETE set. Not the differences.
 *
 * The reason is a specificity tie, and it is silent when you get it wrong.
 * :root[data-theme="dark"] and a future :root[data-skin="cyber"] are both
 * (0,2,0). If the dark block were skin-agnostic and a cyber block came after
 * it in the bundle, then on a root carrying both attributes every token cyber
 * declares in its light block would beat dark, but only for the tokens
 * cyber-dark happened not to redeclare. Writing dark as *chrome's* dark, and
 * keeping every block complete, removes the tie instead of depending on
 * bundle order. taste-check enforces the completeness at no cost: a pair
 * naming a token a theme does not declare is a hard failure, not a skip.
 *
 * Attribute values are quoted here and in tastecheck.config.json. The config
 * compares normalised selector strings, so [data-theme=dark] in one and
 * [data-theme="dark"] in the other do not match. The scope then contributes
 * nothing while the theme still resolves from :root, and the dark palette
 * gets measured against light numbers and reports a pass.
 */

/*
 * A note on why these selectors are not anchored to :root.
 *
 * They were, and it meant the axis only worked on <html>. A consumer writing
 * <div data-theme="dark"> around one panel got nothing, because
 * :root[data-theme="dark"] matches one element in the document and it is not
 * that div. Scoped theming is table stakes for a design system, and all three
 * axes here are meant to nest.
 *
 * Unanchored, each block declares its tokens on whatever element carries the
 * attribute, and inheritance does the rest: a descendant's own declared value
 * beats one inherited from further up, regardless of specificity. So a
 * compact page with one comfortable toolbar inside it works, and so does the
 * reverse.
 *
 * Specificity, checked, because the ties matter more than the wins:
 *   :root                                    (0,1,0)
 *   [data-theme="dark"]                      (0,1,0)  ties, later wins
 *   [data-skin="chrome"][data-theme="dark"]  (0,2,0)  beats both
 * Every skin x theme block is a COMPLETE declaration set, which is what makes
 * the (0,1,0) tie safe rather than load-bearing.
 */

/*
 * How a partially-specified subtree resolves, because this is the part that
 * surprises people.
 *
 * Skin and theme are independent attributes. A subtree that names only one of
 * them gets that one, plus the DEFAULT of the other. So <div data-skin="chrome">
 * inside a dark page renders chrome-light, not chrome-dark: it asked for a
 * skin, and chrome's default theme is light. Name both if you want both.
 *
 * The alternative was folding the two into one attribute the way DaisyUI does
 * (data-theme="chrome-dark"), which removes the question entirely but also
 * removes the ability to change skin and keep the theme. Two axes is the more
 * useful shape for a kit whose whole thesis is that skins are swappable, so
 * the resolution rule is documented instead of designed away.
 */
:root,
[data-skin="chrome"],
[data-theme="light"],
[data-skin="chrome"][data-theme="light"] {
  /* Surfaces, lightest to deepest. base-100 is the page the kit sits on,
     base-200 is a control face, base-300 is anything sunk into the page. */
  --pw-color-base-100: var(--pw-silver-100);
  --pw-color-base-200: var(--pw-silver-300);
  --pw-color-base-300: var(--pw-silver-400);
  --pw-color-base-content: var(--pw-silver-950);

  /* Disabled. WCAG 1.4.3 exempts inactive controls from the 4.5:1 floor and
     this does not take the exemption, but it does not hold 4.5 either: a
     disabled label at full contrast does not look disabled, and the state
     would then be carried by nothing. 3.85:1 is the compromise, and it is
     never the only cue: the press travel and the hover opacity both stop.
     XP's own disabled ink, rgb(161,161,146), measures 1.78:1 here. It is in
     HANDOFF.md as a sourced value and it is not usable as one. */
  --pw-color-disabled-content: var(--pw-silver-700);
  /* The quietest ink that still clears 4.5:1 on the page.
     
     Metadata in this genre is drawn at a whisper: the reference set puts
     coordinate readouts and serial strings at around 8px and something like
     2.5:1, and that is the one thing from those images this kit does not
     copy. 1.4.3 applies to small text, small text is exactly what this is,
     and a kit that measures every other pair does not get to make an
     exception for the pair it would most like to. So the quiet comes from
     size and tracking, and the ink stays at the floor rather than under it.
     
     A separate role from disabled-content, which sits lower on purpose
     because disabled text is exempt. Metadata is not disabled. It is just
     not the thing you came to read. */

  --pw-color-primary: var(--pw-blue-800);
  --pw-color-primary-content: var(--pw-silver-000);
  /* The title bar's own role, and it needs one. It borrowed
     --pw-color-primary until a third skin wanted a near-black bar:
     Luna's title bar IS the accent, and a 1997 player's is the darkest
     thing on the chassis. One token cannot be both. */
  --pw-color-titlebar: var(--pw-blue-800);
  --pw-color-titlebar-content: var(--pw-silver-000);

  /* The focus ring. Held to 3:1 against BOTH the control it rings and the
     page behind it, because an offset ring crosses that boundary and is
     only as visible as its worse half. 7.41:1 and 9.03:1. */
  --pw-color-focus: var(--pw-blue-800);
  /* The other half of the ring, and it is not decoration.
     A single-colour focus ring is only ever as good as the surface behind
     it, and this kit puts buttons on a title bar painted in the accent it
     draws the ring with: --pw-color-focus on --pw-color-primary measured
     1.00:1, a ring that did not exist. Reported as keyboard navigation
     failing on the blue bar, which is exactly what it was.
     Two rings in opposite values, drawn adjacent, so whatever they land on
     at least one separates from it and the boundary between them is always
     visible. 10.86:1 against each other here. */
  --pw-color-focus-contrast: var(--pw-silver-000);

  /* The filled part of a slider, against the track it sits in. Its own role
     rather than a second reference to primary, so a skin can separate a
     value display from an action without touching a component file. */
  --pw-color-range: var(--pw-blue-800);

  --pw-color-lcd: var(--pw-phos-950);
  --pw-color-lcd-content: var(--pw-phos-300);

  --pw-color-success: var(--pw-green-100);
  --pw-color-success-content: var(--pw-green-800);
  --pw-color-warning: var(--pw-amber-100);
  --pw-color-warning-content: var(--pw-amber-800);
  --pw-color-error: var(--pw-red-100);
  --pw-color-error-content: var(--pw-red-800);

  /* ── The bevel's four inks ───────────────────────────────────────────────
     Only one of these has a floor, and the split is the whole point.

     --pw-bevel-frame is the bottom-right outer line. In THIS theme it is
     also the edge that reads against the page, so it happens to measure
     4.69:1 there and 3.85:1 on a panel, and it is the same silver-700 that
     --pw-bevel-boundary points at.

     That coincidence is light-only, and this comment used to state those two
     numbers as though they were the token's, which is how they got read as a
     promise. In dark the light source moves to the top-left corner and frame
     drops to silver-950: measured 1.11:1 on the page and 1.36:1 on a panel.
     Nothing in the gate holds frame to anything, in either theme. The floor
     belongs to --pw-bevel-boundary, below, which is a separate role for
     exactly this reason.

     So frame is not a divider token. Reaching for it to draw a visible line
     gets a line that works in light and disappears in dark, which is what
     the demo stylesheet did until a painted-pixel audit measured it.

     --pw-bevel-light is the classic white top-left highlight, and on a
     silver face it measures 1.47:1. That is not a failure to fix. It is a
     shading cue on an interior edge, it identifies nothing on its own, and
     the control stays fully identifiable with it removed. Raising it to 3:1
     would mean a grey highlight, which is not a highlight.

     This is the same split the portfolio makes between --ink-faint (1.60:1,
     decoration) and --ink-edge (3.33:1, bounds a control). Two tokens doing
     two jobs, and only one of the jobs has a legal floor. */
  --pw-bevel-light: var(--pw-silver-000);   /* top-left outer,  255 */
  --pw-bevel-face:  var(--pw-silver-200);   /* top-left inner,  229 */
  --pw-bevel-shade: var(--pw-silver-500);   /* bottom-right inner, 154 */
  --pw-bevel-frame: var(--pw-silver-700);   /* bottom-right outer, 103 */

  /* Which of the four edges carries WCAG 1.4.11. It differs by theme, and
     that is the point of naming it separately: in light the dark bottom-right
     line is the one that reads against the page, in dark it is the light
     top-left one. Without this role the gate would have to check whichever
     token happened to be dark, which is how the dark bevel ended up lit from
     below. 4.69:1 on the page, 3.85:1 on a panel. */
  --pw-bevel-boundary: var(--pw-silver-700);

  /* ── The divider tier ────────────────────────────────────────────────────
     A line that groups rather than bounds: a table row rule, a list
     separator, a section break. It is NOT --pw-bevel-boundary, which is the
     3:1 control edge and is heavier than any of those want, and it is not a
     bevel ink either.

     This role was added because a painted-pixel audit found the gap rather
     than the other way round. Ranking every token by its worse theme against
     the page, the set jumped from 1.24:1 to 4.29:1 with nothing between, so
     anyone wanting a soft line had a choice of invisible or full boundary
     weight. Two stylesheets in this repo took the third option and reached
     for a bevel ink, which reads in light and vanishes in dark. That is not
     a discipline problem, it is a missing rung.

     Except the rung was not missing from the ramp, only from the roles.
     Light takes silver-600 at 3.05:1 on the page and 2.50:1 on a control
     face; dark takes silver-700 at 2.79:1 and 2.28:1. Both are values the
     ramp already had.

     The two are each other's swap, and that is structure rather than
     coincidence: softer means one rung toward the page, and the themes walk
     the ramp from opposite ends, so light's divider is dark's boundary and
     dark's divider is light's. If a third theme is added, the divider is the
     rung between its page and its boundary.

     The gate holds this to 2:1, which is not a WCAG number and is not
     claimed as one. 1.4.11 does not reach a grouping line, because removing
     it identifies nothing and changes no state. 2:1 is this tier's own
     contract: enough that the line is there in both themes, little enough
     that it stays a grouping cue. */
  --pw-color-divider: var(--pw-silver-600);

  /* What a glossed surface measures.
     The glare is a translucent white layer sitting between the label and the
     face, so a check that composites label-over-face never sees it and reads
     high. These are that stack pre-flattened into an opaque colour, the same
     move the portfolio makes with --ink-solid, so the static gate measures
     what the runtime gate will find painted. */
  --pw-gloss-lit-base-200: var(--pw-silver-200);
  --pw-gloss-lit-primary:  var(--pw-blue-700);

  /* A flat scrim. Not a blur: see css/components/dialog.css. */
  --pw-color-scrim: rgba(20, 20, 19, 0.45);
}

[data-theme="dark"],
[data-skin="chrome"][data-theme="dark"] {
  --pw-color-base-100: var(--pw-silver-900);
  --pw-color-base-200: var(--pw-silver-850);
  --pw-color-base-300: var(--pw-silver-950);
  --pw-color-base-content: var(--pw-silver-100);

  --pw-color-disabled-content: var(--pw-silver-600);

  --pw-color-primary: var(--pw-blue-300);
  --pw-color-primary-content: var(--pw-silver-950);
  --pw-color-titlebar: var(--pw-blue-300);
  --pw-color-titlebar-content: var(--pw-silver-950);

  --pw-color-focus: var(--pw-blue-300);
  /* Dark here, because dark's accent IS the light one: the same collision
     exists in this theme with the values swapped. 9.48:1 against the ring. */
  --pw-color-focus-contrast: var(--pw-silver-950);
  --pw-color-range: var(--pw-blue-300);

  /* Unchanged, and that is the decision. A lit display does not dim because
     the page around it did. */
  --pw-color-lcd: var(--pw-phos-950);
  --pw-color-lcd-content: var(--pw-phos-300);

  --pw-color-success: var(--pw-green-900);
  --pw-color-success-content: var(--pw-green-300);
  --pw-color-warning: var(--pw-amber-900);
  --pw-color-warning-content: var(--pw-amber-300);
  --pw-color-error: var(--pw-red-900);
  --pw-color-error-content: var(--pw-red-300);

  /* ── The light source stays above-left ─────────────────────────────────
     This was wrong, and it was the most serious thing in the kit, because
     the bevel is the product.

     The first version pointed --pw-bevel-frame at silver-600 so the outer
     line would clear 3:1 against a dark page. That worked for contrast and
     broke the physics: the bottom-right "shadow" came out at luma 133 on a
     face of 50, which made it the brightest line on the control. The light
     source flipped from above in light theme to below in dark, so a raised
     control in dark and a sunken one in light shared a signature. The
     toggle's own caption, that neither state is carried by colour, stopped
     being true in dark.

     The fix is not a new palette. It is moving the light edge to the corner
     the light comes from. Every value below is a rung the ramp already had.
     Luma now runs 26, 35, 50, 84, 138 bottom-right to top-left, which is the
     same monotonic order as light's 103, 154, 213, 229, 255.

     The 3:1 boundary moves with it, to --pw-bevel-boundary, which is why
     that role exists. */
  --pw-bevel-light: var(--pw-silver-600);   /* top-left outer,  138 */
  --pw-bevel-face:  var(--pw-silver-750);   /* top-left inner,   84 */
  --pw-bevel-shade: var(--pw-silver-900);   /* bottom-right inner, 35 */
  --pw-bevel-frame: var(--pw-silver-950);   /* bottom-right outer, 26 */

  /* The light edge carries it here. 4.29:1 on the page, 3.49:1 on a panel. */
  --pw-bevel-boundary: var(--pw-silver-600);

  /* One rung toward the page from the boundary, which here means darker.
     2.79:1 on the page, 2.28:1 on a control face. */
  --pw-color-divider: var(--pw-silver-700);

  --pw-gloss-lit-base-200: var(--pw-silver-800);
  --pw-gloss-lit-primary:  var(--pw-blue-300);

  --pw-color-scrim: rgba(0, 0, 0, 0.62);
}

/**
 * STRUCTURAL: everything that is not a colour.
 *
 * DaisyUI v5's real contribution is that it splits these out from the colour
 * roles instead of folding shape into the palette, so a theme can change how
 * square something is without touching what colour it is. That split is what
 * HANDOFF.md asked to extend, and this is it.
 *
 * The names come from the house system rather than from DaisyUI: --space-lg,
 * --radius-control, --text-ui. The colour roles borrow DaisyUI's names for
 * portability; nothing else needs to, and running two naming schemes in one
 * kit would buy nothing.
 *
 * Every value here is skin-overridable. A skin that wants a 0 radius and a
 * notched corner reassigns two tokens and edits no component.
 */

:root {
  /* ── Type ──────────────────────────────────────────────────────────────
     Five sizes, and the ladder stops there. Each step is at least 25% over
     the one below (1.25, 1.27, 1.26, 1.25), because steps closer than that
     read as a mistake rather than as hierarchy.

     --pw-text-ui is the default and the one nearly everything uses. 15px is
     what a real desktop UI runs at: macOS is 13, Windows is 15. A component
     kit that shipped 16px body would be a web page pretending to be an
     application. */
  --pw-text-micro:    0.75rem;      /* 12px. badge, unit label, LCD caption */
  --pw-text-ui:       0.9375rem;    /* 15px. buttons, tabs, labels, body */
  --pw-text-subhead:  1.1875rem;    /* 19px. dialog title */
  --pw-text-heading:  1.5rem;       /* 24px */
  --pw-text-display:  1.875rem;     /* 30px */

  --pw-leading-tight: 1.1;          /* display */
  --pw-leading-ui:    1.2;          /* a single-line control label */
  --pw-leading-body:  1.55;         /* prose inside a panel */

  /* Tracking tightens as size goes up. A 15px control label needs none. */
  --pw-tracking-display: -0.02em;
  /* Metadata is set small and tracked open, which is where its quiet comes
     from. Caps or not, a 12px mono run reads as a block without this. */
  --pw-tracking-meta:     0.04em;
  --pw-tracking-ui:      0;

  /* system-ui first, and this is a considered choice rather than a default.
     The faces this kit is imitating were Tahoma and Segoe UI, both of which
     are in the stack and both of which are already on the machines that have
     them. Shipping a webfont to look like the operating system would be a
     download to render something the reader already owns. */
  --pw-font-ui:   system-ui, -apple-system, 'Segoe UI', Tahoma, sans-serif;
  --pw-font-mono: ui-monospace, 'SF Mono', Menlo, Consolas, monospace;
  --pw-font-lcd:  var(--pw-font-mono);

  /* ── Space ─────────────────────────────────────────────────────────────
     One scale, 4px based, and nothing off it. Space belongs to the
     container, not scattered onto children. */
  --pw-space-3xs: 0.125rem;   /*  2px */
  --pw-space-2xs: 0.25rem;    /*  4px */
  --pw-space-xs:  0.375rem;   /*  6px */
  --pw-space-sm:  0.5rem;     /*  8px */
  --pw-space-md:  0.75rem;    /* 12px */
  --pw-space-lg:  1rem;       /* 16px */
  --pw-space-xl:  1.5rem;     /* 24px */
  --pw-space-2xl: 2rem;       /* 32px */

  /* ── Shape ─────────────────────────────────────────────────────────────
     2px, not 8px and not a pill. A bevel is a set of 1px and 2px edges, and
     a large radius forces those edges to curve through the corner where
     there is not enough room for two of them to stay distinct. The shape
     and the treatment are the same decision here. */
  --pw-radius-control: 2px;
  --pw-radius-box:     3px;
  --pw-border:         1px;

  /* Corner clipping, default none, and it does nothing today.
     It is here because a scalar radius cannot express a notched corner, and
     the deferred second skin is built out of notched corners. Three
     declarations that are inert now, against editing every component later. */
  --pw-clip-control: none;
  --pw-clip-box:     none;

  /* DaisyUI's structural scalar, kept by name. 1 is the shipped depth; 0
     flattens the kit without removing it. See css/treatment/bevel.css for
     what reads this. */
  --pw-depth: 1;

  /* HOW DEEP THE READOUT IS SET, as a token rather than as a number in
     lcd.css, and this is a repair rather than tidying.

     The readout is sunk deeper than anything else in the kit, because it is a
     window into a box rather than a face on one. That is right for chrome and
     it was written as `--pw-bevel-depth: 2` on .pw-lcd itself, inside
     pw.components. A skin declares its knobs in pw.tokens, and a later layer
     beats an earlier one at any specificity, so no skin could reach it: cyber
     and paper both flatten the kit to --pw-bevel-depth: 0 and both still got
     a chrome-bevelled well around their readout. It was the one place in the
     kit where a component's value was genuinely unreachable, which is the
     exact failure --pw-elev and the fill slots exist to prevent.

     The number is the same. The difference is that it is now somewhere a
     skin can answer. */
  --pw-lcd-depth: 2;

  /* Surface texture. Generalised from DaisyUI's --noise, which hard-codes a
     noise SVG. The second skin wants scanlines, not noise, and the honest
     name for the slot is what it is for rather than what chrome puts in it. */
  --pw-texture: none;
  --pw-texture-opacity: 0;

  /* Ornament: a mark a skin draws that no component does. Corner ticks, a
     bracketed frame, a hatched border. It is a background layer rather than an
     element because a treatment cannot add elements: pw.components sorts after
     pw.treatment, so the only things a skin can put on a surface are the ones
     the surface left a slot for. --pw-selected-mark proved the shape works;
     this is the general case.

     The size is separate because a tiling mark needs one and background-size
     repeats its list across layers when it is shorter, which would silently
     resize the elevation fill and the texture too. */
  --pw-ornament: none;
  --pw-ornament-size: auto;

  /* The elevation fill's tile, for the same reason. A dithered elevation is a
     pattern and a pattern has a pitch. */
  --pw-fill-size: auto;

  /* How far the label half of a metadata pair drops behind its value. */
  --pw-meta-label-opacity: 0.72;

  /* SELECTION, as a slot rather than a rule.
     
     A selected list row and a highlighted menu item are painted by the skin,
     not by the component, for the same reason --pw-elev is a slot: the
     component's job is to say which row is selected, and the skin's job is to
     say what selected looks like. The default is the inversion the chrome
     skin has always drawn. The cyber skin fills --pw-selected-mark instead
     and leaves the ground alone.
     
     This has to be a slot rather than a treatment override because
     pw.components sorts after pw.treatment, so a treatment can never win a
     declaration a component made. It can only fill one the component left. */
  --pw-selected-bg:      var(--pw-color-primary);
  --pw-selected-content: var(--pw-color-primary-content);
  --pw-selected-mark:    none;

  /* How thick an invalid field's ring is drawn.

     Not --pw-border, and this is the reason: the cyber skin resolves error,
     success and warning to one cyan pair on purpose, so under that skin colour
     carries nothing and the only thing separating an invalid field from a
     valid one is that its edge is thicker than the resting hairline. A ring at
     the boundary's own weight would be a state with no carrier at all. */
  --pw-invalid-ring: 2px;

  /* Corner brackets: how far the marks sit inside the row, and how long each
     arm runs. Here rather than in the cyber skin so a second skin that wants
     bracketed selection switches it on rather than reinventing the geometry. */
  --pw-bracket-inset: 3px;
  --pw-bracket-arm:   10px;
  --pw-bracket-weight: 2px;

  /* ── Control knobs ─────────────────────────────────────────────────────
     Knobs live with the tokens, not with the rules. Restyling is editing a
     knob; the component file is only opened to change structure.

     Every knob that changes with density lives in density.css instead, so
     each density block can state a complete set. What stays here is what
     density does not move. */

  /* THE SCROLLBAR. Five surfaces in this kit scroll and all three skins used
     to let the host OS draw all five. treatment/scrollbar.css says why these
     are the only three knobs there are, and why it is not the webkit prefix.

     The colours point at semantic tokens rather than carrying literals,
     because a scrollbar is furniture on a surface and a skin that has already
     said what its surfaces are should not have to say it twice. A skin that
     wants a scrollbar unlike its own surfaces overrides them, which cyber
     does. */
  /* Whether an icon subtracts its own blank margins. 0 everywhere, and a
     context that sets a mark inline beside a word turns it on: the badge
     does. css/components/icon.css carries the argument for why that is right
     there and wrong nearly everywhere else, and why this default is here
     rather than on .pw-icon, which is where it was and where it quietly beat
     every context that asked for it. */
  --pw-icon-bearing: 0;

  --pw-scrollbar-width: auto;
  --pw-scrollbar-thumb: var(--pw-color-base-300);
  --pw-scrollbar-track: var(--pw-color-base-200);

  --pw-focus-width:  2px;

  /* The focus ring's inner half, off by default.
     Declared here rather than left to an inline var() fallback so it has one
     home and the gate can see it. A fully transparent shadow rather than
     `none`, because `none` is not a legal entry inside a shadow list and the
     bevel composes this alongside itself. */
  --pw-focus-halo: 0 0 #0000;
  --pw-focus-offset: 2px;

  /* How far a pressed control travels. One pixel, which is what a real
     bevelled button did, and enough that the press is felt on a trackpad
     with no colour change at all. */
  --pw-press-travel: 1px;

  /* The grip is 22x28 and the thing a finger hits is 44x44. They are
     different numbers on purpose, and the split is the house pattern: the
     burger menu on the portfolio draws 22px bars inside a --burger-hit of
     44px for exactly this reason. A 22x44 painted grip on an 8px track reads
     as a domino balanced on a wire; a 44px target you cannot see reads as
     nothing at all, which is the correct outcome. */
  /* Stated, not derived from --pw-control-h. A custom property that
     references another resolves against the element the first is declared
     on, so a derived hit area declared here would never re-derive when a
     subtree changed its density: every compact control inside would keep the
     comfortable target. Same trap that made --pw-bevel-depth inert. */
  --pw-dialog-w:  28rem;

  /* The equaliser's own geometry. The height is stated rather than derived
     from the control scale because a band's travel is the whole point of the
     component: a 44px tall equaliser is a row of switches. */
  /* A fader is not a slider grip turned on its side. The generic grip is 22
     across the travel axis, and on an equaliser that is most of the distance
     a band moves: a 4dB change on a 24dB scale is 21px of a 128px
     well, so the grip covered its own fill entirely and every band read as
     flat. A fader is thin along the travel and long across it, which is what
     every physical one is, and for the same reason. */
  --pw-fader-h: 0.75rem;
  /* The reserved left gutter in a menu, where a check or a radio dot lands.
     Wide enough for a 16px mark plus its breathing room, and reserved on
     every item rather than only the checkable ones, because a menu whose
     labels shift sideways when one item is checkable moves under the
     pointer. */
  --pw-menu-gutter: 2rem;

  /* How tall a list gets before it scrolls. Stated rather than left to the
     content, because a list that grows without limit stops being a list and
     becomes the page. */
  --pw-list-h: 16rem;

  /* A checkbox or radio box. Smaller than a control, because it is a mark
     rather than a surface, and its touch target is a pseudo element sized
     from --pw-control-h instead. */
  --pw-box-size: 1.25rem;

  /* The switch's track. Its height matches --pw-box-size so a switch and a
     checkbox in the same column line up, and the width is that plus one thumb
     of travel, which is what makes the two positions read as two positions
     rather than as a nudge. The thumb takes its own size from the track, so
     there is no third number to keep in step. */
  --pw-switch-w: 2.25rem;
  --pw-switch-h: 1.25rem;

  /* The analyser. Its height is fixed rather than density-scaled: it is a
     display rather than a control, nothing in it is a hit target, and a
     spectrum that changed shape with the density would be reporting the
     layout rather than the audio. */
  --pw-vis-h:          5rem;
  --pw-vis-gap:        var(--pw-space-3xs);
  --pw-vis-grid-pitch: 4px;

  --pw-eq-height: 10rem;
  --pw-eq-gap:    var(--pw-space-2xs);
  /* Where the zero line sits, as a percentage down the well. Declared here
     with the symmetric default and overwritten inline by the component,
     because only the component knows the range: a -12 to +12 scale puts zero
     in the middle, a 0 to 24 scale puts it at the bottom. */
  --pw-eq-zero:   0.5;      /* a fraction of the fader travel, not of the well */

  /* What the well has below the faders: the band's gap and its frequency
     label. The rule is drawn on the well and has to be placed along the
     TRAVEL, so this is what comes off first. Derived from the two tokens the
     band and the label use rather than stated, because both move per
     skin: the label sets line-height: 1 on --pw-text-micro, so its height is
     that token exactly. */
  --pw-eq-label-space: calc(var(--pw-space-2xs) + var(--pw-text-micro));

  /* ── Stacking ──────────────────────────────────────────────────────────
     Two numbers, and they are tokens because a consumer's page has its own
     stacking order and this kit cannot know it.

     A portalled dialog lands at the end of <body> and, with no z-index, sits
     at auto: it beats earlier siblings but loses to any positioned element
     anywhere with a positive z-index. A tab panel one section up the page
     was painting over an open modal for exactly that reason.

     Deliberately low, not 9999. A kit that grabs the top of the stack takes
     it away from the application, and an app has better claim to it. */
  --pw-z-overlay: 100;
  --pw-z-panel:   101;
}

/* Reassign tokens at the breakpoint rather than duplicating component rules,
   so type and spacing shrink together in one place. The kit changes very
   little here on purpose: a control that shrinks on a phone is a control that
   got harder to hit on the device where hitting it is hardest. */
@media (max-width: 30rem) {
  :root {
    --pw-dialog-w: calc(100vw - (var(--pw-space-lg) * 2));
    --pw-text-subhead: 1.0625rem;
  }
}

/**
 * DISPLAY: the palette of a lit screen, which does not follow the page.
 *
 * --pw-color-lcd and its content are the only two semantic colours identical
 * in light and dark, because a lit display is a light source and does not dim
 * because the page did. Paper is the exception: its readout is ink, nothing
 * on it is lit, and it follows the theme. The analyser's palette is declared once here rather
 * than four times in the semantic files.
 *
 * It DOES follow the skin, though, and an earlier version of this file argued
 * otherwise by stretching the theme argument one step too far. Light against
 * dark is the room; chrome against cyber is what the device is. The ramp below
 * is green, amber, red, and the cyber skin collapses success, warning and
 * error to one cyan on purpose, so an analyser painting three hues under it
 * contradicts the skin outright.
 *
 * The ground is its own token rather than --pw-color-lcd. Borrowing the
 * readout's saved a token and coupled two displays a skin has to move apart.
 *
 * Structure from Strider's 1998 skin specifications, which document
 * viscolor.txt as 24 lines: 0 the background, 1 the dot grid, 2 to 17 the
 * analyser gradient top to bottom, 18 to 22 the oscilloscope, 23 the peak
 * marker. The structure is borrowed; the values are this kit's own.
 *
 * The sixteen gradient steps are three anchors interpolated, because a CSS
 * gradient does per-row colouring for free. The five oscilloscope lines are
 * not implemented. There is no oscilloscope.
 */

:root {
  /* The face. Line 0 of the palette this borrows its structure from. */
  --pw-color-vis-bg: var(--pw-phos-950);

  /* The dot grid, line 1. Dim enough to read as texture rather than as
     content, which is the one thing that line is doing. */
  --pw-color-vis-grid: var(--pw-phos-800);

  /* The ramp, bottom to top: quiet, working, hot. Lines 2 to 17, as three
     anchors. Read at the 300 weight because everything here sits on a
     near-black face and the 100s are tints meant for paper-coloured grounds. */
  --pw-color-vis-low:  var(--pw-green-300);
  --pw-color-vis-mid:  var(--pw-amber-300);
  --pw-color-vis-high: var(--pw-red-300);

  /* The peak marker, line 23, and the brightest thing on the surface. */
  --pw-color-vis-peak: var(--pw-phos-300);
}

/**
 * DENSITY: the third axis, and the one the accessibility floor constrains.
 *
 * [data-density] on :root, alongside [data-theme] and [data-skin]. Three
 * orthogonal attributes, and this one is orthogonal in the real sense: it
 * moves no colour, so the contrast gate's numbers hold across all of it.
 * What it does move is every target size, so it carries its own check.
 *
 * ── Why there is no smaller mode than this ────────────────────────────────
 * The references this kit cites are denser than anything here. An XP command
 * button was 75x23. Winamp's transport buttons were 23x18. Both are below
 * WCAG 2.2 AA's 24x24 minimum target (2.5.8), and 18 is below it by a third.
 *
 * So the period silhouette is not available at full fidelity, and this is the
 * one place the kit refuses to offer the choice. `comfortable` is 44, the
 * house floor, which is AAA. `compact` is 32, which is denser than the
 * default by a quarter and still clears AA with eight pixels of headroom, so
 * a later padding change cannot silently drop it under. A mode that cannot be
 * operated is not an option, it is a decoration with a config flag.
 *
 * The 75 survives as --pw-control-min-w. It is the 23 that does not.
 *
 * ── Every value here is stated, never derived ─────────────────────────────
 * Deriving one density token from another would break subtree overrides, for
 * the same reason --pw-bevel-depth was inert when its offsets were computed
 * on :root: a property referencing another resolves against the element the
 * FIRST is declared on. Density is meant to be nestable, so each block
 * restates the complete set.
 */

:root,
[data-density="comfortable"] {
  --pw-control-h:      2.75rem;     /* 44px */
  --pw-control-min-w:  4.6875rem;   /* 75px */
  --pw-control-pad-x:  var(--pw-space-md);
  --pw-control-gap:    var(--pw-space-sm);
  --pw-control-text:   var(--pw-text-ui);
  --pw-thumb-w:        1.375rem;    /* 22px */
  --pw-thumb-h:        1.75rem;     /* 28px */
  --pw-thumb-hit:      2.75rem;     /* 44px */
  --pw-track-h:        0.5rem;
  --pw-badge-h:        1.5rem;
  --pw-panel-pad:      var(--pw-space-xl);
}

[data-density="compact"] {
  --pw-control-h:      2rem;        /* 32px, eight clear of the 24 floor */
  --pw-control-min-w:  3.75rem;     /* 60px */
  --pw-control-pad-x:  var(--pw-space-sm);
  --pw-control-gap:    var(--pw-space-xs);
  /* A rung down the type scale rather than a new size. The scale is capped
     at five and a sixth would mean one of the five is wrong. */
  --pw-control-text:   var(--pw-text-micro);
  --pw-thumb-w:        1.125rem;    /* 18px */
  --pw-thumb-h:        1.375rem;    /* 22px */
  --pw-thumb-hit:      2rem;        /* 32px, still over the floor */
  --pw-track-h:        0.375rem;
  --pw-badge-h:        1.25rem;
  --pw-panel-pad:      var(--pw-space-lg);
}

/* Base scales, before any skin. A skin overriding a duration declares under
   [data-skin="x"], which is (0,1,0) and ties with this file's :root, so
   importing motion after the skins would have let the base win every time and
   a skin's timing would have been silently ignored. */
/**
 * MOTION: a locked scale, and open role assignments.
 *
 * The scale is the house scale and a skin may not add to it. What a skin may
 * do is re-point a role at a different rung: a skin that wants a heavier
 * press sets --pw-duration-press to --pw-duration-fast. That is the whole
 * difference between motion being a system and motion being seven numbers
 * scattered across seven component files.
 *
 * Only transform and opacity are ever transitioned in this kit. box-shadow is
 * a paint property, so animating the bevel would drop every press off the
 * compositor, and a real bevelled button never eased anyway. It flipped.
 */

:root {
  /* The scale. Locked. */
  --pw-duration-snap: 100ms;   /* the floor; under this reads as instant */
  --pw-duration-fast: 150ms;
  --pw-duration-base: 200ms;
  --pw-duration-slow: 300ms;

  /* Never linear. Linear is for progress, not for interface. */
  --pw-ease-out:    cubic-bezier(0.23, 1, 0.32, 1);   /* arrives, settles */
  --pw-ease-in:     cubic-bezier(0.5, 0, 0.75, 0);    /* gets out of the way */
  --pw-ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
  /* Fast out of the gate and a hard stop, for a skin that snaps rather than
     settles. On the base scale rather than invented inside a skin file,
     because an easing is a house value and a skin choosing one from the scale
     is the point of having a scale. */
  --pw-ease-hud:    cubic-bezier(0.2, 0, 0, 1);   /* stays on screen */

  /* Roles. Re-point these, do not invent a sixth duration.

     Exit is 150 against an enter of 200. The rule of thumb is 0.8x, which
     would be 160ms, and 160ms is not on the scale. Staying on the scale
     matters more than hitting the ratio exactly, and 0.75x still leaves
     first. */
  --pw-duration-press: var(--pw-duration-snap);
  --pw-duration-hover: var(--pw-duration-fast);
  --pw-duration-enter: var(--pw-duration-base);
  --pw-duration-exit:  var(--pw-duration-fast);

  --pw-ease-press: var(--pw-ease-out);
  --pw-ease-enter: var(--pw-ease-out);
  --pw-ease-exit:  var(--pw-ease-in);
  --pw-ease-state: var(--pw-ease-in-out);

  /* Continuous motion, which the duration table above does not govern: it
     measures how long a transition takes to finish, and a marquee does not
     finish. It is exempt from the scale and it is NOT exempt from reduced
     motion, where it stops outright rather than speeding up. */
  --pw-duration-marquee: 12s;
  /* Eight frames, so one revolution is 800ms and a frame holds for 100ms,
     which is --pw-duration-snap and the point below which a step reads as
     instant rather than as a step. Faster than this and the arc smears into
     a ring; slower and it reads as a stutter rather than a spin. */
  --pw-duration-spin: 800ms;
  --pw-duration-spin-reduced: 2400ms;
}

/**
 * SKIN: the chrome skin's own knobs.
 *
 * Four numbers decide how much chrome this is. All of them collapse to a
 * flat kit at 0 without any component knowing, which is the test that the
 * treatment layer is separable from the components.
 *
 * A second skin ships its own copy of this file and MUST declare every token
 * in it, even the ones it turns off. Turning gloss off by not shipping the
 * rule would make the presence of gloss a fact about the consumer's markup
 * instead of a fact about the skin, and every [data-gloss] out there would
 * become a dead attribute. Setting --pw-gloss-opacity to 0 silences all of
 * them at once and leaves the markup honest.
 */

:root,
[data-skin="chrome"],
[data-theme="light"],
[data-skin="chrome"][data-theme="light"] {
  /* Multiplies every bevel offset. 1 is the shipped depth. At 0 every offset
     computes to 0px with no blur and no spread, so the whole stack paints
     nothing. That only holds because every offset in bevel.css is a calc();
     one literal 1px in there and depth 0 leaves a ghost edge. */
  --pw-bevel-depth: 1;

  /* The Web 2.0 gloss, off by default and opt-in per element with
     [data-gloss]. Off by default because the bevel alone is the kit's
     resting state and gloss on every surface is the 2007 mistake: the
     highlight stops meaning anything once everything has one.

     Opacities are the ones the period tutorials used, recorded in
     HANDOFF.md: the diagonal glare layer at Overlay 70%, the second
     white-to-transparent highlight at Soft Light 30-70%, and Isabel Nyo's
     ellipse highlight at 7-15%. Translated to a plain alpha composite here,
     because a blend mode against an unknown consumer background is not a
     value anything can measure. */
  --pw-icon-sheet: url("../assets/icons.svg");
  --pw-gloss-opacity: 1;
  --pw-gloss-glare-alpha: 0.35;   /* the 120deg sweep, at its brightest stop */
  --pw-gloss-cap-alpha:   0.28;   /* the inset top-highlight capsule */
  --pw-gloss-cap-height:  46%;

  /* Glow. Only the readout uses it in v1, and it lives here rather than in
     the readout's own rules so that a skin which glows its buttons does not
     need a new token to do it. 0 turns it off everywhere. */
  --pw-glow-intensity: 1;
  --pw-glow-radius: 6px;
  --pw-glow-color: var(--pw-phos-300);

  /* The one place backdrop-filter is allowed, and it is off.
     It is the documented jank primitive (shadcn-ui #327, Chromium's GPU blur
     re-composite), it is worst on exactly the fixed full-viewport element a
     dialog overlay is, and it is worst again on iOS Safari. Nothing in the
     bevel or gloss work needs it: stacked box-shadow never touches the
     live-sampling blur path, which is why it is cheap. */
  --pw-glass-blur: 0px;

  /* The cast shadow's colour. Its geometry lives in css/treatment/bevel.css,
     where it can be derived per element from the inherited depth. Kept quiet:
     this is a control sitting on a surface, not a card floating over a page. */
  --pw-shadow-outer-color: rgba(20, 20, 19, 0.16);

  /* HOW FAR A PRESSED CONTROL TRAVELS, and the readout's depth, and the
     scrollbar. Three axes every skin declares and none of them used to, which
     meant three decisions the chrome skin was making on every skin's behalf.

     Chrome is the one that earns the travel: its controls are objects with a
     lit top edge, and a button that lights up without moving is a picture of a
     button. One pixel, the period value. */
  --pw-press-travel: 1px;
  --pw-range-inset: 0;
  --pw-badge-text-drop: 0;
  --pw-badge-mark-drop: 0;

  /* A window into a box. The deepest thing in the kit and the only element
     that overrides the depth at all. */
  --pw-lcd-depth: 2;

  /* A period scrollbar is a wide one with a raised thumb in it. The thumb
     cannot be bevelled through the standard properties, which
     treatment/scrollbar.css explains, so what is left is the width and the
     two greys, and the greys are the surfaces this skin already declared. */
  --pw-scrollbar-width: auto;
  --pw-scrollbar-thumb: var(--pw-color-base-300);
  --pw-scrollbar-track: var(--pw-color-base-200);

  /* TYPE IS A SKIN AXIS, so the skin says it rather than inheriting the
     default. structural.css still declares both for a consumer who takes the
     tokens without a skin; this is chrome making the same call on purpose.

     The OS's own UI face at 15px. That is the period reading and it is also
     the reason paper looked wrong for three releases: paper never answered
     this, so a printed sheet was set in system-ui and nobody had decided it
     would be. */
  --pw-font-ui: system-ui, -apple-system, 'Segoe UI', Tahoma, sans-serif;
  --pw-text-ui: 0.9375rem;
}

[data-theme="dark"],
[data-skin="chrome"][data-theme="dark"] {
  --pw-bevel-depth: 1;

  --pw-icon-sheet: url("../assets/icons.svg");
  --pw-gloss-opacity: 1;
  /* The glare is dimmer in dark. A 35% white sweep that reads as a sheen on
     silver reads as a grey smear on a dark face, because the same amount of
     white is a much larger proportional lift off a dark ground. */
  --pw-gloss-glare-alpha: 0.16;
  --pw-gloss-cap-alpha:   0.12;
  --pw-gloss-cap-height:  46%;

  --pw-glow-intensity: 1;
  --pw-glow-radius: 6px;
  --pw-glow-color: var(--pw-phos-300);

  --pw-glass-blur: 0px;

  --pw-shadow-outer-color: rgba(0, 0, 0, 0.5);

  /* HOW FAR A PRESSED CONTROL TRAVELS, and the readout's depth, and the
     scrollbar. Three axes every skin declares and none of them used to, which
     meant three decisions the chrome skin was making on every skin's behalf.

     Chrome is the one that earns the travel: its controls are objects with a
     lit top edge, and a button that lights up without moving is a picture of a
     button. One pixel, the period value. */
  --pw-press-travel: 1px;
  --pw-range-inset: 0;
  --pw-badge-text-drop: 0;
  --pw-badge-mark-drop: 0;

  /* A window into a box. The deepest thing in the kit and the only element
     that overrides the depth at all. */
  --pw-lcd-depth: 2;

  /* A period scrollbar is a wide one with a raised thumb in it. The thumb
     cannot be bevelled through the standard properties, which
     treatment/scrollbar.css explains, so what is left is the width and the
     two greys, and the greys are the surfaces this skin already declared. */
  --pw-scrollbar-width: auto;
  --pw-scrollbar-thumb: var(--pw-color-base-300);
  --pw-scrollbar-track: var(--pw-color-base-200);

  /* TYPE IS A SKIN AXIS, so the skin says it rather than inheriting the
     default. structural.css still declares both for a consumer who takes the
     tokens without a skin; this is chrome making the same call on purpose.

     The OS's own UI face at 15px. That is the period reading and it is also
     the reason paper looked wrong for three releases: paper never answered
     this, so a printed sheet was set in system-ui and nobody had decided it
     would be. */
  --pw-font-ui: system-ui, -apple-system, 'Segoe UI', Tahoma, sans-serif;
  --pw-text-ui: 0.9375rem;
}

/* The second skin. Every one of these declares at least the token set its
   chrome counterpart does, and test/contract.test.mjs holds them to it under
   "the skin contract": a skin that declared only its differences would inherit
   chrome's decisions for everything it forgot, silently and
   correctly-looking, and chrome's block includes :root.

   At least, not exactly. This comment said "the identical token set" and
   named a test that did not exist, and by the time anyone counted it was
   chrome 11, cyber 42, paper 35. A skin reaching for knobs chrome has no use
   for is the point of a skin; leaving one of chrome's out is the bug. The
   test now holds the second half of that and not the first. */
/**
 * PRIMITIVE: the cyber skin's raw ramp.
 *
 * Never referenced outside tokens/. Every rung exists because a semantic role
 * in semantic.cyber.css points at it, and the ratios in that file's comments
 * were computed from these values before either file was written.
 *
 * Cool greys rather than neutral ones, because a blue cast is what separates a
 * HUD from a document. Cyan is the only hue doing structural work; the status
 * hues are there because the badge needs four of them and for no other reason.
 */

/* Scoped to the skin, not to :root, and that is load-bearing rather than
   tidy. Both primitive files declared on a bare :root, this one is imported
   second, and :root and [data-skin="cyber"] are both (0,1,0), so wherever the
   two ramps shared a name the later import won EVERYWHERE. All four amber
   shades are shared, and the chrome skin's warning ink has been the cyber
   skin's amber since the second skin landed: --pw-color-warning-content reads
   --pw-amber-800 in light and --pw-amber-300 in dark, and both resolved here.

   Nothing could see it. The contrast gate resolves the cascade the same way
   the browser does, so it measured the colour that was painted and passed;
   what was wrong was that the painted colour was not the declared one. A
   token being overridden is invisible to a check that only asks whether every
   token has a reader.

   The amber ramp used to be here and was read by nothing: four shades that
   existed only to collide with the chrome skin's four, which is how the chrome
   skin spent three releases painting its warning ink in this file's amber.
   Deleted rather than scoped, because a ramp nobody reads is not a ramp.

   The phosphor ramp is deliberately not redeclared here. The readout is a lit
   display and does not follow the skin, so cyber reads --pw-phos-* from the
   base file by inheritance, which is the same reason display.css exists. */
[data-skin="cyber"] {
  --pw-slate-000: #ffffff;
  --pw-slate-050: #eef3f6;   /* light page, and dark's ink */
  --pw-slate-100: #dde7ed;   /* light control face */
  --pw-slate-200: #c9d8e1;   /* light well */
  --pw-slate-300: #b8cad6;
  --pw-slate-400: #8ea3b0;   /* light divider, 2.34:1 on the page */
  --pw-slate-500: #5c7383;   /* boundary in BOTH themes, and disabled ink */
  --pw-slate-700: #4a626f;   /* dark divider, 2.95:1 on the page */
  --pw-slate-850: #151f27;   /* dark control face */
  --pw-slate-900: #0b1116;   /* dark page */
  --pw-slate-950: #05090c;   /* dark well, and light's ink */

  --pw-cyan-100: #d3f2f8;    /* light status chip, the collapsed ground */
  --pw-cyan-300: #3fd8f0;    /* dark primary, 9.85:1 on the dark page */
  --pw-cyan-800: #0a4f63;    /* light primary, 7.35:1 on the light page */
  --pw-cyan-900: #062029;    /* dark status chip, the collapsed ground */

  /* The amber ramp stood here with a comment explaining that it survived the
     collapse to one accent because the readout needed it, "which is amber
     because a segment display is". The readout is --pw-phos-300 and always
     has been, in this palette and in every other, because a lit display does
     not follow the skin. So the reason was false when it was written, nothing
     ever read the ramp, and a plausible sentence kept four dead shades alive
     for three releases and collided them with the chrome skin's on the way. */
}

/**
 * SEMANTIC: the cyber skin's colour roles.
 *
 * The identical twenty-seven roles semantic.chrome.css declares, including the
 * five bevel inks that this skin's depth of 0 means nothing paints through the
 * bevel stack. They are declared anyway, because three component files still
 * read a bevel ink directly and because the contract test holds every skin and
 * theme to the same token set: a block that only declares its differences is a
 * block that silently inherits somebody else's decisions.
 *
 * Every pair the gate checks was computed before this file was written rather
 * than adjusted until it passed. Both themes clear all twenty at their floors.
 *
 * The direction is a HUD rather than a chassis. Cyan carries the primary role
 * in both themes, the ground is near-black with a blue cast in dark and a cool
 * paper in light, and the readout is cyan phosphor on the analyser's ground,
 * lit the same in both themes because a segment display is a light source.
 */

[data-skin="cyber"],
[data-skin="cyber"][data-theme="light"] {
  /* Surfaces, lightest to deepest. Cool rather than neutral: a HUD printed on
     paper is still a HUD. */
  --pw-color-base-100: var(--pw-slate-050);
  --pw-color-base-200: var(--pw-slate-100);
  --pw-color-base-300: var(--pw-slate-200);
  --pw-color-base-content: var(--pw-slate-950);
  --pw-color-disabled-content: var(--pw-slate-500);
  /* The quietest ink that still clears 4.5:1 on the page.
     
     Metadata in this genre is drawn at a whisper: the reference set puts
     coordinate readouts and serial strings at around 8px and something like
     2.5:1, and that is the one thing from those images this kit does not
     copy. 1.4.3 applies to small text, small text is exactly what this is,
     and a kit that measures every other pair does not get to make an
     exception for the pair it would most like to. So the quiet comes from
     size and tracking, and the ink stays at the floor rather than under it.
     
     A separate role from disabled-content, which sits lower on purpose
     because disabled text is exempt. Metadata is not disabled. It is just
     not the thing you came to read. */

  --pw-color-primary: var(--pw-cyan-800);
  --pw-color-primary-content: var(--pw-slate-000);
  /* The title bar's own role, and it needs one. It borrowed
     --pw-color-primary until a third skin wanted a near-black bar:
     Luna's title bar IS the accent, and a 1997 player's is the darkest
     thing on the chassis. One token cannot be both. */
  --pw-color-titlebar: var(--pw-cyan-800);
  --pw-color-titlebar-content: var(--pw-slate-000);
  --pw-color-focus: var(--pw-cyan-800);
  --pw-color-focus-contrast: var(--pw-slate-000);
  --pw-color-range: var(--pw-cyan-800);

  /* The readout does not follow the theme. A segment display is a physical
     object with its own backlight, and a light-themed LCD is a calculator.
     It does follow the skin: cyan phosphor on the analyser's ground, the one
     accent this skin has, rather than chrome's green. */
  --pw-color-lcd: var(--pw-cyan-900);
  --pw-color-lcd-content: var(--pw-cyan-300);

  /* ONE ACCENT, AND SHAPE CARRIES THE REST.
     
     The chrome skin gives success, warning and error a hue each, which is what
     a chassis does: green, amber and red are the moulded-plastic vocabulary.
     A HUD does not have three inks. It has a ground, an ink, and one signal
     colour, and it tells you which signal you are looking at by drawing a
     different shape.
     
     So all three roles resolve to the same cyan pair, and the badge's mark is
     what differentiates them: a check, an exclamation, a cross, a dot. That
     mark was always there and was always the thing carrying the meaning for
     anyone who cannot separate the hues; collapsing the palette just stops
     pretending the colour was doing the work.
     
     The roles stay three tokens rather than becoming one alias, because a
     consumer who wants their own red for error should be able to say so
     without unpicking a shared name. */
  --pw-color-success: var(--pw-cyan-100);
  --pw-color-success-content: var(--pw-cyan-800);
  --pw-color-warning: var(--pw-cyan-100);
  --pw-color-warning-content: var(--pw-cyan-800);
  --pw-color-error: var(--pw-cyan-100);
  --pw-color-error-content: var(--pw-cyan-800);

  /* The five bevel inks, declared and unused by the bevel: depth 0 collapses
     that stack entirely. --pw-bevel-boundary is the exception and is doing
     real work, because css/treatment/glow.css draws this skin's one hairline
     with it and 1.4.11 applies to that hairline exactly as it applied to
     chrome's outer line. 3.83:1 on the page, 3.30:1 on a control. */
  --pw-bevel-light: var(--pw-slate-000);
  --pw-bevel-face: var(--pw-slate-100);
  --pw-bevel-shade: var(--pw-slate-300);
  --pw-bevel-frame: var(--pw-slate-400);
  --pw-bevel-boundary: var(--pw-slate-500);

  --pw-color-divider: var(--pw-slate-400);

  /* Nothing is glossed here, so the pre-flattened companions are the faces
     themselves. They stay declared: a consumer who writes [data-gloss] gets a
     silent no-op rather than an unresolved token. */
  --pw-gloss-lit-base-200: var(--pw-slate-100);
  --pw-gloss-lit-primary: var(--pw-cyan-800);

  --pw-color-scrim: rgba(6, 14, 20, 0.62);
}

[data-skin="cyber"][data-theme="dark"] {
  --pw-color-base-100: var(--pw-slate-900);
  --pw-color-base-200: var(--pw-slate-850);
  --pw-color-base-300: var(--pw-slate-950);
  --pw-color-base-content: var(--pw-slate-050);
  --pw-color-disabled-content: var(--pw-slate-500);

  --pw-color-primary: var(--pw-cyan-300);
  --pw-color-primary-content: var(--pw-slate-950);
  --pw-color-titlebar: var(--pw-cyan-300);
  --pw-color-titlebar-content: var(--pw-slate-950);
  --pw-color-focus: var(--pw-cyan-300);
  --pw-color-focus-contrast: var(--pw-slate-950);
  --pw-color-range: var(--pw-cyan-300);

  --pw-color-lcd: var(--pw-cyan-900);
  --pw-color-lcd-content: var(--pw-cyan-300);

  --pw-color-success: var(--pw-cyan-900);
  --pw-color-success-content: var(--pw-cyan-300);
  --pw-color-warning: var(--pw-cyan-900);
  --pw-color-warning-content: var(--pw-cyan-300);
  --pw-color-error: var(--pw-cyan-900);
  --pw-color-error-content: var(--pw-cyan-300);

  /* Same inversion the chrome skin makes, for the same reason: whichever ink
     reads against the ground carries the boundary, and here that is the pale
     one. 4.85:1 on the page, 3.72:1 on a control. */
  --pw-bevel-light: var(--pw-slate-700);
  --pw-bevel-face: var(--pw-slate-850);
  --pw-bevel-shade: var(--pw-slate-900);
  --pw-bevel-frame: var(--pw-slate-950);
  --pw-bevel-boundary: var(--pw-slate-500);

  --pw-color-divider: var(--pw-slate-700);

  --pw-gloss-lit-base-200: var(--pw-slate-850);
  --pw-gloss-lit-primary: var(--pw-cyan-300);

  --pw-color-scrim: rgba(0, 0, 0, 0.66);
}

/**
 * SKIN: the cyber skin's own knobs.
 *
 * Every knob skin.chrome.css declares, and then some, because a skin that
 * declares fewer makes the missing ones a fact about the consumer's markup
 * rather than a fact about the skin. Turning gloss off by not shipping the
 * rule would leave every [data-gloss] in the wild as a dead attribute; setting
 * the opacity to 0 silences all of them at once and keeps the markup honest.
 *
 * This said "the same ten tokens" for a long time and neither number was ever
 * ten. The floor is chrome's set, the ceiling is whatever this skin needs, and
 * test/contract.test.mjs counts rather than trusting the sentence.
 *
 * Two of these are the whole point of the file. --pw-bevel-depth: 0 collapses
 * the entire chrome treatment to nothing, which is the claim the depth knob
 * was built to make and had never been asked to keep. --pw-gloss-opacity: 0
 * does the same for the Web 2.0 highlight. What replaces them is one hairline
 * per control, in css/treatment/glow.css.
 */

[data-skin="cyber"],
[data-skin="cyber"][data-theme="light"] {
  /* Zero, and every offset in bevel.css is a calc() of it, so the stacked
     insets compute to 0px with no blur and no spread and paint nothing. */
  --pw-bevel-depth: 0;

  /* Off, everywhere, without touching a line of markup. */
  --pw-gloss-opacity: 0;
  --pw-gloss-glare-alpha: 0;
  --pw-gloss-cap-alpha: 0;
  --pw-gloss-cap-height: 0%;

  /* The readout still glows, and it is the only thing that does. A HUD where
     every control glows is a HUD where the glow means nothing, which is the
     same mistake as gloss on every surface. */
  --pw-glow-intensity: 1;
  --pw-glow-radius: 8px;
  --pw-glow-color: var(--pw-cyan-300);

  --pw-glass-blur: 0px;

  /* No cast shadow: nothing here is a physical object sitting on a surface. */
  --pw-shadow-outer-color: rgba(0, 0, 0, 0);

  /* PROPORTION AND TIMING, WHICH A SKIN OWNS AND THIS ONE HAD NEVER TOUCHED.

     Measured before this landed: the cyber skin overrode 29 of 29 semantic
     colours, 10 of 10 treatment knobs, 0 of 11 density tokens, and 0 of 18
     motion tokens. Every control was the same height, the same padding, the
     same type, and the same timing as the chrome skin's. Two skins differing
     in all of their colour and none of their rhythm read as one design twice,
     which is the diagnosis the reference set made and this file half-acted on
     by swapping the face to mono and stopping.

     A terminal is denser and quicker than a chassis. Nothing here invents a
     value: the type drops one rung, the paddings drop one rung of the space
     scale, and the durations alias to the snap end of the scale that already
     exists. Fewer decisions, not smaller numbers.

     --pw-control-h is deliberately absent. It is 44px because WCAG 2.5.8 and
     the house floor say so, check:a11y hit-tests it, and no skin gets to be
     dense at the cost of a hit target. Density here is padding, gaps, and
     type, which cost nothing anybody has to hit. */
  --pw-text-ui:    0.8125rem;
  --pw-text-micro: 0.6875rem;
  --pw-leading-ui: 1.15;

  --pw-control-pad-x: var(--pw-space-sm);
  --pw-control-gap:   var(--pw-space-xs);
  --pw-badge-h:       1.375rem;
  --pw-panel-pad:     var(--pw-space-lg);

  /* Snap rather than settle. The durations stay inside the house 100 to 300ms
     band and simply sit at its floor, and the ease is sharper than chrome's
     soft landing: fast out of the gate, hard stop, which is what a readout
     updating does and what a moulded button does not. Not linear. */
  --pw-duration-hover: var(--pw-duration-snap);
  --pw-duration-enter: var(--pw-duration-snap);
  --pw-duration-exit:  var(--pw-duration-snap);
  --pw-ease-press: var(--pw-ease-hud);
  --pw-ease-enter: var(--pw-ease-hud);
  --pw-ease-state: var(--pw-ease-hud);

  /* THE ANALYSER, IN ONE ACCENT.

     The default ramp is green, amber, red, which is the chrome skin's status
     vocabulary. This skin collapses success, warning and error to a single
     cyan on purpose, so three hues on the analyser would contradict the one
     decision that does most of the work here.

     Magnitude is carried by brightness instead, which is what a phosphor
     scope does anyway: the ramp is one hue from dim to near-white, and the
     face goes with it so the bars are not cyan on a green ground.

     The readout used to keep chrome's green on the argument that a time
     display is a different object from a spectrum. Side by side in the
     showcase, it read as the one part of the skin nobody had reskinned. It
     is cyan on this ground now. The analyser still has its own face token
     rather than borrowing --pw-color-lcd, so the two can move apart. */
  --pw-color-vis-bg:   var(--pw-cyan-900);
  --pw-color-vis-grid: var(--pw-cyan-800);
  --pw-color-vis-low:  var(--pw-cyan-800);
  --pw-color-vis-mid:  var(--pw-cyan-300);
  --pw-color-vis-high: var(--pw-cyan-100);
  --pw-color-vis-peak: var(--pw-cyan-100);

  /* SQUARE, because the lineage has no radius in it.

     The notch was only ever half the shape. --pw-clip-control reaches the
     eleven surfaces that do not draw a focus ring, and clip-path clips an
     outline, so the other twenty kept the chrome skin's 2px radius: every
     button, tab, text field, checkbox, select and switch, and the dialog
     panel. Measured before this landed, and the reference set's own summary
     of what the lineage has in common is "corner brackets and chamfers
     instead of radii, nothing is rounded". Twenty rounded controls is the
     shape the skin was built to get away from, kept by a default nobody had
     overridden.

     So the skin says square and the notch stays what it always was: an extra
     on the surfaces that can take one. A control a keyboard lands on cannot
     be notched, and now it is at least not rounded either.

     The radio is the exception and stays round, because field.css writes 50%
     as a literal rather than reading this token. That is deliberate and it
     survives a skin change on purpose: the roundness is what says choose-one
     before a word has been read, which is semantics rather than decoration
     and does not stop being true under a different frame. */
  --pw-radius-control: 0;
  --pw-radius-box:     0;

  /* Notched corners, which is the reference lineage's frame: a cut corner
     rather than a radius. --pw-clip-control and --pw-clip-box are the hooks
     the README has always offered for exactly this and that nothing had ever
     used, so this is the first time the claim that a notched skin is a token
     change gets tested.

     Two opposite corners rather than four. Four reads as an octagon; two
     reads as a cut, and the diagonal it sets up is what the HUD idiom is
     made of.

     It reaches the eleven surfaces that do not draw a focus ring, which is
     the window chrome, the menu, the tab panel, the readout, the badge and
     the wells. Everything a keyboard lands on keeps square corners, because
     clip-path clips an outline and the focus ring is an outline. bevel.css
     carries the reasoning and a test holds the two lists together. */
  --pw-clip-control:
    polygon(3px 0, 100% 0, 100% calc(100% - 3px), calc(100% - 3px) 100%, 0 100%, 0 3px);
  --pw-clip-box:
    polygon(5px 0, 100% 0, 100% calc(100% - 5px), calc(100% - 5px) 100%, 0 100%, 0 5px);

  /* Scanlines, which is the other half of the lineage: surface as pattern
     rather than as shade. The chrome skin says "this is moulded plastic" with
     a gradient; this says "this is a screen" with a raster.

     One pixel on, two off, at an alpha the token carries. --pw-texture-opacity
     was declared and read by nothing until this, which made it the fourth
     dead knob in the file. Baking the alpha into the gradient here is what
     wires it, and it means a consumer can turn the raster down without
     replacing the pattern. */
  --pw-texture-opacity: 0.055;
  --pw-texture: repeating-linear-gradient(
    to bottom,
    rgba(0, 0, 0, var(--pw-texture-opacity)) 0 1px,
    rgba(0, 0, 0, 0) 1px 3px
  );

  /* SELECTION IS DRAWN, NOT TINTED.
     
     The chrome skin marks a selected row by inverting it: the accent as a
     ground, its content colour on top. On a screen-lineage skin that reads as
     a lit cell, and it is the one idiom every reference in the set avoids.
     They bracket the selected thing and leave the ground alone.
     
     So the wash comes off and four corner marks go on. The row keeps the text
     colour it already had, which also makes the state cheaper to measure: a
     selected row stops being a second foreground on a second ground and goes
     back to being the same text it was.
     
     Eight gradients, two per corner. A border with a mask would be shorter
     and would not survive forced-colors, where masks stay and backgrounds go;
     gradients vanish there, which is correct, because the components restate
     selection in the system's own inks under that media query.
     
     Shape rather than colour, so removing the wash does not leave
     aria-selected carrying the state alone. */

  /* MONO, UPPERCASE, TRACKED.
     
     The single biggest reason this skin still read as the chrome one with a
     different palette. Both used the same system UI face at the same size with
     the same rhythm, so the only difference was hue and a bevel, and a skin
     that differs by hue is a theme.
     
     --pw-font-ui is a token precisely so a skin can answer this. The mono face
     is the one every reference in the set uses for structure: a terminal, a
     HUD and a BBS all set their labels in a fixed pitch, and the even column
     is what makes a row of controls read as an instrument rather than as a
     web page.
     
     Uppercase and the tracking that uppercase needs are in glow.css, on the
     controls only. Body copy stays as it is, which is the other half of the
     reference note: mono uppercase for structure, and only there. */
  --pw-font-ui: var(--pw-font-mono);
  --pw-tracking-ui: 0.045em;

  /* The skin's own sheet, drawn in vector to the rules in
     assets/icon-font.cyber.mjs: 1px strokes, 45 degree diagonals, one axis.
     Two files, one drawing. A 45 degree hairline cannot fill whole pixels,
     so at 1x the vector sheet paints it grey; the .px sheet is the same
     glyphs snapped to pixels, and image-set() gives each screen the one that
     is sharp on it. Same cell and order as every sheet, and the bearings are
     generated into css/skins/cyber/icon-index.css. */
  --pw-icon-sheet: image-set(url("../assets/icons.cyber.px.svg") 1x, url("../assets/icons.cyber.svg") 2x);
  --pw-selected-bg: transparent;
  --pw-selected-content: inherit;
  --pw-selected-mark:
    linear-gradient(var(--pw-color-primary), var(--pw-color-primary))
      left top / var(--pw-bracket-arm) var(--pw-bracket-weight) no-repeat,
    linear-gradient(var(--pw-color-primary), var(--pw-color-primary))
      left top / var(--pw-bracket-weight) var(--pw-bracket-arm) no-repeat,
    linear-gradient(var(--pw-color-primary), var(--pw-color-primary))
      right top / var(--pw-bracket-arm) var(--pw-bracket-weight) no-repeat,
    linear-gradient(var(--pw-color-primary), var(--pw-color-primary))
      right top / var(--pw-bracket-weight) var(--pw-bracket-arm) no-repeat,
    linear-gradient(var(--pw-color-primary), var(--pw-color-primary))
      left bottom / var(--pw-bracket-arm) var(--pw-bracket-weight) no-repeat,
    linear-gradient(var(--pw-color-primary), var(--pw-color-primary))
      left bottom / var(--pw-bracket-weight) var(--pw-bracket-arm) no-repeat,
    linear-gradient(var(--pw-color-primary), var(--pw-color-primary))
      right bottom / var(--pw-bracket-arm) var(--pw-bracket-weight) no-repeat,
    linear-gradient(var(--pw-color-primary), var(--pw-color-primary))
      right bottom / var(--pw-bracket-weight) var(--pw-bracket-arm) no-repeat;

  /* NOTHING HERE IS AN OBJECT, so nothing here depresses. The chrome skin
     moves a pressed control one pixel down because it is a thing you push.
     A HUD's control is a mark on glass: it brightens, it brackets, and it
     stays exactly where it was. This skin was translating a pixel anyway,
     because the travel had never been a skin's to set. */
  --pw-press-travel: 0;
  --pw-range-inset: 1px;
  --pw-badge-text-drop: 1px;
  --pw-badge-mark-drop: -1px;

  /* And the readout is not a well. --pw-bevel-depth: 0 above flattens the
     whole treatment, and the readout kept a chrome-bevelled surround through
     three releases regardless, because lcd.css wrote the depth as a literal
     in a later layer where no skin could reach it. It is a token now and this
     is the answer: a screen on an instrument is flush with its panel. */
  --pw-lcd-depth: 0;

  /* A hairline, in the accent, on the darker of the two grounds. The same
     move as the field's bracket: the furniture is drawn in the one ink this
     skin has, at the one weight it draws anything. */
  --pw-scrollbar-width: thin;
  --pw-scrollbar-thumb: var(--pw-color-primary);
  --pw-scrollbar-track: var(--pw-color-base-100);

}

[data-skin="cyber"][data-theme="dark"] {
  --pw-bevel-depth: 0;

  --pw-gloss-opacity: 0;
  --pw-gloss-glare-alpha: 0;
  --pw-gloss-cap-alpha: 0;
  --pw-gloss-cap-height: 0%;

  --pw-glow-intensity: 1;
  --pw-glow-radius: 8px;
  --pw-glow-color: var(--pw-cyan-300);

  --pw-glass-blur: 0px;

  --pw-shadow-outer-color: rgba(0, 0, 0, 0);

  --pw-color-vis-bg:   var(--pw-cyan-900);
  --pw-color-vis-grid: var(--pw-cyan-800);
  --pw-color-vis-low:  var(--pw-cyan-800);
  --pw-color-vis-mid:  var(--pw-cyan-300);
  --pw-color-vis-high: var(--pw-cyan-100);
  --pw-color-vis-peak: var(--pw-cyan-100);

  --pw-radius-control: 0;
  --pw-radius-box:     0;

  /* The same two, and the raster a shade stronger: a light scanline on a dark
     screen reads weaker than a dark one on a light screen at the same alpha. */
  --pw-clip-control:
    polygon(3px 0, 100% 0, 100% calc(100% - 3px), calc(100% - 3px) 100%, 0 100%, 0 3px);
  --pw-clip-box:
    polygon(5px 0, 100% 0, 100% calc(100% - 5px), calc(100% - 5px) 100%, 0 100%, 0 5px);

  --pw-texture-opacity: 0.09;
  --pw-texture: repeating-linear-gradient(
    to bottom,
    rgba(0, 0, 0, var(--pw-texture-opacity)) 0 1px,
    rgba(0, 0, 0, 0) 1px 3px
  );

  /* SELECTION IS DRAWN, NOT TINTED.
     
     The chrome skin marks a selected row by inverting it: the accent as a
     ground, its content colour on top. On a screen-lineage skin that reads as
     a lit cell, and it is the one idiom every reference in the set avoids.
     They bracket the selected thing and leave the ground alone.
     
     So the wash comes off and four corner marks go on. The row keeps the text
     colour it already had, which also makes the state cheaper to measure: a
     selected row stops being a second foreground on a second ground and goes
     back to being the same text it was.
     
     Eight gradients, two per corner. A border with a mask would be shorter
     and would not survive forced-colors, where masks stay and backgrounds go;
     gradients vanish there, which is correct, because the components restate
     selection in the system's own inks under that media query.
     
     Shape rather than colour, so removing the wash does not leave
     aria-selected carrying the state alone. */

  /* MONO, UPPERCASE, TRACKED.
     
     The single biggest reason this skin still read as the chrome one with a
     different palette. Both used the same system UI face at the same size with
     the same rhythm, so the only difference was hue and a bevel, and a skin
     that differs by hue is a theme.
     
     --pw-font-ui is a token precisely so a skin can answer this. The mono face
     is the one every reference in the set uses for structure: a terminal, a
     HUD and a BBS all set their labels in a fixed pitch, and the even column
     is what makes a row of controls read as an instrument rather than as a
     web page.
     
     Uppercase and the tracking that uppercase needs are in glow.css, on the
     controls only. Body copy stays as it is, which is the other half of the
     reference note: mono uppercase for structure, and only there. */
  --pw-font-ui: var(--pw-font-mono);
  --pw-tracking-ui: 0.045em;

  /* See the light block: pixels at 1x, vector at 2x. */
  --pw-icon-sheet: image-set(url("../assets/icons.cyber.px.svg") 1x, url("../assets/icons.cyber.svg") 2x);
  --pw-selected-bg: transparent;
  --pw-selected-content: inherit;
  --pw-selected-mark:
    linear-gradient(var(--pw-color-primary), var(--pw-color-primary))
      left top / var(--pw-bracket-arm) var(--pw-bracket-weight) no-repeat,
    linear-gradient(var(--pw-color-primary), var(--pw-color-primary))
      left top / var(--pw-bracket-weight) var(--pw-bracket-arm) no-repeat,
    linear-gradient(var(--pw-color-primary), var(--pw-color-primary))
      right top / var(--pw-bracket-arm) var(--pw-bracket-weight) no-repeat,
    linear-gradient(var(--pw-color-primary), var(--pw-color-primary))
      right top / var(--pw-bracket-weight) var(--pw-bracket-arm) no-repeat,
    linear-gradient(var(--pw-color-primary), var(--pw-color-primary))
      left bottom / var(--pw-bracket-arm) var(--pw-bracket-weight) no-repeat,
    linear-gradient(var(--pw-color-primary), var(--pw-color-primary))
      left bottom / var(--pw-bracket-weight) var(--pw-bracket-arm) no-repeat,
    linear-gradient(var(--pw-color-primary), var(--pw-color-primary))
      right bottom / var(--pw-bracket-arm) var(--pw-bracket-weight) no-repeat,
    linear-gradient(var(--pw-color-primary), var(--pw-color-primary))
      right bottom / var(--pw-bracket-weight) var(--pw-bracket-arm) no-repeat;

  /* THE REST OF THE SET, RESTATED UNCHANGED.

     None of these move between the themes, which is exactly why they have to
     be written here. [data-skin="cyber"] and
     [data-skin="cyber"][data-theme="dark"] tie on (0,2,0) with nothing but
     source order between them, so the block above is not an ancestor this one
     inherits from, it is a sibling this one is decided against. A block
     carrying only its differences leaves every token it omits to whichever
     rule the bundle happens to import last: silently, and looking correct.
     This block was thirteen tokens short of its light counterpart, including
     four the density axis owns.

     The density knobs are restated for that reason, and the compact block at
     the bottom of this file is what stops them from turning that axis off. */
  --pw-text-ui: 0.8125rem;
  --pw-text-micro: 0.6875rem;
  --pw-leading-ui: 1.15;

  --pw-control-pad-x: var(--pw-space-sm);
  --pw-control-gap:   var(--pw-space-xs);
  --pw-badge-h:       1.375rem;
  --pw-panel-pad:     var(--pw-space-lg);

  --pw-duration-hover: var(--pw-duration-snap);
  --pw-duration-enter: var(--pw-duration-snap);
  --pw-duration-exit:  var(--pw-duration-snap);
  --pw-ease-press: var(--pw-ease-hud);
  --pw-ease-enter: var(--pw-ease-hud);
  --pw-ease-state: var(--pw-ease-hud);

  /* NOTHING HERE IS AN OBJECT, so nothing here depresses. The chrome skin
     moves a pressed control one pixel down because it is a thing you push.
     A HUD's control is a mark on glass: it brightens, it brackets, and it
     stays exactly where it was. This skin was translating a pixel anyway,
     because the travel had never been a skin's to set. */
  --pw-press-travel: 0;
  --pw-range-inset: 1px;
  --pw-badge-text-drop: 1px;
  --pw-badge-mark-drop: -1px;

  /* And the readout is not a well. --pw-bevel-depth: 0 above flattens the
     whole treatment, and the readout kept a chrome-bevelled surround through
     three releases regardless, because lcd.css wrote the depth as a literal
     in a later layer where no skin could reach it. It is a token now and this
     is the answer: a screen on an instrument is flush with its panel. */
  --pw-lcd-depth: 0;

  /* A hairline, in the accent, on the darker of the two grounds. The same
     move as the field's bracket: the furniture is drawn in the one ink this
     skin has, at the one weight it draws anything. */
  --pw-scrollbar-width: thin;
  --pw-scrollbar-thumb: var(--pw-color-primary);
  --pw-scrollbar-track: var(--pw-color-base-100);

}

/* THE DENSITY AXIS, RESTATED FOR THIS SKIN.
   
   Not optional and not tidiness. [data-skin="cyber"] and
   [data-density="compact"] are both (0,1,0), and this file imports after
   density.css, so the block above wins the tie and every density-owned token
   it sets would hold at BOTH densities. Measured before this existed: cyber
   at compact had the comfortable 8px padding, so the compact axis was silently
   dead under this skin for everything the skin had touched.
   
   Only the four tokens density.css owns are restated. The type scale is not
   here on purpose: density aliases --pw-control-text to --pw-text-ui and
   --pw-text-micro, so moving those rungs above flows through both densities
   correctly and restating them here would break the alias rather than help it.
   
   (0,2,0) settles the tie with the base block. The POSITION settles the
   other one, and that half was missing here until paper shipped the bug it
   prevents: [data-skin="cyber"][data-theme="dark"] is also (0,2,0) and now
   declares its full set, density knobs included, so a tie on specificity is
   decided by whichever is written last. This block goes below every theme
   block for that reason, and test/contract.test.mjs holds it there. */
[data-skin="cyber"][data-density="compact"] {
  --pw-control-pad-x: var(--pw-space-xs);
  --pw-control-gap:   var(--pw-space-2xs);
  --pw-badge-h:       1.125rem;
  --pw-panel-pad:     var(--pw-space-md);
}

/* The third skin, and the one the elevation slot was split for: its depth is
   dither density rather than a shadow. */
/**
 * PRIMITIVE: the paper skin's raw ramps.
 *
 * Scoped to the skin, not to :root, for the reason primitive.cyber.css gives
 * at length: two ramps on a bare :root tie at (0,1,0) and the later import
 * wins under every skin, which is how the chrome skin spent three releases
 * painting its warning ink in the cyber skin's amber.
 *
 * ── Two inks, and the second one is spent carefully ──────────────────────
 * A duotone is a stock and two inks. The first version of this skin read that
 * as "orange and blue", which is what reference 7 shows, and it was the wrong
 * reading: that image is a photograph screened in two colours, not an
 * interface. A saturated blue primary sitting next to a warm accent had no
 * relationship to paper and no relationship to the rest of the skin either.
 *
 * So the ink is graphite, which is what a two-colour print job is actually
 * made of: the black plate, and one spot colour bought for the things worth
 * spending it on. --pw-paper-* runs stock to graphite and is the whole of the
 * first plate. --pw-warm-* is the spot, and it is reserved for attention:
 * focus, the range, the selected row, warning, and error. Nothing decorative
 * gets it.
 *
 * The phosphor ramp is deliberately absent. The readout is a lit display and
 * does not follow the skin, so it is read from the base file, which is also
 * what the cyber skin does.
 *
 * Every value was chosen against the contrast pairs rather than by eye, and
 * several moved: four after the first static pass, two after the runtime pass
 * (which measures what is painted, and caught the list's 0.75-opacity second
 * line at 4.35 and 4.28), and the spot once more when the same line landed at
 * 4.39 on the selected row.
 */

[data-skin="paper"] {
  /* The first plate: stock through to graphite. */
  --pw-paper-000: #ffffff;
  --pw-paper-050: #f5f1e8;
  --pw-paper-100: #eae4d6;
  --pw-paper-200: #dbd3c1;
  --pw-paper-300: #a89d82;
  --pw-paper-500: #847b6a;
  --pw-paper-700: #61594a;
  --pw-paper-800: #4a4438;
  --pw-paper-900: #2b271f;
  --pw-paper-950: #1a1712;

  /* The spot. Four shades, because a spot colour still needs a tint for its
     ground and a solid for its ink, in both themes. */
  --pw-warm-100: #f8e2cd;
  --pw-warm-300: #f2bc84;
  --pw-warm-700: #7d3b09;
  --pw-warm-800: #78400f;
}

/**
 * SEMANTIC: the paper skin's colour roles.
 *
 * Every role the chrome skin declares, declared again, because a skin that
 * states only its differences inherits the other one's decisions for
 * everything it forgot, silently and correctly-looking.
 *
 ── Where the spot colour goes ──────────────────────────────────────────
 * A two-colour print job is a black plate and one spot, and the spot is what
 * you spend on the things that need it. Here that is focus, the range, the
 * selected row, warning, and error. Everything structural is graphite: the
 * primary, the title bar, the whole chassis.
 *
 * Success is plain ink, deliberately. A thing that went right does not need
 * the expensive colour. Warning and error share the spot and the badge's own
 * glyph carries the difference, which is the argument the cyber skin makes
 * one ink further along: shape says which, colour says how loud.
 *
 * ── Dark ─────────────────────────────────────────────────────────────────
 * A light ground is what reference 7 is, and the kit requires both
 * themes of every skin. So dark is not an inverted sheet, it is a darker
 * stock: the same two inks printed on card rather than on paper, with the
 * inks moving up their ramps to stay legible on it.
 */

[data-skin="paper"],
[data-skin="paper"][data-theme="light"] {
  --pw-color-base-100: var(--pw-paper-050);
  --pw-color-base-200: var(--pw-paper-100);
  --pw-color-base-300: var(--pw-paper-200);
  --pw-color-base-content: var(--pw-paper-950);
  --pw-color-disabled-content: var(--pw-paper-500);

  --pw-color-primary: var(--pw-paper-900);
  --pw-color-primary-content: var(--pw-paper-050);
  --pw-color-titlebar: var(--pw-paper-950);
  --pw-color-titlebar-content: var(--pw-paper-050);
  --pw-color-focus: var(--pw-warm-700);
  --pw-color-focus-contrast: var(--pw-paper-050);
  --pw-color-range: var(--pw-warm-700);

  /* The readout is ink. Nothing on a printed sheet is lit, so this is the one
     skin whose readout follows the theme, the same way its analyser does:
     ink on a toned panel in light, paper on ink in dark. */
  --pw-color-lcd: var(--pw-paper-200);
  --pw-color-lcd-content: var(--pw-paper-950);

  /* Success is plain ink. In a two-colour job the spot is what you spend on
     the thing that needs attention, and a thing that went right does not. */
  --pw-color-success: var(--pw-paper-200);
  --pw-color-success-content: var(--pw-paper-950);
  --pw-color-warning: var(--pw-warm-100);
  --pw-color-warning-content: var(--pw-warm-800);
  --pw-color-error: var(--pw-warm-100);
  --pw-color-error-content: var(--pw-warm-800);

  /* The bevel pair is declared and unused: --pw-bevel-depth is 0 in this skin,
     so every offset collapses and the four inks paint nothing. Declared
     anyway, because the treatment reads --pw-bevel-boundary for the hairline
     that carries 1.4.11 and a half-declared pair is how the other three end up
     resolving somewhere nobody looked. */
  --pw-bevel-light: var(--pw-paper-000);
  --pw-bevel-face: var(--pw-paper-100);
  --pw-bevel-shade: var(--pw-paper-300);
  --pw-bevel-frame: var(--pw-paper-500);
  --pw-bevel-boundary: var(--pw-paper-500);
  --pw-color-divider: var(--pw-paper-300);

  --pw-gloss-lit-base-200: var(--pw-paper-100);
  --pw-gloss-lit-primary: var(--pw-paper-900);
  --pw-color-scrim: rgba(26, 23, 18, 0.58);
}

[data-skin="paper"][data-theme="dark"] {
  --pw-color-base-100: var(--pw-paper-950);
  --pw-color-base-200: var(--pw-paper-900);
  /* Not --pw-paper-700, which is the divider and the dither ink. A well is the
     one ground the list's 0.75-opacity second line sits on, and at 700 it
     measured 4.28 against a 4.5 floor. */
  --pw-color-base-300: var(--pw-paper-800);
  --pw-color-base-content: var(--pw-paper-050);
  --pw-color-disabled-content: var(--pw-paper-500);

  --pw-color-primary: var(--pw-paper-200);
  --pw-color-primary-content: var(--pw-paper-950);
  --pw-color-titlebar: var(--pw-paper-900);
  --pw-color-titlebar-content: var(--pw-paper-050);
  --pw-color-focus: var(--pw-warm-300);
  --pw-color-focus-contrast: var(--pw-paper-950);
  --pw-color-range: var(--pw-warm-300);

  --pw-color-lcd: var(--pw-paper-950);
  --pw-color-lcd-content: var(--pw-paper-100);

  --pw-color-success: var(--pw-paper-800);
  --pw-color-success-content: var(--pw-paper-050);
  --pw-color-warning: var(--pw-warm-800);
  --pw-color-warning-content: var(--pw-warm-300);
  --pw-color-error: var(--pw-warm-800);
  --pw-color-error-content: var(--pw-warm-300);

  --pw-bevel-light: var(--pw-paper-700);
  --pw-bevel-face: var(--pw-paper-900);
  --pw-bevel-shade: var(--pw-paper-950);
  --pw-bevel-frame: var(--pw-paper-500);
  --pw-bevel-boundary: var(--pw-paper-500);
  --pw-color-divider: var(--pw-paper-700);

  --pw-gloss-lit-base-200: var(--pw-paper-900);
  --pw-gloss-lit-primary: var(--pw-paper-200);
  --pw-color-scrim: rgba(5, 4, 3, 0.62);
}

/**
 * SKIN: the paper skin's own knobs.
 *
 * This is the skin the architecture was waiting for, and the reason it was
 * chosen over the two easier ones. --pw-elev is consumed as a box-shadow, both
 * shipped skins answered with one, and the kit's central claim had therefore
 * only ever been proved in the shape it was built for. Elevation here is
 * DITHER DENSITY, which is a background-image, and until the fill slots
 * existed this skin could not have been written at all.
 *
 * The other two were rejected for the opposite reason. A BBS skin's double
 * rule is two inset shadows, so it slots straight in and proves nothing.
 *
 * ── The second ground ────────────────────────────────────────────────────
 * A halftone surface is not one colour, it is two: the stock and the dot. The
 * contrast gate reads a computed background-color, which is the stock, and it
 * cannot see the dot at all. So text on a dithered surface is measured against
 * a ground it only partly sits on.
 *
 * The answer is not to keep the dither away from text, which would put it
 * nowhere useful. It is to hold the DOT to the same floor the stock is held
 * to, and to declare it as a pair so the gate measures it. --pw-dither-ink is
 * in tastecheck.config.json against --pw-color-base-content at 4.5, which is
 * the one pair no other skin in this kit needs and the one thing a fill-based
 * skin has to get right.
 *
 * ── Print does not move much ─────────────────────────────────────────────
 * Chrome settles, cyber snaps, and this drifts: the slowest of the three and
 * the softest easing, because ink does not have a mechanism.
 */

[data-skin="paper"],
[data-skin="paper"][data-theme="light"] {
  /* No bevel and no gloss. The whole stacked-inset treatment collapses, which
     is what --pw-bevel-depth: 0 has always claimed it would do, and the
     halftone fill is what arrives in its place. The icon sheet is two files,
     pixels at 1x and vector at 2x, because its lens is the one vector glyph
     and a curve is grey at 1x. */
  --pw-bevel-depth: 0;
  --pw-icon-sheet: image-set(url("../assets/icons.paper.px.svg") 1x, url("../assets/icons.paper.svg") 2x);
  --pw-gloss-opacity: 0;
  --pw-gloss-glare-alpha: 0;
  --pw-gloss-cap-alpha: 0;
  --pw-gloss-cap-height: 0%;

  /* Nothing glows. Ink is not a light source. */
  --pw-glow-intensity: 0;
  --pw-glow-radius: 0px;
  --pw-glow-color: var(--pw-paper-900);

  --pw-glass-blur: 0px;
  --pw-shadow-outer-color: rgba(0, 0, 0, 0);

  /* THE SCREEN IS THE SURFACE, DARKENED. Not an ink of its own.

     Two versions of this were wrong before it was right, and both were wrong
     the same way. The screen was a fixed colour, and a fixed colour printed on
     every surface has a different local contrast on each one: a warm grey dot
     measured 1.64:1 on paper stock and 3.91:1 on the primary. The primary
     looked like it had a rash. Softening the ink fixed the paper and did
     nothing for the button, because the button was never the same problem.

     Ink does not work that way. A screen is the surface's own colour at
     partial coverage, so it is a translucent black here and a translucent
     white in dark, and the dot is whatever it lands on with less light coming
     back. Local contrast is then constant everywhere: 1.25:1 on the stock,
     1.14:1 on the primary, 1.15:1 on the spot.

     This is the pattern glow.css already used for the cyber skin's scanlines,
     which are rgba(0, 0, 0, ...) rather than a named ink. The kit had solved
     it once and I did not look. */
  --pw-dither-ink: rgba(0, 0, 0, 0.10);

  /* Square, and no chamfer either. A cut corner is the HUD lineage; print is
     a rule and a right angle. */
  --pw-radius-control: 0;
  --pw-radius-box: 0;

  --pw-texture: none;
  --pw-texture-opacity: 0;

  /* Selection is the second ink laid over the row, which is what a duotone
     does with emphasis, and the chrome inversion is close enough to right
     that only the ground changes. */
  --pw-selected-bg: var(--pw-warm-700);
  --pw-selected-content: var(--pw-paper-000);
  --pw-selected-mark: none;

  /* Generous, which is the third rhythm. Chrome is 12px and cyber is 8; print
     has margins. */
  --pw-control-pad-x: var(--pw-space-lg);
  --pw-control-gap: var(--pw-space-md);
  --pw-panel-pad: var(--pw-space-xl);
  --pw-leading-ui: 1.35;
  --pw-tracking-ui: 0.01em;

  /* Ink drifts. The slowest of the three skins and the softest easing. */
  --pw-duration-hover: var(--pw-duration-base);
  --pw-duration-enter: var(--pw-duration-slow);
  --pw-ease-press: var(--pw-ease-in-out);
  --pw-ease-enter: var(--pw-ease-in-out);
  --pw-ease-state: var(--pw-ease-in-out);

  /* The analyser is a duotone too: one ink from quiet to hot, and the stock
     behind it rather than a phosphor face, because a printed chart is not a
     screen. */
  --pw-color-vis-bg: var(--pw-paper-100);
  --pw-color-vis-grid: var(--pw-paper-300);
  --pw-color-vis-low: var(--pw-paper-300);
  --pw-color-vis-mid: var(--pw-paper-700);
  --pw-color-vis-high: var(--pw-warm-700);
  --pw-color-vis-peak: var(--pw-paper-950);

  /* INK DOES NOT MOVE. A pressed control on a printed sheet has nowhere to
     travel to, and this skin was travelling a pixel because the travel had
     never been a skin's to set. Its press state is the second ink laid over
     the first, which the selection slot already does. */
  --pw-press-travel: 0;
  --pw-range-inset: 0;
  --pw-badge-text-drop: 1px;
  --pw-badge-mark-drop: 0;

  /* Nor is the readout a well. Same reason as the bevel: there is no depth on
     a sheet, and the dither is what arrives in its place. */
  --pw-lcd-depth: 0;

  /* A rule and a channel, which is what a margin is. Thin, because a printed
     page's furniture is thinner than its text, and in the stock's own greys
     rather than in the accent: the warm ink is for emphasis and a scrollbar
     is not emphasis. */
  --pw-scrollbar-width: thin;
  --pw-scrollbar-thumb: var(--pw-paper-300);
  --pw-scrollbar-track: var(--pw-paper-100);

  /* A SLAB, which is the one face that answers both things this skin already
     claims. Its icon sheet is documented as the heaviest of the three, three
     pixel strokes against chrome's two, and as "a catalogue's vocabulary
     rather than a HUD's". A slab serif is the heaviest serif there is, so it
     carries the weight argument, and it is unmistakably print, so it carries
     the catalogue one. It also closes a real gap the audit measured: paper's
     icons were running about twice the weight of its text, because its text
     was chrome's system-ui.

     The stack degrades on purpose. Rockwell ships with macOS and with Office
     on Windows, Roboto Slab is there if the consumer loaded it, and Georgia
     is on everything. A machine with none of the three lands on a
     transitional serif, which is the wrong century and the right medium: a
     book face on a printed sheet is a worse answer than a slab and a much
     better one than the operating system's UI sans. */
  --pw-font-ui: 'pw-Rockwell', 'Roboto Slab', Georgia, 'Times New Roman', serif;
  --pw-text-ui: 0.9375rem;
}

[data-skin="paper"][data-theme="dark"] {
  --pw-bevel-depth: 0;
  --pw-icon-sheet: image-set(url("../assets/icons.paper.px.svg") 1x, url("../assets/icons.paper.svg") 2x);
  --pw-gloss-opacity: 0;
  --pw-gloss-glare-alpha: 0;
  --pw-gloss-cap-alpha: 0;
  --pw-gloss-cap-height: 0%;

  --pw-glow-intensity: 0;
  --pw-glow-radius: 0px;
  --pw-glow-color: var(--pw-paper-200);

  --pw-glass-blur: 0px;
  --pw-shadow-outer-color: rgba(0, 0, 0, 0);

  /* White, not black. A darkening screen on a near-black stock does
     nothing at all. */
  --pw-dither-ink: rgba(255, 255, 255, 0.09);

  --pw-radius-control: 0;
  --pw-radius-box: 0;

  --pw-texture: none;
  --pw-texture-opacity: 0;

  --pw-selected-bg: var(--pw-warm-800);
  --pw-selected-content: var(--pw-paper-050);
  --pw-selected-mark: none;

  --pw-control-pad-x: var(--pw-space-lg);
  --pw-control-gap: var(--pw-space-md);
  --pw-panel-pad: var(--pw-space-xl);
  --pw-leading-ui: 1.35;
  --pw-tracking-ui: 0.01em;

  --pw-duration-hover: var(--pw-duration-base);
  --pw-duration-enter: var(--pw-duration-slow);
  --pw-ease-press: var(--pw-ease-in-out);
  --pw-ease-enter: var(--pw-ease-in-out);
  --pw-ease-state: var(--pw-ease-in-out);

  --pw-color-vis-bg: var(--pw-paper-900);
  --pw-color-vis-grid: var(--pw-paper-700);
  --pw-color-vis-low: var(--pw-paper-500);
  --pw-color-vis-mid: var(--pw-paper-200);
  --pw-color-vis-high: var(--pw-warm-300);
  --pw-color-vis-peak: var(--pw-paper-050);

  --pw-press-travel: 0;

  --pw-range-inset: 0;

  --pw-badge-text-drop: 1px;

  --pw-badge-mark-drop: 0;
  --pw-lcd-depth: 0;
  /* The same two rungs, reflected: the stock is near-black here, so the
     channel is the dark end of the ramp and the rule is a step up from it. */
  --pw-scrollbar-width: thin;
  --pw-scrollbar-thumb: var(--pw-paper-500);
  --pw-scrollbar-track: var(--pw-paper-900);

  /* A SLAB, which is the one face that answers both things this skin already
     claims. Its icon sheet is documented as the heaviest of the three, three
     pixel strokes against chrome's two, and as "a catalogue's vocabulary
     rather than a HUD's". A slab serif is the heaviest serif there is, so it
     carries the weight argument, and it is unmistakably print, so it carries
     the catalogue one. It also closes a real gap the audit measured: paper's
     icons were running about twice the weight of its text, because its text
     was chrome's system-ui.

     The stack degrades on purpose. Rockwell ships with macOS and with Office
     on Windows, Roboto Slab is there if the consumer loaded it, and Georgia
     is on everything. A machine with none of the three lands on a
     transitional serif, which is the wrong century and the right medium: a
     book face on a printed sheet is a worse answer than a slab and a much
     better one than the operating system's UI sans. */
  --pw-font-ui: 'pw-Rockwell', 'Roboto Slab', Georgia, 'Times New Roman', serif;
  --pw-text-ui: 0.9375rem;
}

/* THE DENSITY AXIS, RESTATED FOR THIS SKIN, AND WRITTEN LAST ON PURPOSE.

   [data-skin="paper"] and [data-density="compact"] are both (0,1,0) and this
   file imports after density.css, so the base block above wins the tie and
   every density-owned token it sets would hold at both densities. This
   selector is (0,2,0), which settles that half.

   The position in the file is the other half, and it is the half that was
   missing. [data-skin="paper"][data-theme="dark"] is ALSO (0,2,0), and it
   carries the same three knobs, because every skin x theme block in this kit
   declares its full set rather than its differences. A tie on specificity is
   decided by whichever is written last. With this block above the dark one,
   compact and comfortable both computed 1rem under paper+dark: the axis was
   dead across half the skin while the comment here said it could not be.

   So it goes at the bottom, below every theme block, and
   test/contract.test.mjs holds it there. */
[data-skin="paper"][data-density="compact"] {
  --pw-control-pad-x: var(--pw-space-md);
  --pw-control-gap: var(--pw-space-sm);
  --pw-panel-pad: var(--pw-space-lg);
}

/* The fourth skin, and the furthest from the first: a 1998 bitmap chassis,
   which is chrome's Web 2.0 inverted on every axis it has. It began as a
   page-local block in demo/player/classic.html, which proved it worked and
   proved nothing else, because a look that lives in a demo is a look a
   consumer cannot have. */
/**
 * PRIMITIVE: the dialup skin's ramp.
 *
 * Two families, because this skin has two very different grounds. Light is
 * Windows 95's own silver, which is what a 1998 application chassis was made
 * of and what every bevel in that era was drawn from. Dark is Winamp's actual
 * main.bmp grey, which is a shade nobody else in this kit uses: near-black
 * enough to sit under a phosphor display and light enough to bevel.
 *
 * The phosphor is its own family and it does not follow the theme. A lit
 * display is a light source; it does not dim because the page did. Same
 * argument semantic.chrome.css makes for --pw-color-lcd.
 */

[data-skin="dialup"] {
  /* Win95 silver. The four tiers a raised control is drawn from, and they are
     the reason this skin exists: 255, 223, 128, 0 is the exact ladder every
     3D border in that decade used. */
  --pw-dialup-000: #ffffff;
  --pw-dialup-050: #dfdfdf;
  --pw-dialup-100: #c0c0c0;
  --pw-dialup-200: #a8a8a8;
  --pw-dialup-300: #808080;
  --pw-dialup-500: #666666;
  --pw-dialup-700: #3f3f3f;
  --pw-dialup-800: #2a2a2a;
  --pw-dialup-900: #1a1a1a;
  --pw-dialup-950: #000000;

  /* The navy a period title bar and a selected row were both painted in.
     Every one of these is a number the contrast gate chose, not one I liked:
     navy-700 carries BOTH halves of the focus ring on the dark chassis, so it
     has to clear 3:1 against black and 4.5:1 against white at the same time,
     which is a narrow band and #2f6fb5 is in it. The first pass used the
     authentic #0a3a6a and failed the black half at 1.83. */
  --pw-navy-400: #5278b2;
  --pw-navy-700: #2f6fb5;
  --pw-navy-800: #000080;
  --pw-navy-900: #00004a;

  /* Phosphor. Fixed across both themes, for the reason in the header. */
  --pw-lcd-green-300: #00e000;
  --pw-lcd-green-500: #00a000;
  --pw-lcd-black: #000000;

  /* Status, in the muted inks a period dialog used. Nothing here is
     saturated: a 1998 chassis had no room for a bright chip. */
  --pw-moss-100: #c6dcc6;
  --pw-moss-800: #003a00;
  --pw-amber-100: #e8dcb0;
  --pw-amber-800: #4a3000;
  --pw-rust-100: #e6c0c0;
  --pw-rust-800: #6a0000;
  --pw-moss-300: #7fae7f;
  --pw-amber-300: #c9b167;
  --pw-rust-300: #d18585;
}

/**
 * SEMANTIC: what the dialup skin's colours are FOR.
 *
 * Both blocks declare the identical set, because [data-skin="dialup"] and
 * [data-skin="dialup"][data-theme="dark"] tie on specificity and are decided
 * by source order: a block carrying only its differences loses that tie for
 * every token it leaves out, silently. test/contract.test.mjs holds it.
 *
 * ── The two grounds are not the same idea ────────────────────────────────
 * Light is the application chassis: Win95 silver, black text, a navy title
 * bar. Dark is the player itself, Winamp's own main.bmp grey, which is the
 * surface a phosphor display was designed to sit on. Most skins treat dark as
 * light inverted. This one treats it as a different object.
 */

[data-skin="dialup"],
[data-skin="dialup"][data-theme="light"] {
  --pw-color-base-100: var(--pw-dialup-100);
  --pw-color-base-200: var(--pw-dialup-100);
  --pw-color-base-300: var(--pw-dialup-200);
  --pw-color-base-content: var(--pw-dialup-950);
  --pw-color-disabled-content: var(--pw-dialup-500);

  --pw-color-primary: var(--pw-navy-800);
  --pw-color-primary-content: var(--pw-dialup-000);
  --pw-color-titlebar: var(--pw-navy-800);
  --pw-color-titlebar-content: var(--pw-dialup-000);
  --pw-color-focus: var(--pw-dialup-950);
  --pw-color-focus-contrast: var(--pw-dialup-000);
  --pw-color-range: var(--pw-navy-800);

  /* The display does not follow the theme. */
  --pw-color-lcd: var(--pw-lcd-black);
  --pw-color-lcd-content: var(--pw-lcd-green-300);

  --pw-color-success: var(--pw-moss-100);
  --pw-color-success-content: var(--pw-moss-800);
  --pw-color-warning: var(--pw-amber-100);
  --pw-color-warning-content: var(--pw-amber-800);
  --pw-color-error: var(--pw-rust-100);
  --pw-color-error-content: var(--pw-rust-800);

  /* The 3D border, in the ladder the era used. */
  --pw-bevel-light: var(--pw-dialup-000);
  --pw-bevel-face: var(--pw-dialup-050);
  --pw-bevel-shade: var(--pw-dialup-300);
  --pw-bevel-frame: var(--pw-dialup-950);
  --pw-bevel-boundary: var(--pw-dialup-950);
  --pw-color-divider: var(--pw-dialup-300);

  /* Gloss is off in this skin, so these are the flat colours underneath it.
     They exist because the contrast block measures a pre-flattened pair: a
     glare is a sibling pseudo-element and no rendered-page check sees
     through it. */
  --pw-gloss-lit-base-200: var(--pw-dialup-100);
  --pw-gloss-lit-primary: var(--pw-navy-800);
  --pw-color-scrim: rgba(0, 0, 0, 0.5);
}

[data-skin="dialup"][data-theme="dark"] {
  --pw-color-base-100: var(--pw-dialup-700);
  --pw-color-base-200: var(--pw-dialup-700);
  --pw-color-base-300: var(--pw-dialup-800);
  --pw-color-base-content: #e8e8e8;
  --pw-color-disabled-content: #9a9a9a;

  --pw-color-primary: var(--pw-navy-700);
  --pw-color-primary-content: #ffffff;
  --pw-color-titlebar: var(--pw-navy-900);
  --pw-color-titlebar-content: #d8d8e4;
  --pw-color-focus: #ffffff;
  --pw-color-focus-contrast: var(--pw-dialup-950);
  --pw-color-range: var(--pw-navy-400);

  --pw-color-lcd: var(--pw-lcd-black);
  --pw-color-lcd-content: var(--pw-lcd-green-300);

  --pw-color-success: var(--pw-moss-800);
  --pw-color-success-content: var(--pw-moss-300);
  --pw-color-warning: var(--pw-amber-800);
  --pw-color-warning-content: var(--pw-amber-300);
  --pw-color-error: var(--pw-rust-800);
  --pw-color-error-content: var(--pw-rust-300);

  /* On a dark chassis the LIGHT edge is the one that reads as the boundary,
     so the roles keep their names and swap which end of the ramp they point
     at. The boundary is held to 3:1 against the ground like every other
     control edge in this kit, which on #3f3f3f means it cannot be the near
     black the light theme uses. */
  --pw-bevel-light: #707070;
  --pw-bevel-face: #4a4a4a;
  --pw-bevel-shade: var(--pw-dialup-800);
  --pw-bevel-frame: var(--pw-dialup-900);
  --pw-bevel-boundary: #9a9a9a;
  /* 2:1 against the chassis, which is this tier's own contract rather than a
     WCAG floor: a grouping line identifies nothing and changes no state, so
     1.4.11 does not reach it. #666 is the light theme's divider and it lands
     at 1.83 on #3f3f3f, which the gate caught. */
  --pw-color-divider: #707070;

  --pw-gloss-lit-base-200: var(--pw-dialup-700);
  --pw-gloss-lit-primary: var(--pw-navy-700);
  --pw-color-scrim: rgba(0, 0, 0, 0.7);
}

/**
 * SKIN: the dialup skin's own knobs.
 *
 * Named for the thing rather than for the product: a dialup-mount unit's
 * faceplate, silver and bevelled, with a phosphor display set into it. The
 * player it is drawn from is named in the README as prior art, which is what
 * everybody in this genre does and is fine; it is not what a consumer types.
 * test/contract.test.mjs holds that line, and it holds it because this exact
 * palette shipped under the product's name once already.
 *
 * The fourth skin, and the one that argues the kit's central claim hardest,
 * because it is the furthest thing from the first. Chrome is Web 2.0 on
 * purpose: gloss, stacked bevels, blue gradients, rounded controls. A 1998
 * bitmap skin is its opposite on every one of those axes, and this file is
 * the whole difference. No component changes, no forked rule.
 *
 * ── Where it came from ───────────────────────────────────────────────────
 * demo/player/classic.html had all of this as a page-local block for a while,
 * which proved it worked and proved nothing else: a look that lives in a demo
 * is a look a consumer cannot have. It is a skin now, so `data-skin="dialup"`
 * is the whole installation.
 *
 * ── What it does NOT change ──────────────────────────────────────────────
 * The density axis. Winamp's transport buttons were 23x18 and this kit's
 * floor is a 32px target, held by a contract test, so the SKIN does not go
 * near it. demo/player/classic.html still overrides the density knobs on its
 * own page, under its own attribute, which is exactly the line: a consumer
 * may go under the floor and the package may not take them there.
 */

[data-skin="dialup"],
[data-skin="dialup"][data-theme="light"] {
  /* Depth 1, not the half-pixel that looked clever. Every offset in
     bevel.css is a calc of this, so 0.5 does draw the one-pixel edge a 1998
     skin has, and it also asks for half-pixel shadows, which cannot be drawn:
     the browser antialiases them and where the grey lands depends on the
     element's own subpixel position. Two buttons side by side rasterised
     differently and read as misaligned. The tier colours carry the era; the
     depth just has to be an integer. */
  --pw-bevel-depth: 1;

  /* Chrome's sheet, deliberately. A period toolbar mark is solid, geometric
     and sized to sit on a button, which is what that set already is. Drawing
     it again to end up in the same place is how a sheet gains twenty-four
     glyphs and no new information. */
  --pw-icon-sheet: url("../assets/icons.svg");

  /* Nothing on a bitmap skin is glossy, lit, blurred or rounded. */
  --pw-gloss-opacity: 0;
  --pw-gloss-glare-alpha: 0;
  --pw-gloss-cap-alpha: 0;
  --pw-gloss-cap-height: 0%;

  --pw-glow-intensity: 1;
  --pw-glow-radius: 1px;
  --pw-glow-color: var(--pw-lcd-green-300);

  --pw-glass-blur: 0px;
  --pw-shadow-outer-color: rgba(0, 0, 0, 0);

  --pw-radius-control: 0;
  --pw-radius-box: 0;
  --pw-clip-control: none;
  --pw-clip-box: none;
  --pw-texture: none;
  --pw-texture-opacity: 0;

  /* A button on this chassis is an object you push, and it moves. Same
     reasoning chrome uses; this is the other skin that earns the travel. */
  --pw-press-travel: 1px;
  --pw-range-inset: 0;
  --pw-badge-text-drop: 0;
  --pw-badge-mark-drop: 1px;

  /* The display is flush with the panel, not sunk into it. Winamp's is flat
     black in the bitmap with no inset at all. */
  --pw-lcd-depth: 0;

  --pw-scrollbar-width: auto;
  --pw-scrollbar-thumb: var(--pw-dialup-100);
  --pw-scrollbar-track: var(--pw-dialup-200);

  /* The UI face of the era. Not a system stack: Tahoma is what the dialogs
     and the chassis labels were set in, and it is on every machine
     that has ever had Office. */
  --pw-font-ui: 'Tahoma', 'MS Sans Serif', Geneva, Verdana, sans-serif;
  --pw-text-ui: 0.6875rem;

  --pw-selected-bg: var(--pw-navy-800);
  --pw-selected-content: var(--pw-dialup-000);
  --pw-selected-mark: none;

  --pw-leading-ui: 1.2;
  --pw-tracking-ui: 0;

  /* An interface made of bitmaps does not ease. It snaps. */
  --pw-duration-hover: var(--pw-duration-snap);
  --pw-duration-enter: var(--pw-duration-snap);
  --pw-ease-press: var(--pw-ease-out);
  --pw-ease-enter: var(--pw-ease-out);
  --pw-ease-state: var(--pw-ease-out);

  --pw-control-pad-x: var(--pw-space-sm);
  --pw-control-gap: var(--pw-space-xs);
  --pw-panel-pad: var(--pw-space-md);

  --pw-color-vis-bg: var(--pw-lcd-black);
  --pw-color-vis-grid: transparent;
  --pw-color-vis-low: var(--pw-lcd-green-500);
  --pw-color-vis-mid: #00c800;
  --pw-color-vis-high: var(--pw-lcd-green-300);
  --pw-color-vis-peak: #b0b0b0;
}

[data-skin="dialup"][data-theme="dark"] {
  --pw-bevel-depth: 1;
  --pw-icon-sheet: url("../assets/icons.svg");

  --pw-gloss-opacity: 0;
  --pw-gloss-glare-alpha: 0;
  --pw-gloss-cap-alpha: 0;
  --pw-gloss-cap-height: 0%;

  --pw-glow-intensity: 1;
  --pw-glow-radius: 2px;
  --pw-glow-color: var(--pw-lcd-green-300);

  --pw-glass-blur: 0px;
  --pw-shadow-outer-color: rgba(0, 0, 0, 0);

  --pw-radius-control: 0;
  --pw-radius-box: 0;
  --pw-clip-control: none;
  --pw-clip-box: none;
  --pw-texture: none;
  --pw-texture-opacity: 0;

  --pw-press-travel: 1px;

  --pw-range-inset: 0;

  --pw-badge-text-drop: 0;

  --pw-badge-mark-drop: 1px;
  --pw-lcd-depth: 0;

  --pw-scrollbar-width: auto;
  --pw-scrollbar-thumb: #4a4a4a;
  --pw-scrollbar-track: var(--pw-dialup-800);

  --pw-font-ui: 'Tahoma', 'MS Sans Serif', Geneva, Verdana, sans-serif;
  --pw-text-ui: 0.6875rem;

  /* The deep navy, not the primary. navy-700 is pinned by the focus ring: it
     has to clear 3:1 against black and 4.5:1 against white at once, which
     lands it mid-tone, and a selected row's SECONDARY line is its content at
     reduced opacity. White at 0.85 over that mid-tone composites to #e0e9f4
     and measures 4.22 against it, which check-colour caught on the playlist's
     duration column and the declared-pair block could not, because nothing
     declares a pair for a colour that only exists after compositing. */
  --pw-selected-bg: var(--pw-navy-900);
  --pw-selected-content: #ffffff;
  --pw-selected-mark: none;

  --pw-leading-ui: 1.2;
  --pw-tracking-ui: 0;

  --pw-duration-hover: var(--pw-duration-snap);
  --pw-duration-enter: var(--pw-duration-snap);
  --pw-ease-press: var(--pw-ease-out);
  --pw-ease-enter: var(--pw-ease-out);
  --pw-ease-state: var(--pw-ease-out);

  --pw-control-pad-x: var(--pw-space-sm);
  --pw-control-gap: var(--pw-space-xs);
  --pw-panel-pad: var(--pw-space-md);

  --pw-color-vis-bg: var(--pw-lcd-black);
  --pw-color-vis-grid: transparent;
  --pw-color-vis-low: var(--pw-lcd-green-500);
  --pw-color-vis-mid: #00c800;
  --pw-color-vis-high: var(--pw-lcd-green-300);
  --pw-color-vis-peak: #b0b0b0;
}

/* THE DENSITY AXIS, RESTATED, AND WRITTEN LAST.

   [data-skin="dialup"] and [data-density="compact"] are both (0,1,0) and this
   file imports after density.css, so the blocks above would win the tie and
   hold their padding at both densities. This selector is (0,2,0), which
   settles that half.

   The position settles the other half. The theme block above is also (0,2,0)
   and carries the same knobs, because every skin x theme block here declares
   its full set, and a tie on specificity goes to whichever is written last.
   The paper skin shipped this exact bug with the exact same comment above it
   claiming it could not happen. test/contract.test.mjs holds the order now. */
[data-skin="dialup"][data-density="compact"] {
  --pw-control-pad-x: var(--pw-space-xs);
  --pw-control-gap:   var(--pw-space-2xs);
  --pw-panel-pad:     var(--pw-space-sm);
}

/* Presets: palettes within a skin, scoped under the skin they belong to, so
   an incoherent pairing matches nothing rather than rendering. One file each,
   imported after every skin so a preset's themed blocks are decided on
   specificity rather than on this list's order. */
/**
 * PRESET: deck, on the chrome skin.
 *
 * A late-nineties audio deck: blue-slate chassis, black display panels,
 * phosphor readout, gold faders, cyan metadata. The player it is drawn from
 * is cited in the README the way NES.css cites Nintendo, and is not the name,
 * because a trademark is not a name to ship.
 *
 * A preset is one file: a ramp, the roles pointing at it, and any knob it
 * wants moved. Nothing else in the repo has to know it exists.
 *
 * A preset may move a treatment's knobs and this one turns gloss off. The
 * line between preset and skin is about who OWNS the treatment: a skin ships
 * a file in css/treatment/ and decides what fills the elevation slot, a
 * preset ships none and moves values that treatment already reads. Moving
 * --pw-bevel-depth would cross it, since that is the elevation model rather
 * than a decoration. Nothing enforces that yet.
 *
 * Presets nest inside their skin rather than forming a fourth axis, because
 * theme, density and skin are orthogonal and this is not: a deck palette
 * under the cyber skin is nonsense, and scoping it means that combination
 * matches nothing and falls back instead of rendering something incoherent.
 *
 * Every block carries the preset attribute, which puts the themed ones at
 * (0,3,0). Chrome's own light block reaches (0,2,0), so a preset that only
 * reached (0,2,0) would be settled by import order instead.
 */

:root {

  --pw-pewter-000: #ffffff;
  --pw-pewter-050: #dde3ec;   /* light page */
  --pw-pewter-100: #c9d2df;   /* light control face */
  --pw-pewter-200: #b0bccd;   /* light well */
  --pw-pewter-400: #8794a8;   /* light divider, 2.54:1 on the page */
  --pw-pewter-500: #707d91;   /* dark divider, same 2.54:1 on its own page */
  --pw-pewter-600: #5a6478;   /* light boundary and disabled ink */
  --pw-pewter-700: #98a2b3;   /* dark boundary and disabled ink */
  --pw-pewter-800: #3b4252;   /* dark control face */
  --pw-pewter-900: #2e3440;   /* dark page */
  --pw-pewter-950: #1b1f27;   /* dark well */
  --pw-pewter-990: #141821;   /* light ink, and dark's primary ink */
  --pw-pewter-995: #14171d;   /* the dark title bar, darker than its own page */   /* light ink */
  --pw-pewter-010: #e5eaf2;   /* dark ink */

  --pw-ice-300: #4fd2ff;      /* dark primary, 9.03:1 on the dark page */
  --pw-ice-800: #1c4f70;      /* light primary */

  --pw-gold-300: #ffd23f;     /* the faders, and dark's warning ink */
  --pw-gold-800: #7a5300;     /* light's fader */

  --pw-deck-mint-100: #cfeddc;
  --pw-deck-mint-300: #5ff0a8;
  --pw-deck-mint-800: #0a5133;
  --pw-deck-mint-900: #153322;
  --pw-deck-gold-100: #f6e6c0;
  --pw-deck-gold-800: #6b4a05;
  --pw-deck-gold-900: #332708;
  --pw-deck-rose-100: #f7d9de;
  --pw-deck-rose-300: #ff8095;
  --pw-deck-rose-800: #8a1226;
  --pw-deck-rose-900: #331116;
}

[data-skin="chrome"][data-preset="deck"],
[data-skin="chrome"][data-preset="deck"][data-theme="light"] {

  /* A light theme for a skin that never had one. The chassis lightens; the
     displays do not, because a segment readout is a backlit object. */

  --pw-color-base-100: var(--pw-pewter-050);
  --pw-color-base-200: var(--pw-pewter-100);
  --pw-color-base-300: var(--pw-pewter-200);
  --pw-color-base-content: var(--pw-pewter-990);
  --pw-color-disabled-content: var(--pw-pewter-600);
  --pw-color-primary: var(--pw-ice-800);
  --pw-color-primary-content: var(--pw-pewter-000);
  --pw-color-titlebar: var(--pw-pewter-900);
  --pw-color-titlebar-content: var(--pw-pewter-010);
  --pw-color-focus: var(--pw-ice-800);
  --pw-color-focus-contrast: var(--pw-pewter-000);
  --pw-color-range: var(--pw-gold-800);
  --pw-color-lcd: var(--pw-phos-950);
  --pw-color-lcd-content: var(--pw-phos-300);
  --pw-color-success: var(--pw-deck-mint-100);
  --pw-color-success-content: var(--pw-deck-mint-800);
  --pw-color-warning: var(--pw-deck-gold-100);
  --pw-color-warning-content: var(--pw-deck-gold-800);
  --pw-color-error: var(--pw-deck-rose-100);
  --pw-color-error-content: var(--pw-deck-rose-800);
  --pw-bevel-light: var(--pw-pewter-000);
  --pw-bevel-face: var(--pw-pewter-100);
  --pw-bevel-shade: var(--pw-pewter-400);
  --pw-bevel-frame: var(--pw-pewter-600);
  --pw-bevel-boundary: var(--pw-pewter-600);
  --pw-color-divider: var(--pw-pewter-400);
  --pw-gloss-lit-base-200: var(--pw-pewter-100);
  --pw-gloss-lit-primary: var(--pw-ice-800);
  --pw-color-scrim: rgba(20, 24, 33, 0.62);

  /* No --pw-bevel-depth here. It inherits chrome's 1, and a preset that set
     it would be collapsing the elevation model rather than tuning a
     decoration. It did declare it, redundantly, until the check went in. */
  --pw-gloss-opacity: 0;
  --pw-gloss-glare-alpha: 0;
  --pw-gloss-cap-alpha: 0;
  --pw-gloss-cap-height: 0%;

  --pw-glow-intensity: 1;
  --pw-glow-radius: 6px;
  --pw-glow-color: var(--pw-phos-300);

  --pw-glass-blur: 0px;

  --pw-shadow-outer-color: rgba(28, 28, 37, 0.22);
}

[data-skin="chrome"][data-preset="deck"][data-theme="dark"] {

  /* The base skin's own register: blue-slate chassis, black displays, and a
     title bar darker than the page it sits on. */

  --pw-color-base-100: var(--pw-pewter-900);
  --pw-color-base-200: var(--pw-pewter-800);
  --pw-color-base-300: var(--pw-pewter-950);
  --pw-color-base-content: var(--pw-pewter-010);
  --pw-color-disabled-content: var(--pw-pewter-700);
  --pw-color-primary: var(--pw-ice-300);
  --pw-color-primary-content: var(--pw-pewter-990);
  --pw-color-titlebar: var(--pw-pewter-995);
  --pw-color-titlebar-content: var(--pw-pewter-010);
  --pw-color-focus: var(--pw-ice-300);
  --pw-color-focus-contrast: var(--pw-pewter-990);
  --pw-color-range: var(--pw-gold-300);
  --pw-color-lcd: var(--pw-phos-950);
  --pw-color-lcd-content: var(--pw-phos-300);
  --pw-color-success: var(--pw-deck-mint-900);
  --pw-color-success-content: var(--pw-deck-mint-300);
  --pw-color-warning: var(--pw-deck-gold-900);
  --pw-color-warning-content: var(--pw-gold-300);
  --pw-color-error: var(--pw-deck-rose-900);
  --pw-color-error-content: var(--pw-deck-rose-300);
  --pw-bevel-light: var(--pw-pewter-700);
  --pw-bevel-face: var(--pw-pewter-800);
  --pw-bevel-shade: var(--pw-pewter-950);
  --pw-bevel-frame: var(--pw-pewter-995);
  --pw-bevel-boundary: var(--pw-pewter-700);
  --pw-color-divider: var(--pw-pewter-500);
  --pw-gloss-lit-base-200: var(--pw-pewter-800);
  --pw-gloss-lit-primary: var(--pw-ice-300);
  --pw-color-scrim: rgba(0, 0, 0, 0.66);

  --pw-gloss-opacity: 0;
  --pw-gloss-glare-alpha: 0;
  --pw-gloss-cap-alpha: 0;
  --pw-gloss-cap-height: 0%;

  --pw-glow-intensity: 1;
  --pw-glow-radius: 6px;
  --pw-glow-color: var(--pw-phos-300);

  --pw-glass-blur: 0px;

  --pw-shadow-outer-color: rgba(0, 0, 0, 0.55);
}

/* PROPORTION. A deck is a smaller instrument than a chassis.
   
   A preset may move rhythm now, and this is what that is for. The palette
   alone made deck a recolour of chrome, and the thing a late-nineties audio
   deck was is tighter: less padding around a control, less air
   between them, and type a rung down. Winamp's main window was 275 by 116
   pixels, and none of the proportions here are within a mile of that, but the
   direction is the point.
   
   What it does not move is the treatment. The bevel is chrome's, the easing is
   chrome's, and the plastic still settles rather than snapping, because this
   is the same chassis built smaller and plainer rather than a different
   lineage. That is the line between a preset and a skin, and a test holds it.
   
   --pw-control-h is untouched, here for the same reason it is untouched in
   every skin: 44px is a hit target, not a style. */
[data-skin="chrome"][data-preset="deck"] {
  --pw-text-ui:       0.8125rem;
  --pw-control-pad-x: var(--pw-space-sm);
  --pw-control-gap:   var(--pw-space-xs);
  --pw-panel-pad:     var(--pw-space-lg);
}

/* Restated at compact, because [data-skin][data-preset] beats
   [data-density="compact"] and the axis would otherwise be dead under this
   preset. (0,3,0), so it beats the block above as well. */
[data-skin="chrome"][data-preset="deck"][data-density="compact"] {
  --pw-control-pad-x: var(--pw-space-xs);
  --pw-control-gap:   var(--pw-space-2xs);
  --pw-panel-pad:     var(--pw-space-md);
}

/**
 * PRESET: redline, for the cyber skin.
 *
 * ── Why this exists ──────────────────────────────────────────────────────
 * The cyber skin is documented as "accessible cyberpunk" and it is not one. It
 * is a terminal: slate and cyan, one restrained accent, a hairline edge, a
 * quiet raster. Its own reference note says so in the first line, "terminal,
 * HUD, and BBS rather than bevelled plastic", and every decision in it follows
 * that and not the word on the tin.
 *
 * That restraint is the right default and it is what makes the skin usable, so
 * it stays. This is where the name gets cashed instead: near-black stock, one
 * hot ink, the raster pushed, and the glow turned up. The base skin is the
 * instrument and this is the signage.
 *
 * ── What a preset may and may not do ─────────────────────────────────────
 * Anything a skin may do except the treatment. So the palette moves, the
 * raster moves, the glow moves, and the rhythm tightens. What does not move is
 * what fills the elevation slot: the hairline and the inner shadow are still
 * glow.css's, which is what keeps this a preset rather than a fourth skin. A
 * test enforces exactly that.
 *
 * ── One ink, harder ──────────────────────────────────────────────────────
 * The cyber skin already collapses success, warning, and error into one accent
 * and lets the badge's glyph say which. This keeps that and changes the accent
 * to a red that carries the whole skin, which is more defensible here than in
 * most palettes: a signage red on near-black IS the reference, and a second
 * hue would dilute exactly the thing the preset is for.
 *
 * Every value is measured against the same pairs every other look is, in both
 * themes. The dark theme needed its own boundary and divider shades, because
 * one mid cannot serve a #f2e8ea ground and a #0a0507 one.
 */

[data-skin="cyber"][data-preset="redline"],
[data-skin="cyber"][data-preset="redline"][data-theme="light"] {
  /* The stock, warmed toward the ink rather than neutral, because a red
     signage palette on a cold grey reads as an error state. */
  --pw-neon-000: #ffffff;
  --pw-neon-050: #f2e8ea;
  --pw-neon-100: #e2ccd2;
  --pw-neon-200: #c9a8b1;
  --pw-neon-300: #8d6c76;
  --pw-neon-400: #a8828d;
  --pw-neon-500: #6b4d55;
  --pw-neon-600: #70424d;
  --pw-neon-700: #4f2c34;
  --pw-neon-850: #1c1013;
  --pw-neon-900: #140b0e;
  --pw-neon-950: #0a0507;

  --pw-hot-100: #ffd9de;
  --pw-hot-300: #ff5c6e;
  --pw-hot-800: #8a0a1c;
  --pw-hot-900: #4d0611;

  --pw-color-base-100: var(--pw-neon-050);
  --pw-color-base-200: var(--pw-neon-100);
  --pw-color-base-300: var(--pw-neon-200);
  --pw-color-base-content: var(--pw-neon-950);
  --pw-color-disabled-content: var(--pw-neon-500);

  --pw-color-primary: var(--pw-hot-800);
  --pw-color-primary-content: var(--pw-neon-000);
  --pw-color-titlebar: var(--pw-hot-900);
  --pw-color-titlebar-content: var(--pw-neon-050);
  --pw-color-focus: var(--pw-hot-800);
  --pw-color-focus-contrast: var(--pw-neon-000);
  --pw-color-range: var(--pw-hot-800);

  --pw-color-success: var(--pw-hot-100);
  --pw-color-success-content: var(--pw-hot-800);
  --pw-color-warning: var(--pw-hot-100);
  --pw-color-warning-content: var(--pw-hot-800);
  --pw-color-error: var(--pw-hot-100);
  --pw-color-error-content: var(--pw-hot-800);

  --pw-bevel-light: var(--pw-neon-000);
  --pw-bevel-face: var(--pw-neon-100);
  --pw-bevel-shade: var(--pw-neon-300);
  --pw-bevel-frame: var(--pw-neon-500);
  --pw-bevel-boundary: var(--pw-neon-500);
  --pw-color-divider: var(--pw-neon-300);

  --pw-gloss-lit-base-200: var(--pw-neon-100);
  --pw-gloss-lit-primary: var(--pw-hot-800);
  --pw-color-scrim: rgba(10, 5, 7, 0.7);

  /* Selection is NOT restated here, and that is the decision rather than an
     omission. The cyber skin takes the wash off and draws four corner
     brackets in --pw-color-primary instead, which is what every reference in
     its set does and the chrome inversion is what they all avoid. This preset
     moves the primary to the hot ink, so leaving --pw-selected-bg and
     --pw-selected-content alone hands the brackets that ink for free.

     Restating them made the ground the SAME hot ink the brackets are drawn
     in: rgb(138,10,28) on rgb(138,10,28) in the light theme, selection
     invisible, and a preset quietly reverting its skin's most distinctive
     decision back into the inversion that skin exists to reject. The dark
     theme survived it on luck, because the primary moves to hot-300 there and
     the pair happened to separate. check-colour.mjs measures the mark against
     its ground at 3:1 now, so neither can come back. */

  /* The raster, pushed. The base skin runs its scanlines at 0.055 because a
     terminal is a surface you read for hours; signage is not. */
  --pw-texture-opacity: 0.12;

  /* And the glow, which the base skin spends on the readout alone. Here it is
     the point. */
  --pw-glow-intensity: 1;
  --pw-glow-radius: 12px;
  --pw-glow-color: var(--pw-hot-300);

  /* Tighter BETWEEN controls, not inside them. The first version took the
     padding down to 6px as well, which is 0.46em of the type it is set in, and
     it read as cramped because it was: the label very nearly touched the
     border. Signage is dense in its spacing and generous in its lozenges, and
     I had that backwards.

     So the gap tightens and the padding is left to the skin. A preset does not
     have to win on every axis to be a different thing. */
  --pw-control-gap: var(--pw-space-2xs);
  --pw-tracking-ui: 0.08em;

  --pw-color-vis-bg: var(--pw-neon-950);
  --pw-color-vis-grid: var(--pw-neon-850);
  --pw-color-vis-low: var(--pw-hot-900);
  --pw-color-vis-mid: var(--pw-hot-800);
  --pw-color-vis-high: var(--pw-hot-300);
  --pw-color-vis-peak: var(--pw-neon-050);
}

/* (0,3,0), so it beats the block above and the density file both. Only the gap
   is restated, because the padding is the skin's now and the skin already
   declares both densities. */
[data-skin="cyber"][data-preset="redline"][data-density="compact"] {
  --pw-control-gap: var(--pw-space-3xs);
}

[data-skin="cyber"][data-preset="redline"][data-theme="dark"] {
  --pw-color-base-100: var(--pw-neon-950);
  --pw-color-base-200: var(--pw-neon-900);
  --pw-color-base-300: var(--pw-neon-850);
  --pw-color-base-content: var(--pw-neon-050);
  /* Its own mid, because one shade cannot sit 3:1 from a #f2e8ea ground and a
     #0a0507 one. */
  --pw-color-disabled-content: var(--pw-neon-400);

  --pw-color-primary: var(--pw-hot-300);
  --pw-color-primary-content: var(--pw-neon-950);
  --pw-color-titlebar: var(--pw-hot-900);
  --pw-color-titlebar-content: var(--pw-hot-300);
  --pw-color-focus: var(--pw-hot-300);
  --pw-color-focus-contrast: var(--pw-neon-950);
  --pw-color-range: var(--pw-hot-300);

  --pw-color-success: var(--pw-hot-900);
  --pw-color-success-content: var(--pw-hot-300);
  --pw-color-warning: var(--pw-hot-900);
  --pw-color-warning-content: var(--pw-hot-300);
  --pw-color-error: var(--pw-hot-900);
  --pw-color-error-content: var(--pw-hot-300);

  --pw-bevel-light: var(--pw-neon-700);
  --pw-bevel-face: var(--pw-neon-900);
  --pw-bevel-shade: var(--pw-neon-950);
  --pw-bevel-frame: var(--pw-neon-400);
  --pw-bevel-boundary: var(--pw-neon-400);
  --pw-color-divider: var(--pw-neon-600);

  --pw-gloss-lit-base-200: var(--pw-neon-900);
  --pw-gloss-lit-primary: var(--pw-hot-300);
  --pw-color-scrim: rgba(4, 2, 3, 0.76);

  /* Not restated, for the reason written at the light block above. */

  --pw-texture-opacity: 0.16;

  --pw-glow-intensity: 1;
  --pw-glow-radius: 12px;
  --pw-glow-color: var(--pw-hot-300);

  --pw-control-gap: var(--pw-space-2xs);
  --pw-tracking-ui: 0.08em;

  --pw-color-vis-bg: var(--pw-neon-950);
  --pw-color-vis-grid: var(--pw-neon-850);
  --pw-color-vis-low: var(--pw-hot-900);
  --pw-color-vis-mid: var(--pw-hot-800);
  --pw-color-vis-high: var(--pw-hot-300);
  --pw-color-vis-peak: var(--pw-neon-050);
}

/**
 * PRESET: newsprint, for the paper skin.
 *
 * The paper skin is a two-colour job: a graphite plate and one spot bought for
 * the things worth spending it on. This is the one-colour job, which is what
 * most printing was: cheap cool stock, one plate, and a coarser screen
 * because the paper cannot hold a fine one.
 *
 * That is the whole idea and every value follows from it.
 *
 * ── There is no spot ─────────────────────────────────────────────────────
 * So warning and error are graphite like everything else, and the badge's own
 * glyph is the only thing left saying which. The kit has made that trade twice
 * before, in the cyber skin and again in paper's success role, and it is the
 * same argument each time: shape survives a palette losing a hue, and colour
 * does not.
 *
 * It is also the honest version of a rule this kit states everywhere and only
 * really tests here. A skin with three status hues can claim it never leans on
 * colour alone without ever being asked to prove it. A palette with one ink has
 * no way to cheat.
 *
 * ── The screen is coarser ────────────────────────────────────────────────
 * --pw-dither-ink is a skin knob rather than part of the treatment, so a preset
 * may move it and this one does: newsprint takes more ink and holds less
 * detail. What it cannot move is --pw-fill-raised and --pw-fill-sunken, which
 * are the treatment, which is what keeps this a preset. The dot gets heavier;
 * the process stays halftone.
 */

[data-skin="paper"][data-preset="newsprint"],
[data-skin="paper"][data-preset="newsprint"][data-theme="light"] {
  /* Cool and cheap, against the warm stock of the skin it sits in. */
  --pw-news-000: #ffffff;
  --pw-news-050: #eeeeec;
  --pw-news-100: #e2e2df;
  --pw-news-200: #d0d0cc;
  --pw-news-300: #9d9d97;
  --pw-news-500: #787872;
  --pw-news-700: #55554f;
  --pw-news-800: #3d3d38;
  --pw-news-900: #242422;
  --pw-news-950: #141413;

  --pw-color-base-100: var(--pw-news-050);
  --pw-color-base-200: var(--pw-news-100);
  --pw-color-base-300: var(--pw-news-200);
  --pw-color-base-content: var(--pw-news-950);
  --pw-color-disabled-content: var(--pw-news-500);

  --pw-color-primary: var(--pw-news-900);
  --pw-color-primary-content: var(--pw-news-050);
  --pw-color-titlebar: var(--pw-news-950);
  --pw-color-titlebar-content: var(--pw-news-050);
  --pw-color-focus: var(--pw-news-900);
  --pw-color-focus-contrast: var(--pw-news-050);
  --pw-color-range: var(--pw-news-700);

  /* One plate. The glyph carries the difference. */
  --pw-color-success: var(--pw-news-200);
  --pw-color-success-content: var(--pw-news-950);
  --pw-color-warning: var(--pw-news-200);
  --pw-color-warning-content: var(--pw-news-950);
  --pw-color-error: var(--pw-news-200);
  --pw-color-error-content: var(--pw-news-950);

  --pw-bevel-light: var(--pw-news-000);
  --pw-bevel-face: var(--pw-news-100);
  --pw-bevel-shade: var(--pw-news-300);
  --pw-bevel-frame: var(--pw-news-500);
  --pw-bevel-boundary: var(--pw-news-500);
  --pw-color-divider: var(--pw-news-300);

  --pw-gloss-lit-base-200: var(--pw-news-100);
  --pw-gloss-lit-primary: var(--pw-news-900);
  --pw-color-scrim: rgba(20, 20, 19, 0.58);

  --pw-selected-bg: var(--pw-news-900);
  --pw-selected-content: var(--pw-news-050);

  /* Heavier than the skin's 0.10. Cheap stock takes more ink and spreads it. */
  --pw-dither-ink: rgba(0, 0, 0, 0.14);

  --pw-color-vis-bg: var(--pw-news-100);
  --pw-color-vis-grid: var(--pw-news-300);
  --pw-color-vis-low: var(--pw-news-300);
  --pw-color-vis-mid: var(--pw-news-700);
  --pw-color-vis-high: var(--pw-news-950);
  --pw-color-vis-peak: var(--pw-news-950);
}

[data-skin="paper"][data-preset="newsprint"][data-theme="dark"] {
  --pw-color-base-100: var(--pw-news-950);
  --pw-color-base-200: var(--pw-news-900);
  --pw-color-base-300: var(--pw-news-800);
  --pw-color-base-content: var(--pw-news-050);
  --pw-color-disabled-content: var(--pw-news-500);

  --pw-color-primary: var(--pw-news-200);
  --pw-color-primary-content: var(--pw-news-950);
  --pw-color-titlebar: var(--pw-news-900);
  --pw-color-titlebar-content: var(--pw-news-050);
  --pw-color-focus: var(--pw-news-200);
  --pw-color-focus-contrast: var(--pw-news-950);
  --pw-color-range: var(--pw-news-300);

  --pw-color-success: var(--pw-news-800);
  --pw-color-success-content: var(--pw-news-050);
  --pw-color-warning: var(--pw-news-800);
  --pw-color-warning-content: var(--pw-news-050);
  --pw-color-error: var(--pw-news-800);
  --pw-color-error-content: var(--pw-news-050);

  --pw-bevel-light: var(--pw-news-700);
  --pw-bevel-face: var(--pw-news-900);
  --pw-bevel-shade: var(--pw-news-950);
  --pw-bevel-frame: var(--pw-news-500);
  --pw-bevel-boundary: var(--pw-news-500);
  --pw-color-divider: var(--pw-news-700);

  --pw-gloss-lit-base-200: var(--pw-news-900);
  --pw-gloss-lit-primary: var(--pw-news-200);
  --pw-color-scrim: rgba(6, 6, 6, 0.62);

  --pw-selected-bg: var(--pw-news-200);
  --pw-selected-content: var(--pw-news-950);

  --pw-dither-ink: rgba(255, 255, 255, 0.12);

  --pw-color-vis-bg: var(--pw-news-900);
  --pw-color-vis-grid: var(--pw-news-700);
  --pw-color-vis-low: var(--pw-news-500);
  --pw-color-vis-mid: var(--pw-news-200);
  --pw-color-vis-high: var(--pw-news-050);
  --pw-color-vis-peak: var(--pw-news-050);
}

}

@layer pw.reset {
/**
 * RESET: the smallest set of assumptions the skin needs, and nothing more.
 *
 * A component kit that resets globally is a kit that fights every page it is
 * dropped into. Everything here is scoped to panelware's own class names via
 * :where(), which contributes zero specificity, so a consumer overrides any
 * of it with a single class and no !important.
 *
 * This file declares no colour. If a colour appears here the skin split has
 * already leaked, because a reset that knows what colour a button is has
 * stopped being a reset.
 */

:where(.pw-button, .pw-toggle, .pw-tab, .pw-panel, .pw-title-button,
       .pw-badge, .pw-slider, .pw-slider-track, .pw-slider-range,
       .pw-slider-thumb, .pw-lcd, .pw-lcd-render, .pw-lcd-cell,
       .pw-tab-list, .pw-tab-panel, .pw-overlay,
       .pw-input, .pw-textarea, .pw-switch, .pw-switch-thumb,
       .pw-tooltip, .pw-separator, .pw-toolbar, .pw-visualiser) {
  box-sizing: border-box;
}

/* Native control chrome, removed so the bevel is the only chrome. Font is
   reset to the kit's own rather than inherited, because a button does not
   inherit font-family from its ancestors in any browser and inheriting the
   consumer's page font would make the kit look different in every app.

   border: 0 belongs here rather than on each component. appearance: none
   removes a button's native background in Chrome and leaves its border
   alone, so a control outside the shared elevation slot keeps the UA's
   2px outset and renders as a bevelled box nobody asked for. The menu bar
   triggers did exactly that, and the bar is meant to be flat.

   The slot sets border: 0 as well, which is why every component built before
   this one looked right and hid the gap. Setting it once here covers the
   next control added outside the slot too. */
:where(.pw-button, .pw-toggle, .pw-tab, .pw-title-button,
       .pw-transport-button, .pw-menubar-trigger,
       .pw-checkbox, .pw-radio, .pw-select,
       .pw-input, .pw-textarea, .pw-switch, .pw-collapsible-trigger) {
  appearance: none;
  border: 0;
  margin: 0;
  font-family: var(--pw-font-ui);
  font-size: var(--pw-control-text);
  line-height: var(--pw-leading-ui);
  letter-spacing: var(--pw-tracking-ui);
  cursor: pointer;
  /* asChild exists so a button can be an anchor, and an anchor arrives with
     an underline the kit never asked for. The colour is already handled by
     the shared treatment; this is the other half. */
  text-decoration: none;
}

/* The focus ring is declared once, here, for every control in the kit.
   :focus-visible rather than :focus, so a mouse press does not leave a ring
   behind and a Tab does.

   Two rings, in opposite values, drawn adjacent rather than one ring with a
   gap. A single ring is only ever as visible as the surface behind it
   happens to allow, and this kit puts controls on a title bar painted in the
   very accent the ring is drawn with, where it measured 1.00:1 and simply
   was not there.

   The inner ring is a box-shadow spread and the outer is the outline, so
   together they form one 4px band whose two halves contrast with each other
   at better than 9:1. On the page the outer ring carries it; on the accent
   bar the inner one does; on anything else the boundary between them is
   still a visible edge. That is what WCAG 2.4.13 is asking for, and it is
   why the ring no longer needs an offset: the inner ring fills what the gap
   used to. */
:where(.pw-button, .pw-toggle, .pw-tab, .pw-title-button, .pw-transport-button,
       .pw-menubar-trigger, .pw-checkbox, .pw-radio, .pw-select,
       .pw-slider-thumb, .pw-panel, .pw-list, .pw-lcd-pause,
       .pw-input, .pw-textarea, .pw-switch, .pw-collapsible-trigger):focus-visible {
  --pw-focus-halo: 0 0 0 var(--pw-focus-width) var(--pw-color-focus-contrast);
  outline: var(--pw-focus-width) solid var(--pw-color-focus);
  outline-offset: var(--pw-focus-width);
  /* For controls outside the shared elevation slot. Those inside it compose
     the halo with their bevel instead, in a later layer. */
  box-shadow: var(--pw-focus-halo);
}

/* The text a screen reader gets when the visible thing is a picture of text.
   Not display:none and not visibility:hidden, both of which remove it from
   the accessibility tree along with the screen. */
.pw-sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  white-space: nowrap;
  border: 0;
  clip-path: inset(50%);
}

}

/**
 * BEVEL: the chrome, as a swappable pair of shadow stacks.
 *
 * The pattern is 98.css's: two stacked inset box-shadows as a raised/sunken
 * pair, swapped on press. The sunken pair is the raised pair mirrored,
 * highlight for frame and face for shade, which is 98.css's own derivation.
 *
 * 98.css writes its offsets as literals. Here every one is a calc() on
 * --pw-bevel-depth, so a skin with no bevel zeroes the lot without editing a
 * rule. One literal 1px sneaking in leaves a ghost edge at depth 0 and
 * nothing reports it.
 *
 * NES.css does the same flip through a Sass mixin. Custom properties carry it
 * better here because a mixin resolves at build time and cannot be re-pointed
 * by a consumer, and this kit ships no build.
 */

@layer pw.treatment {
  /* ── Where these are declared is load-bearing ───────────────────────────
     On the component, not on :root, and that is not a style preference.

     A custom property that references another one is substituted against the
     element the FIRST one is declared on. Declare --pw-bevel-1 on :root and
     its var(--pw-bevel-depth) resolves against :root's depth, once. What
     descendants then inherit is the already-computed "1px", so setting
     --pw-bevel-depth: 0 on a container does nothing at all, and setting it
     on the readout to sink it deeper does nothing either.

     Both of those were shipped and both looked correct in the source. The
     depth-0 panel on demo/states.html is what caught them, which is the
     entire reason that panel exists: the knob had been verified by reading
     the calc() rather than by looking at a component with the knob turned
     down.

     Declaring the derivation here means every surface computes its own
     stack from whatever depth it has inherited, so the knob works at any
     level: :root, a container, or one component. */
  :where(.pw-button, .pw-toggle, .pw-tab, .pw-panel, .pw-badge,
         .pw-slider-track, .pw-slider-thumb, .pw-lcd,
         .pw-tab-panel, .pw-title-button, .pw-window,
         .pw-transport-button, .pw-progress, .pw-eq, .pw-menu, .pw-list,
         .pw-checkbox, .pw-radio, .pw-select, .pw-select-list, .pw-select-button,
         .pw-input, .pw-textarea, .pw-switch, .pw-switch-thumb, .pw-tooltip,
         .pw-visualiser,
         .pw-raised, .pw-sunken) {
    /* Two knobs multiplied, because they have two owners. --pw-bevel-depth is
       the skin's: chrome ships 1 and the cyber preset ships 0, and a consumer
       has no business editing it. --pw-depth is the consumer's, kept by name
       from DaisyUI, and until now it was declared, documented as the thing
       that flattens the kit, published in the demo's token table, and read by
       absolutely nothing. Its own comment said "see bevel.css for what
       reads this" and bevel.css did not.

       Multiplied here rather than resolved on :root for the reason the test
       below this file's derivation checks: a custom property referencing
       another is substituted against the element the first is declared on, so
       a :root derivation resolves once and every descendant inherits a
       finished pixel value. Doing it in the component block is what lets
       either knob be set on a subtree and mean something. */
    --pw-bevel-scale: calc(var(--pw-bevel-depth) * var(--pw-depth));
    --pw-bevel-1:  calc(var(--pw-bevel-scale) * 1px);
    --pw-bevel-2:  calc(var(--pw-bevel-scale) * 2px);
    --pw-bevel-1n: calc(var(--pw-bevel-scale) * -1px);
    --pw-bevel-2n: calc(var(--pw-bevel-scale) * -2px);

    --pw-bevel-raised-outer:
      inset var(--pw-bevel-1n) var(--pw-bevel-1n) var(--pw-bevel-frame),
      inset var(--pw-bevel-1)  var(--pw-bevel-1)  var(--pw-bevel-light);
    --pw-bevel-raised-inner:
      inset var(--pw-bevel-2n) var(--pw-bevel-2n) var(--pw-bevel-shade),
      inset var(--pw-bevel-2)  var(--pw-bevel-2)  var(--pw-bevel-face);

    --pw-bevel-sunken-outer:
      inset var(--pw-bevel-1n) var(--pw-bevel-1n) var(--pw-bevel-light),
      inset var(--pw-bevel-1)  var(--pw-bevel-1)  var(--pw-bevel-frame);
    --pw-bevel-sunken-inner:
      inset var(--pw-bevel-2n) var(--pw-bevel-2n) var(--pw-bevel-face),
      inset var(--pw-bevel-2)  var(--pw-bevel-2)  var(--pw-bevel-shade);

    /* The cast shadow. The skin owns its colour and the treatment owns its
       geometry, so this scales with depth for free and collapses with the
       rest of the stack. */
    --pw-shadow-outer:
      0 calc(var(--pw-bevel-depth) * 1px)
        calc(var(--pw-bevel-depth) * 2px) var(--pw-shadow-outer-color);

    /* The two values a component swaps between. A raised surface
       casts; a sunken one is a hole in something and casts nothing. */
    --pw-shadow-raised:
      var(--pw-bevel-raised-outer),
      var(--pw-bevel-raised-inner),
      var(--pw-shadow-outer);
    --pw-shadow-sunken:
      var(--pw-bevel-sunken-outer),
      var(--pw-bevel-sunken-inner);

    /* The other half of elevation, for a skin whose depth is not a shadow.

       --pw-elev is consumed as a box-shadow, which was fine while both skins
       answered with one. A skin whose raised and sunken states differ by
       DITHER DENSITY, by hatch pitch, or by any other pattern cannot put that
       in a box-shadow at all: it is a background-image, and no custom property
       feeds two different properties.

       So the pair is mirrored. The chrome skin has no fill elevation and says
       so with none, which costs nothing and means the slot below can read it
       unconditionally rather than guarding every surface.

       Same reason these live on the component rather than on :root: a fill
       derived from a skin knob has to resolve against whatever the element
       inherited, not against the document. */
    --pw-fill-raised: none;
    --pw-fill-sunken: none;

    /* The defaults for anything that has not said otherwise. A component
       choosing sunken sets both, and a test holds the two together, because
       setting one and forgetting the other is a surface that is sunken in one
       skin and raised in another with nothing reporting it. */
    --pw-elev: var(--pw-shadow-raised);
    --pw-elev-fill: var(--pw-fill-raised);
  }

  /* ── The shared slot ─────────────────────────────────────────────────────
     Every panelware surface reads --pw-elev and nothing reads
     --pw-shadow-raised directly. The name is deliberately role-neutral: the
     second skin puts an outer glow and a hairline in this slot, and a
     component file that said "bevel" would then be describing something that
     is not there. Renaming it later is a major version; naming it right now
     is free.

     :where() so the whole block is zero specificity and a component rule,
     or a consumer's, overrides any line of it with one class.

     Nothing here sets overflow: hidden, and nothing downstream may either.
     It is the obvious way to clip a gloss highlight to a rounded corner, and
     it would clip the second skin's outer glow dead. gloss.css clips its own
     pseudo-elements instead. */
  :where(.pw-button, .pw-toggle, .pw-tab, .pw-panel, .pw-badge,
         .pw-slider-track, .pw-slider-thumb, .pw-lcd,
         .pw-tab-panel, .pw-title-button, .pw-window,
         .pw-transport-button, .pw-progress, .pw-eq, .pw-menu, .pw-list,
         .pw-checkbox, .pw-radio, .pw-select, .pw-select-list, .pw-select-button,
         .pw-input, .pw-textarea, .pw-switch, .pw-switch-thumb, .pw-tooltip,
         .pw-visualiser) {
    /* The bevel, plus the focus ring's inner half when there is one.
       --pw-focus-halo is transparent by default and set on :focus-visible. */
    box-shadow: var(--pw-elev), var(--pw-focus-halo);
    /* The background stack, in one token so a component that paints its own
       surface can splice the slot in rather than repeating it. The visualiser
       is the one that does, and before this token existed it simply shadowed
       the whole stack: the cyber skin's scanlines never reached the one
       surface in the kit that is literally a screen.

       Ornament on top, then the elevation fill, then the texture underneath.
       That order is the argument: a mark a skin draws sits over the depth, and
       the depth sits over the surface pattern. */
    --pw-surface-layers: var(--pw-ornament), var(--pw-elev-fill), var(--pw-texture);
    background-image: var(--pw-surface-layers);
    /* One size per layer, in the same order. background-size repeats a short
       list across every layer, so a single value here would resize the fill
       and the texture to whatever the ornament wanted. */
    --pw-surface-sizes: var(--pw-ornament-size), var(--pw-fill-size), auto;
    background-size: var(--pw-surface-sizes);
    /* No border. The outer inset shadow IS the frame, which is 98.css's own
       arrangement: it sets border: none on its buttons and lets the four
       insets carry the whole edge.

       Drawing both was the first thing the depth-0 panel on demo/states.html
       caught. A 1px border in --pw-bevel-frame sitting immediately outside a
       1px inset shadow in --pw-bevel-frame reads as one 2px line on the dark
       side, pushes the white highlight a pixel inward where the border hides
       half of it, and, and the part that matters, does not scale with
       --pw-bevel-depth, so it survived depth 0 as a ghost edge. Everything
       the comment at the top of this file warns about, shipped. */
    border: 0;
    border-radius: var(--pw-radius-control);
    clip-path: var(--pw-clip-control);
    background-color: var(--pw-color-base-200);
    color: var(--pw-color-base-content);
  }

  /* Forced colors strips box-shadow and keeps border, so a kit whose entire
     boundary is a shadow has no boundary at all in Windows High Contrast.
     This is the reason to own the case explicitly rather than to keep a
     border everywhere for it: here the border is the only edge, it uses the
     system's own colours, and it is the one place --pw-border is read. */
  @media (forced-colors: active) {
    :where(.pw-button, .pw-toggle, .pw-tab, .pw-panel, .pw-badge,
           .pw-slider-track, .pw-slider-thumb, .pw-lcd,
           .pw-tab-panel, .pw-title-button, .pw-window,
           .pw-transport-button, .pw-progress, .pw-eq, .pw-menu, .pw-list,
           .pw-checkbox, .pw-radio, .pw-select, .pw-select-list, .pw-select-button,
           .pw-input, .pw-textarea, .pw-switch, .pw-switch-thumb, .pw-tooltip,
           .pw-visualiser) {
      border: var(--pw-border) solid ButtonBorder;
    }
  }

  /* Everything that draws a focus ring opts out of the corner clip, and this
     is not tidiness.

     clip-path clips an element's rendering entire: its corners, its
     pseudo-elements, and its OUTLINE. The focus ring in this kit is an
     outline, declared once in reset.css for the list repeated below. So a
     skin that set --pw-clip-control, which the README offers as the way to
     give the kit notched corners, would have removed the focus ring from
     every control in that skin. Measured with a 10px outline and a 12px
     chamfer side by side: the unclipped box draws its ring, the clipped box
     draws nothing at all.

     That leaves eleven surfaces the clip still reaches, which is the whole
     window chrome, the menu, the tab panel, the readout, the badge and the
     wells. A notched skin is still a token change. It just cannot notch the
     things a keyboard lands on.

     The list is reset.css's focus-ring list verbatim, and a test holds the
     two together, because the failure is silent: the ring is simply not
     painted and every computed style still says outline-style: solid.

     Three of these have a second reason on top.
     clip-path clips an element's outline and its pseudo-elements along with
     its corners. Each of these draws its hit area as a ::after expander and
     its focus ring as an outline at a 2px offset, so a skin that set
     --pw-clip-control would silently collapse the target back to the ink and
     clip the ring away with it.

     Measured, by setting a notched polygon and hit-testing from each
     control's centre. The thumb's 44x44 went back to the 22x28 grip. The
     checkbox and the radio went back to their 20x20 boxes: a hit at centre
     plus 15px, and at centre plus 18px, both stopped landing on the control.
     That is under the house 44 floor and under WCAG 2.5.8's 24x24.

     The radio has a second reason. It is the one round thing in the kit and
     the roundness is what says choose-one before a word has been read, and a
     notched polygon overrides border-radius rather than intersecting with
     it, so the hook squared it off.

     None of those three has room for a notch at its size anyway. */
  :where(.pw-button, .pw-toggle, .pw-tab, .pw-title-button, .pw-transport-button,
         .pw-menubar-trigger, .pw-checkbox, .pw-radio, .pw-select,
         .pw-slider-thumb, .pw-panel, .pw-list, .pw-lcd-pause,
         .pw-input, .pw-textarea, .pw-switch, .pw-collapsible-trigger) { clip-path: none; }

  /* Documented escape hatches for surfaces a consumer builds that are not
     panelware components. Nothing internal uses them; internal components
     assign --pw-elev instead, which is one declaration rather than a second
     class in the markup. */
  .pw-raised { box-shadow: var(--pw-shadow-raised); }
  .pw-sunken { box-shadow: var(--pw-shadow-sunken); }
}

/**
 * GLOSS: the Web 2.0 highlight, opt-in twice.
 *
 * Per element with [data-gloss], and per skin with --pw-gloss-opacity. Both
 * gates exist for different reasons. The attribute keeps the decision at the
 * call site, where "this one button is the primary action" is known. The
 * token keeps the decision reversible for a whole skin at once: a skin that
 * refuses gloss sets the opacity to 0 and every [data-gloss] in every
 * consumer's markup goes quiet without anyone editing markup.
 *
 * An attribute rather than a class, because a class would accumulate in the
 * markup next to the component class and that is the beginning of treatment
 * classes smeared across HTML.
 *
 * The values are the period ones, recorded in HANDOFF.md from the actual
 * 2006-07 tutorials and from makeaero's current source. The 120deg glare with
 * its four stops is makeaero's verbatim. The tutorials specify blend modes
 * (Overlay at 70%, Soft Light at 30-70%); those became plain alpha here,
 * because a blend mode composites against whatever is behind the element and
 * a component kit does not know what that is. An unmeasurable value is not a
 * value this kit can gate.
 *
 * Neither pseudo-element is transitioned. The gloss is a static highlight on
 * a still surface; a sweeping glare is the thing that makes a page look like
 * it is selling something.
 */

@layer pw.treatment {
  :where([data-gloss]) {
    position: relative;
    isolation: isolate;
  }

  /* The diagonal glare. */
  :where([data-gloss])::before,
  /* The inset top-highlight capsule, which is the half that reads
     as a curved surface. The glare alone reads as a reflection on flat
     glass. */
  :where([data-gloss])::after {
    content: '';
    position: absolute;
    pointer-events: none;
    opacity: var(--pw-gloss-opacity);
    /* Each pseudo clips itself rather than the host clipping both with
       overflow: hidden. See the note in bevel.css: overflow would clip the
       second skin's outer glow along with the highlight. */
    border-radius: inherit;
    /* Behind the content, not in front of it.
       The host isolates, so -1 paints above its own background and below its
       inline content. That covers a bare text node, which the previous
       arrangement could not: it lifted the host's ELEMENT children to
       z-index 2, so `<button data-gloss>Save</button>` put the highlight over
       the word while `<button data-gloss><span>Save</span></button>` did not.
       The fix there would have been to make every component wrap its children
       in a span, which costs the button's own flex gap between an icon and a
       label, for a layering problem the stacking context already solves. */
    z-index: -1;
  }

  :where([data-gloss])::before {
    inset: 0;
    background-image: linear-gradient(
      120deg,
      transparent 30%,
      rgb(255 255 255 / var(--pw-gloss-glare-alpha)) 45%,
      rgb(255 255 255 / calc(var(--pw-gloss-glare-alpha) * 0.14)) 55%,
      transparent 70%
    );
  }

  :where([data-gloss])::after {
    inset: var(--pw-bevel-2) var(--pw-bevel-2) auto var(--pw-bevel-2);
    height: var(--pw-gloss-cap-height);
    background-image: linear-gradient(
      to bottom,
      rgb(255 255 255 / var(--pw-gloss-cap-alpha)),
      transparent
    );
  }

  /* A pressed control is sunken, and a sunken thing does not catch a
     highlight on its top edge. Killing the capsule and dimming the glare is
     what makes the press read as depth rather than as a colour change.

     The element list is doing real work, and :active alone was a bug.
     :active matches every ANCESTOR of the element being activated, not only
     the element itself. The dialog's title bar carries the gloss and
     contains the close button, so pressing the X put the title bar in
     :active and dimmed the entire bar: the glare fell from 1 to 0.4 and the
     highlight capsule went to 0. It reads as a change that is not attached
     to the thing you pressed, because it is not.

     It is not period behaviour either. An XP titlebar's gradient did not
     move when you clicked its close button; only the button did.

     So the rule is now what it always meant: the gloss dims on a control
     that sinks. A title bar is not a control and does not sink, so it is
     not in this list and never matches, whether the press lands on it or on
     something inside it. */
  :where(button, a[href], [role="button"], [role="tab"], [role="switch"])[data-gloss]:active::after,
  :where([data-gloss][data-state="on"])::after {
    opacity: 0;
  }
  :where(button, a[href], [role="button"], [role="tab"], [role="switch"])[data-gloss]:active::before,
  :where([data-gloss][data-state="on"])::before {
    opacity: calc(var(--pw-gloss-opacity) * 0.4);
  }
}

/* The second skin's elevation. After bevel and gloss, because it replaces
   what they put in the shared slot and one attribute of specificity is all
   that separates them. */
/**
 * TREATMENT: the cyber skin's elevation.
 *
 * This file is the answer to the question the token contract was built to ask:
 * can a second skin replace the treatment without touching a component? Nothing
 * below names a component's behaviour, only its surface.
 *
 * --pw-elev is the slot, and it was named for a role rather than for a bevel
 * precisely so this file could put something else in it. The chrome skin fills
 * it with four stacked inset shadows; this fills it with one hairline. Both are
 * box-shadow, both land in the same declaration in bevel.css, and no component
 * knows which it got.
 *
 * Specificity is doing the work. bevel.css declares the pair inside a bare
 * :where(), which is (0,0,0) on purpose so a component can override it with one
 * class. One attribute selector in front is (0,1,0) and wins, so this needs no
 * !important and no duplicated component rules.
 *
 * The selector list below is bevel.css's DERIVATION list, copied, which is the
 * one that declares the raised and sunken pair. It is two entries longer than
 * the shared slot that reads them, because .pw-raised and .pw-sunken are the
 * documented escape hatches for a consumer's own surfaces and they have to
 * mean something under both skins. Copying the shorter list left them on
 * chrome's bevel while everything around them went flat.
 *
 * test/css.test.mjs fails if the two disagree: a surface that reached the slot
 * in one skin and not the other is flat in exactly one skin, and nothing else
 * would say so.
 */

@layer pw.treatment {
  [data-skin="cyber"] :where(.pw-button,
         .pw-toggle,
         .pw-tab,
         .pw-panel,
         .pw-badge,
         .pw-slider-track,
         .pw-slider-thumb,
         .pw-lcd,
         .pw-tab-panel,
         .pw-title-button,
         .pw-window,
         .pw-transport-button,
         .pw-progress,
         .pw-eq,
         .pw-menu,
         .pw-list,
         .pw-checkbox,
         .pw-radio,
         .pw-select,
         .pw-select-list,
         .pw-select-button,
         .pw-input,
         .pw-textarea,
         .pw-switch,
         .pw-switch-thumb,
         .pw-tooltip,
         .pw-visualiser,
         .pw-raised,
         .pw-sunken) {
    /* One hairline in the boundary ink, which carries WCAG 1.4.11 in this skin
       exactly as chrome's outer line did: 3.83:1 on the page and 3.30:1 on a
       control in light, 4.85:1 and 3.72:1 in dark. Chrome spent four inks on an
       edge because it was imitating moulded plastic. This is imitating a
       screen, and a screen's edge is one line. */
    --pw-shadow-raised: inset 0 0 0 1px var(--pw-bevel-boundary);

    /* Sunken keeps the hairline and adds an inner shadow, so the difference
       between the two states is depth rather than a colour swap. That matters
       most on the toggle, where the sunken face is what says "on": the rule
       that no state is carried by colour alone does not stop applying because
       the skin changed. */
    --pw-shadow-sunken:
      inset 0 0 0 1px var(--pw-bevel-boundary),
      inset 0 2px 6px -2px var(--pw-color-scrim);

    /* No fill elevation. This skin's depth is a line and an inner shadow, both
       of which are shadows, so the fill pair is declared and empty. Declaring
       it rather than leaving it to the base is the same rule the rest of this
       file follows: a skin that states only its differences inherits the other
       one's decisions for everything it forgot. */
    --pw-fill-raised: none;
    --pw-fill-sunken: none;
  }

  /* Uppercase, and only where the reference set puts it.
     
     A control's label is structure: it names an action, it is read at a
     glance, and in every reference in the set it is set in caps. A list row's
     text is content and stays as the consumer wrote it, which is why the row
     is not in this list and neither is a menu item.
     
     text-transform rather than a token, because no component declares it, so
     the treatment layer can simply claim it. That is the one property in this
     file that does not need a slot: pw.components only outranks pw.treatment
     where both declare the same thing, and here only one of them does.
     
     The tracking comes from --pw-tracking-ui, which the skin raises. Caps at
     the default tracking set solid is the classic way to make uppercase
     unreadable.
     
     .pw-lcd-caption was in this list and nothing in the kit has ever rendered
     it: no component emits it and no component file styles it, so the rule
     could never match. Writing the class reference is what found it, because
     it was the one class in the bundle with no honest description. */
  [data-skin="cyber"] :where(.pw-button,
                             .pw-toggle,
                             .pw-tab,
                             .pw-badge,
                             .pw-menubar-trigger,
                             .pw-title) {
    text-transform: uppercase;
  }
}

/* The third skin's elevation, and the first that is not a box-shadow. */
/**
 * TREATMENT: the paper skin's elevation, which is not a shadow.
 *
 * This file is the reason the elevation slot was split, and the answer to the
 * question the token contract was built to ask. The kit's claim is that a skin
 * replaces the treatment without touching a component. It had been proved
 * twice and both times with a box-shadow, because --pw-elev is consumed as
 * one, so the claim had only ever been tested in the shape it was built for.
 *
 * Elevation here is DITHER DENSITY. A raised surface is printed at a coarse
 * screen and a sunken one at a fine, faint one, which is what a halftone does
 * with depth and what no box-shadow can express. It goes in --pw-fill-raised
 * and --pw-fill-sunken, and no component knows.
 *
 * ── The hairline is not decoration ───────────────────────────────────────
 * Both states carry the same 1px boundary and the density is the only thing
 * that changes. That is deliberate: WCAG 1.4.11 asks 3:1 of the edge that says
 * where a control ends, a screen of dots does not make a crisp edge at any
 * density, and a skin that expressed its whole boundary as a halftone would be
 * a skin whose controls have no identifiable edge. The dots carry depth, the
 * line carries the boundary, and they are different jobs.
 *
 * ── The pitch is literal ─────────────────────────────────────────────────
 * 4px, written here rather than tokenised, which is what glow.css does with
 * its own 1px hairline and inner shadow. A treatment's geometry belongs to the
 * treatment; what the skin owns is the ink, and that is --pw-dither-ink.
 *
 * The selector list is bevel.css's derivation list, copied, and a test holds
 * every treatment to the same one: a surface that reaches the slot in one skin
 * and not another is flat in exactly one skin with nothing reporting it.
 */

@layer pw.treatment {
  [data-skin="paper"] :where(.pw-button,
         .pw-toggle,
         .pw-tab,
         .pw-panel,
         .pw-badge,
         .pw-slider-track,
         .pw-slider-thumb,
         .pw-lcd,
         .pw-tab-panel,
         .pw-title-button,
         .pw-window,
         .pw-transport-button,
         .pw-progress,
         .pw-eq,
         .pw-menu,
         .pw-list,
         .pw-checkbox,
         .pw-radio,
         .pw-select,
         .pw-select-list,
         .pw-select-button,
         .pw-input,
         .pw-textarea,
         .pw-switch,
         .pw-switch-thumb,
         .pw-tooltip,
         .pw-visualiser,
         .pw-raised,
         .pw-sunken) {
    /* One line, both states. See the header. */
    --pw-shadow-raised: inset 0 0 0 1px var(--pw-bevel-boundary);
    --pw-shadow-sunken: inset 0 0 0 1px var(--pw-bevel-boundary);

    /* And the depth, as a screen. Both states on the same 4px pitch, so they
       read as one process at two densities rather than as two patterns.

       20% ink raised and 2% sunken. The first version was 38% and 7%, which
       is not a screen: at 38% the dots nearly touch, they stay legible AS
       dots at reading size, and type sits on a field of them. A halftone is
       supposed to resolve into a tone.

       The depth gap is about 1.09:1 in composited tone, which is subtle by
       design and is not carrying anything on its own. The 1px boundary above
       identifies the control, and every state that means something says so
       twice: the toggle's on state changes its ground and its weight as well
       as its elevation. */
    --pw-fill-raised: radial-gradient(var(--pw-dither-ink) 1px, transparent 1.1px);
    --pw-fill-sunken: radial-gradient(var(--pw-dither-ink) 0.35px, transparent 0.45px);
    --pw-fill-size: 4px 4px;
  }
}

/* The scrollbar is a surface treatment on boxes other files drew, and it
   belongs to no one component: five of them scroll. */
/**
 * THE SCROLLBAR, as a slot rather than as whatever the host OS ships.
 *
 * Five surfaces in this kit scroll: a list, a panel body (which is what a
 * dialog scrolls), a window body, an open select, and the equaliser sideways. Every one of them showed the
 * operating system's own scrollbar, in all three skins, which is the single
 * loudest place the skin stopped at the edge of the component. A bevelled
 * chrome panel with a macOS overlay bar down its side is not a period
 * interface, and a printed sheet with one is not a printed sheet.
 *
 * ── Why a treatment and not a component ──────────────────────────────────
 * It fills slots on surfaces other files drew, which is what pw.treatment is
 * for, and it belongs to no single component: putting it in list.css and
 * dialog.css and window.css is the duplication this repo keeps paying for.
 *
 * ── Why the standard properties and not ::-webkit-scrollbar ──────────────
 * `scrollbar-width` and `scrollbar-color` are the CSS Scrollbars spec and work
 * in Firefox and in Chromium 121+. The ::-webkit- pseudo-elements are richer
 * (you can bevel the thumb, which is very tempting for the chrome skin) and
 * they are a non-standard prefix Firefox will never take, so a kit that drew
 * its period scrollbar that way would have a period scrollbar in one engine
 * and the host OS's in another. Two scrollbars is worse than one.
 *
 * So the skin gets a width and two colours, and that is the whole slot. What
 * that cannot express is a bevel on the thumb. It is written down here rather
 * than worked around, because the workaround is the prefix.
 *
 * ── Why :where() ─────────────────────────────────────────────────────────
 * Zero specificity, so a consumer takes it back with one class. The same
 * reason reset.css uses it for the focus ring list.
 */

@layer pw.treatment {
  :where(.pw-list, .pw-panel-body, .pw-window-body, .pw-select-list, .pw-eq) {
    scrollbar-width: var(--pw-scrollbar-width);
    scrollbar-color: var(--pw-scrollbar-thumb) var(--pw-scrollbar-track);
  }
}

/**
 * ICON.
 *
 * One 16x16 sheet, stepped by whole pixels, applied as a mask so the ink is
 * `currentColor`. That last part is the whole reason this is a mask rather
 * than a background image: an icon inside a button takes the button's colour,
 * including its disabled colour and its primary-content colour, without the
 * component knowing an icon is there. A coloured PNG would need one file per
 * state per theme.
 *
 * ── Forced colors needs a special case, and this file used to claim it did
 *    not ────────────────────────────────────────────────────────────────────
 * The claim was that the system paints the text colour and the icon is the
 * text colour. It is not. background-color is one of the properties forced
 * colors overrides and mask-image is not, so the mask survives and gets
 * painted in the forced BACKGROUND colour: measured, .pw-icon computed
 * rgb(255,255,255) while its host's color was rgb(0,0,0). White ink on a
 * white canvas. Every icon in the kit was invisible to exactly the user the
 * mode exists for, and the comment above the code said it was fine.
 *
 * System colour keywords are exempt from the forcing, so the fix is to name
 * them per context. forced-color-adjust: none is the wrong tool here: it
 * would preserve the authored colour, which is the thing that is wrong.
 *
 * ── The scale is a whole number, and the size does not follow density ─────
 * The sheet is a pixel grid. A fractional scale puts cell edges between
 * device pixels, which tears a glyph rather than softening it, so
 * --pw-icon-scale is 1 or 2 and never 1.5.
 *
 * That is also why the icon stays 16px in compact density while the control
 * around it drops from 44 to 32. The obvious move, scaling the icon by the
 * same ratio, would be 0.727 of the grid. A 16px mark inside a 32px control
 * is a proportion the whole 90s toolbar canon already used, so this costs
 * nothing worth having.
 */

@layer pw.components {
  :root,
  [data-skin="chrome"],
  [data-theme="light"] {
    --pw-icon-w: 16px;
    --pw-icon-h: 16px;
    --pw-icon-cols: 8;
    --pw-icon-rows: 4;
    --pw-icon-scale: 1;
    /* --pw-icon-sheet is NOT here, and that is the fix rather than an
       omission. It lived in this block, which is pw.components, and a skin
       declares its knobs in pw.tokens. pw.components sorts later, so this
       file won every time and a skin pointing the token at its own sheet was
       silently ignored: the token had been documented as a skin's to set
       since it was written and could never be set. It is a skin knob
       now, declared by each skin file the way every other knob is. */
  }

  .pw-icon {
    display: inline-block;
    flex: 0 0 auto;
    /* Set per icon by the component, as plain integers. Never a px offset:
       taste-check's treatments check flags a px literal in markup, and it
       would be right, because every pixel dimension belongs in here. */
    --pw-icon-x: 0;
    --pw-icon-y: 0;
    /* Empty cell columns either side of this glyph's ink, set per icon by the
       component. Zero here means "no correction", which is the right answer
       for every icon that is not sitting inline beside a word. Declared rather
       than left to a fallback so the gate can see them: a var() with no
       declaration anywhere is the cheapest bug in this project to introduce
       and it renders as nothing at all. */
    --pw-icon-ink-l: 0;
    --pw-icon-ink-r: 0;
    /* And the switch that spends them. 0 here, so the correction is off
       everywhere until a context asks for it.

       It has to be opt-in, which is the part worth writing down. Subtracting
       the bearing is right wherever an icon sits inline beside a word and the
       gap between them is meant to look even: without it the badge's check
       measured 10px of inset against the exclamation's 15 and read as
       inconsistent padding, which it was, just not in the padding. It is
       WRONG everywhere else in this kit, and the wrongness is not subtle. A
       menu's check and a list's play marker sit in a fixed gutter, so pulling
       them in by their own ink moves each one a different distance and the
       column stops being a column. An icon-only control centres its mark, and
       the two bearings differ, so subtracting both shifts it off centre. A
       collapsible's chevron rotates, and a bearing describes the ink of a
       glyph that is no longer pointing that way, which is the same trap
       spinner.tsx documents for its own frames.

       So the arithmetic lives here, once, and the answer to "does this
       context want it" is one custom property rather than two copies of a
       calc(). A consumer wrapping an icon and a label in their own component
       sets --pw-icon-bearing: 1 on the wrapper and gets what the badge gets;
       before this it was two lines of calc() copied out of badge.css, which
       is why exactly one selector in the kit had them.

       The default is declared in structural.css and NOT here, which is the
       opposite of what the two ink tokens above do and is not an oversight.
       A custom property declared on this element wins over the same property
       inherited from an ancestor, so `--pw-icon-bearing: 0` sitting here beat
       every context that switched it on: the badge asked for the correction,
       the icon overruled it, and the marks went straight back to 10px of
       inset against the exclamation's 15. A switch has to live above the
       thing it switches. */
    margin-left: calc(var(--pw-icon-ink-l) * -1px * var(--pw-icon-scale) * var(--pw-icon-bearing));
    margin-right: calc(var(--pw-icon-ink-r) * -1px * var(--pw-icon-scale) * var(--pw-icon-bearing));
    width: calc(var(--pw-icon-w) * var(--pw-icon-scale));
    height: calc(var(--pw-icon-h) * var(--pw-icon-scale));
    background-color: currentColor;
    mask-repeat: no-repeat;
    -webkit-mask-repeat: no-repeat;
    mask-image: var(--pw-icon-sheet);
    -webkit-mask-image: var(--pw-icon-sheet);
    mask-size:
      calc(var(--pw-icon-cols) * var(--pw-icon-w) * var(--pw-icon-scale))
      calc(var(--pw-icon-rows) * var(--pw-icon-h) * var(--pw-icon-scale));
    -webkit-mask-size:
      calc(var(--pw-icon-cols) * var(--pw-icon-w) * var(--pw-icon-scale))
      calc(var(--pw-icon-rows) * var(--pw-icon-h) * var(--pw-icon-scale));
    mask-position:
      calc(var(--pw-icon-x) * var(--pw-icon-w) * var(--pw-icon-scale) * -1)
      calc(var(--pw-icon-y) * var(--pw-icon-h) * var(--pw-icon-scale) * -1);
    -webkit-mask-position:
      calc(var(--pw-icon-x) * var(--pw-icon-w) * var(--pw-icon-scale) * -1)
      calc(var(--pw-icon-y) * var(--pw-icon-h) * var(--pw-icon-scale) * -1);
  }

  /* An icon on its own inside a control is the control's whole label, so the
     control needs an accessible name from somewhere else. The Icon component
     enforces that; this is here for anyone writing the markup by hand. */
  .pw-icon[data-standalone] { margin: 0 auto; }

  /* ── The spinner ─────────────────────────────────────────────────────────
     Eight cells on the sheet's last row, stepped. The house rule is that only
     transform and opacity animate, and this animates mask-position, so it is
     a deliberate deviation rather than an oversight.

     The reason the rule exists is compositing cost, and the alternative here
     costs more than it saves: driving this with a transform means the sheet
     on an inner element translated inside an outer one with overflow: hidden,
     which is two elements, a clip this kit has refused everywhere else, and
     the same number of paints. What happens is eight discrete
     repaints of a 16x16 box per revolution, roughly ten a second. Rotating a
     single glyph with transform WOULD obey the rule and is the thing that is
     genuinely wrong: 45 degrees is not exact on a square lattice, so it
     resamples, and resampled pixel art tears.

     It reads the frames off the row the component already set rather than
     naming row three here, so the animation cannot drift from the generated
     index when the sheet grows again. */
  .pw-spinner { display: inline-flex; align-items: center; }

  .pw-spinner .pw-icon {
    animation: pw-spin var(--pw-duration-spin) steps(8, end) infinite;
  }

  @keyframes pw-spin {
    from {
      mask-position: 0 calc(var(--pw-icon-y) * var(--pw-icon-h) * var(--pw-icon-scale) * -1);
      -webkit-mask-position: 0 calc(var(--pw-icon-y) * var(--pw-icon-h) * var(--pw-icon-scale) * -1);
    }

    to {
      mask-position:
        calc(var(--pw-icon-cols) * var(--pw-icon-w) * var(--pw-icon-scale) * -1)
        calc(var(--pw-icon-y) * var(--pw-icon-h) * var(--pw-icon-scale) * -1);
      -webkit-mask-position:
        calc(var(--pw-icon-cols) * var(--pw-icon-w) * var(--pw-icon-scale) * -1)
        calc(var(--pw-icon-y) * var(--pw-icon-h) * var(--pw-icon-scale) * -1);
    }
  }

  /* Slowed rather than stopped, and that is the one place this kit does not
     cut continuous motion outright. A stopped spinner does not read as a
     respected preference, it reads as a hung process, which is the single
     message a busy indicator must never send. The label beside it is the
     part that carries the meaning; this keeps the shape honest.

     The selector is repeated in full because a media block adds no
     specificity, so a shorter one here would lose to the rule above. */
  @media (prefers-reduced-motion: reduce) {
    .pw-spinner .pw-icon {
      animation: pw-spin var(--pw-duration-spin-reduced) steps(8, end) infinite;
    }
  }

  @media (forced-colors: active) {
    /* Named per context rather than once, because the system paints a link,
       a button and body text in three different colours and an icon has to
       follow the one it is sitting in. */
    .pw-icon { background-color: CanvasText; }
    a .pw-icon { background-color: LinkText; }
    button .pw-icon,
    [role="button"] .pw-icon { background-color: ButtonText; }
  }
}

/* Generated: one rule per icon name, so hand-written markup can say
   data-icon="check" instead of carrying the sprite cell itself. */
/**
 * Icon name to sheet cell, as CSS.
 *
 * GENERATED from assets/icon-font.mjs by scripts/build-icons-index.mjs.
 * Do not edit by hand: edit the font data and run `npm run generate`.
 * test/icons.test.mjs fails if this file has drifted from that source, and
 * check:cssom asks the browser whether each rule resolves to the cell the
 * index names, because a selector that matches nothing renders cell 0,0
 * rather than an error.
 *
 * This is the CSS-only half of what the Icon component does inline. See the
 * generator for why both exist and how they order against each other.
 */

@layer pw.components {
  .pw-icon[data-icon="play"] {
    --pw-icon-x: 0;
    --pw-icon-y: 0;
    --pw-icon-ink-l: 5;
    --pw-icon-ink-r: 4;
  }

  .pw-icon[data-icon="pause"] {
    --pw-icon-x: 1;
    --pw-icon-y: 0;
    --pw-icon-ink-l: 4;
    --pw-icon-ink-r: 4;
  }

  .pw-icon[data-icon="stop"] {
    --pw-icon-x: 2;
    --pw-icon-y: 0;
    --pw-icon-ink-l: 4;
    --pw-icon-ink-r: 4;
  }

  .pw-icon[data-icon="previous"] {
    --pw-icon-x: 3;
    --pw-icon-y: 0;
    --pw-icon-ink-l: 2;
    --pw-icon-ink-r: 4;
  }

  .pw-icon[data-icon="next"] {
    --pw-icon-x: 4;
    --pw-icon-y: 0;
    --pw-icon-ink-l: 4;
    --pw-icon-ink-r: 2;
  }

  .pw-icon[data-icon="eject"] {
    --pw-icon-x: 5;
    --pw-icon-y: 0;
    --pw-icon-ink-l: 2;
    --pw-icon-ink-r: 2;
  }

  .pw-icon[data-icon="close"] {
    --pw-icon-x: 6;
    --pw-icon-y: 0;
    --pw-icon-ink-l: 3;
    --pw-icon-ink-r: 3;
  }

  .pw-icon[data-icon="minimize"] {
    --pw-icon-x: 7;
    --pw-icon-y: 0;
    --pw-icon-ink-l: 3;
    --pw-icon-ink-r: 3;
  }

  .pw-icon[data-icon="maximize"] {
    --pw-icon-x: 0;
    --pw-icon-y: 1;
    --pw-icon-ink-l: 3;
    --pw-icon-ink-r: 3;
  }

  .pw-icon[data-icon="restore"] {
    --pw-icon-x: 1;
    --pw-icon-y: 1;
    --pw-icon-ink-l: 3;
    --pw-icon-ink-r: 3;
  }

  .pw-icon[data-icon="chevron-down"] {
    --pw-icon-x: 2;
    --pw-icon-y: 1;
    --pw-icon-ink-l: 2;
    --pw-icon-ink-r: 2;
  }

  .pw-icon[data-icon="chevron-up"] {
    --pw-icon-x: 3;
    --pw-icon-y: 1;
    --pw-icon-ink-l: 2;
    --pw-icon-ink-r: 2;
  }

  .pw-icon[data-icon="chevron-right"] {
    --pw-icon-x: 4;
    --pw-icon-y: 1;
    --pw-icon-ink-l: 4;
    --pw-icon-ink-r: 5;
  }

  .pw-icon[data-icon="chevron-left"] {
    --pw-icon-x: 5;
    --pw-icon-y: 1;
    --pw-icon-ink-l: 5;
    --pw-icon-ink-r: 4;
  }

  .pw-icon[data-icon="check"] {
    --pw-icon-x: 6;
    --pw-icon-y: 1;
    --pw-icon-ink-l: 2;
    --pw-icon-ink-r: 2;
  }

  .pw-icon[data-icon="exclamation"] {
    --pw-icon-x: 7;
    --pw-icon-y: 1;
    --pw-icon-ink-l: 7;
    --pw-icon-ink-r: 7;
  }

  .pw-icon[data-icon="dot"] {
    --pw-icon-x: 0;
    --pw-icon-y: 2;
    --pw-icon-ink-l: 5;
    --pw-icon-ink-r: 5;
  }

  .pw-icon[data-icon="minus"] {
    --pw-icon-x: 1;
    --pw-icon-y: 2;
    --pw-icon-ink-l: 3;
    --pw-icon-ink-r: 3;
  }

  .pw-icon[data-icon="plus"] {
    --pw-icon-x: 2;
    --pw-icon-y: 2;
    --pw-icon-ink-l: 3;
    --pw-icon-ink-r: 3;
  }

  .pw-icon[data-icon="caret-down"] {
    --pw-icon-x: 3;
    --pw-icon-y: 2;
    --pw-icon-ink-l: 3;
    --pw-icon-ink-r: 3;
  }

  .pw-icon[data-icon="search"] {
    --pw-icon-x: 4;
    --pw-icon-y: 2;
    --pw-icon-ink-l: 3;
    --pw-icon-ink-r: 2;
  }

  .pw-icon[data-icon="info"] {
    --pw-icon-x: 5;
    --pw-icon-y: 2;
    --pw-icon-ink-l: 7;
    --pw-icon-ink-r: 7;
  }

  .pw-icon[data-icon="question"] {
    --pw-icon-x: 6;
    --pw-icon-y: 2;
    --pw-icon-ink-l: 4;
    --pw-icon-ink-r: 4;
  }

  .pw-icon[data-icon="ellipsis"] {
    --pw-icon-x: 7;
    --pw-icon-y: 2;
    --pw-icon-ink-l: 3;
    --pw-icon-ink-r: 3;
  }

  .pw-icon[data-icon="spinner-1"] {
    --pw-icon-x: 0;
    --pw-icon-y: 3;
    --pw-icon-ink-l: 7;
    --pw-icon-ink-r: 2;
  }

  .pw-icon[data-icon="spinner-2"] {
    --pw-icon-x: 1;
    --pw-icon-y: 3;
    --pw-icon-ink-l: 4;
    --pw-icon-ink-r: 2;
  }

  .pw-icon[data-icon="spinner-3"] {
    --pw-icon-x: 2;
    --pw-icon-y: 3;
    --pw-icon-ink-l: 2;
    --pw-icon-ink-r: 2;
  }

  .pw-icon[data-icon="spinner-4"] {
    --pw-icon-x: 3;
    --pw-icon-y: 3;
    --pw-icon-ink-l: 2;
    --pw-icon-ink-r: 4;
  }

  .pw-icon[data-icon="spinner-5"] {
    --pw-icon-x: 4;
    --pw-icon-y: 3;
    --pw-icon-ink-l: 2;
    --pw-icon-ink-r: 7;
  }

  .pw-icon[data-icon="spinner-6"] {
    --pw-icon-x: 5;
    --pw-icon-y: 3;
    --pw-icon-ink-l: 2;
    --pw-icon-ink-r: 4;
  }

  .pw-icon[data-icon="spinner-7"] {
    --pw-icon-x: 6;
    --pw-icon-y: 3;
    --pw-icon-ink-l: 2;
    --pw-icon-ink-r: 2;
  }

  .pw-icon[data-icon="spinner-8"] {
    --pw-icon-x: 7;
    --pw-icon-y: 3;
    --pw-icon-ink-l: 4;
    --pw-icon-ink-r: 2;
  }

  .pw-spinner .pw-icon {
    --pw-icon-ink-l: 2;
    --pw-icon-ink-r: 2;
  }
}

/**
 * BUTTON.
 *
 * Radix ships no Button primitive. shadcn uses Slot for asChild and nothing
 * else, and so does this. Worth stating plainly, because a reader who assumes
 * @radix-ui/react-button exists will spend an afternoon on it.
 */

@layer pw.components {
  .pw-button {
    --pw-elev: var(--pw-shadow-raised);
    --pw-elev-fill: var(--pw-fill-raised);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--pw-control-gap);
    min-height: var(--pw-control-h);
    min-width: var(--pw-control-min-w);
    padding: 0 var(--pw-control-pad-x);
    /* transform only. The bevel flip is not transitioned and does not want
       to be: box-shadow is a paint property, and a real bevelled button
       snapped rather than eased. */
    transition: transform var(--pw-duration-press) var(--pw-ease-press),
                opacity var(--pw-duration-hover) var(--pw-ease-state);
  }

  /* Hover moves opacity, so the state is never carried by colour alone. */
  .pw-button:hover { opacity: 0.92; }

  .pw-button:active {
    --pw-elev: var(--pw-shadow-sunken);
    --pw-elev-fill: var(--pw-fill-sunken);
    transform: translateY(var(--pw-press-travel));
    opacity: 1;
  }

  .pw-button[data-variant="primary"] {
    background-color: var(--pw-color-primary);
    color: var(--pw-color-primary-content);
  }

  .pw-button:disabled,
  .pw-button[aria-disabled="true"] {
    color: var(--pw-color-disabled-content);
    cursor: default;
  }
  /* Disabled stops moving as well as dimming. Contrast alone does not carry
     the state here (3.85:1 is quiet, not absent), so the absent press and
     the absent hover carry the rest of it. */
  .pw-button:disabled:hover,
  .pw-button[aria-disabled="true"]:hover { opacity: 1; }
  .pw-button:disabled:active,
  .pw-button[aria-disabled="true"]:active { transform: none; }

  /* Reduced motion cuts the duration and keeps the state change, because the
     1px drop IS the affordance here and removing it would leave a 0.92
     opacity shift doing the work alone.

     Both selectors are repeated verbatim from above. A @media block adds no
     specificity, so a bare `.pw-button { transform: none }` at (0,1,0) would
     lose to `.pw-button:active` at (0,2,0) no matter where it sat in the
     file, and this whole block would silently do nothing while reading as
     correct. */
  @media (prefers-reduced-motion: reduce) {
    .pw-button { transition-duration: 1ms; }
    .pw-button:active { transform: translateY(var(--pw-press-travel)); }
  }
}

/**
 * TOGGLE.
 *
 * The difference from a button is that :active is momentary and
 * [data-state="on"] is not. Radix gives the second for free.
 */

@layer pw.components {
  .pw-toggle {
    --pw-elev: var(--pw-shadow-raised);
    --pw-elev-fill: var(--pw-fill-raised);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--pw-control-gap);
    min-height: var(--pw-control-h);
    min-width: var(--pw-control-h);
    padding: 0 var(--pw-control-pad-x);
    transition: transform var(--pw-duration-press) var(--pw-ease-press),
                opacity var(--pw-duration-hover) var(--pw-ease-state);
  }

  .pw-toggle:hover { opacity: 0.92; }

  /* On is a persistent sunken state, plus a weight shift. The bevel alone is
     a shape cue, and a shape cue at 2px is exactly what a low-vision reader
     at 200% zoom can miss. The weight is the second, non-shape signal, and
     neither of them is colour. */
  /* Both attributes, because the CSS was keyed entirely off data-state and
     the accessibility tree entirely off aria-pressed or aria-checked, with
     nothing binding them. Three spellings because a standalone toggle is a
     button that is pressed and a toggle inside a group is a radio that is
     checked, and the skin has no business caring which. Radix emits both, so this holds under Radix; the kit also ships as
     standalone CSS, and a consumer wiring it by hand got a button announced
     as pressed that rendered completely unpressed. The kit already pairs
     :disabled with [aria-disabled="true"] for the same reason. */
  .pw-toggle[data-state="on"],
  .pw-toggle[aria-pressed="true"],
  .pw-toggle[aria-checked="true"] {
    --pw-elev: var(--pw-shadow-sunken);
    --pw-elev-fill: var(--pw-fill-sunken);
    background-color: var(--pw-color-base-300);
    font-weight: 600;
  }

  .pw-toggle:active { transform: translateY(var(--pw-press-travel)); }

  /* A segmented control: several toggles as one choice. Gapped rather than
     joined, because two bevelled buttons sharing an edge draw that edge
     twice and it reads as a seam. */
  .pw-toggle-group {
    display: inline-flex;
    align-items: stretch;
    gap: var(--pw-space-3xs);
  }

  /* No cast shadow on a segment, and this is a set-level rule rather than a
     per-segment tweak.

     A raised surface casts and a sunken one does not, which is right for a
     button on its own and wrong for a row that is one choice. The selected
     segment is sunken so it casts nothing, every other segment is raised so
     it casts 0 1px 2px, and the row then has two different bottom edges.
     Measured: the raised segments paint two pixels taller than the selected
     one. Two pixels on a 44px control is 4.5% and reads as nothing; on a
     32px control in compact density it is 6.25% and reads as a row that does
     not line up, which is where it was reported from.

     Dropping the cast rather than adding one to the selected segment is the
     half that keeps the physics: a hole in a surface does not cast a shadow.
     What the segments lose is 1px of lift they did not need, since the state
     is carried by the bevel direction and the weight on the inside. */
  .pw-toggle-group .pw-toggle { --pw-shadow-outer: 0 0 0 0 transparent; }

  .pw-toggle:disabled,
  .pw-toggle[aria-disabled="true"] {
    color: var(--pw-color-disabled-content);
    cursor: default;
  }

  /* In forced colors the bevel is stripped and both faces force to Canvas,
     so two of the three cues the on-state relies on are gone and font-weight
     is left carrying it alone. */
  @media (forced-colors: active) {
    .pw-toggle[data-state="on"],
    .pw-toggle[aria-pressed="true"],
    .pw-toggle[aria-checked="true"] {
      background-color: Highlight;
      color: HighlightText;
      forced-color-adjust: none;
    }
  }

  @media (prefers-reduced-motion: reduce) {
    .pw-toggle { transition-duration: 1ms; }
    .pw-toggle:active { transform: translateY(var(--pw-press-travel)); }
  }
}

/**
 * TABS.
 *
 * The one detail that makes a tab strip read as tabs rather than as a row of
 * buttons: the active trigger's bottom edge is suppressed so it fuses with
 * the panel below it. Without that the active tab is a pressed button that
 * happens to sit above a box.
 */

@layer pw.components {
  /* A stacking context, so the fuse below is contained.
     The active trigger and the panel use z-index to decide which of their
     two overlapping edges is visible. Without a context those numbers are
     global, and .pw-tab-panel { z-index: 1 } was painting over a portalled
     modal dialog, which has no z-index of its own and therefore auto.
     Containing them here means the fuse cannot reach outside the group no
     matter what a consumer's page does. */
  .pw-tabs {
    position: relative;
    z-index: 0;
  }

  .pw-tab-list {
    display: flex;
    gap: var(--pw-space-3xs);
    /* The strip itself is flat. A bevelled container around bevelled
       triggers is two frames deep and reads as a mistake. */
    background: none;
    border: 0;
  }

  .pw-tab {
    --pw-elev: var(--pw-shadow-raised);
    --pw-elev-fill: var(--pw-fill-raised);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: var(--pw-control-h);
    padding: 0 var(--pw-control-pad-x);
    /* The bottom corners stay square so the fuse below has a straight edge
       to meet. */
    border-radius: var(--pw-radius-control) var(--pw-radius-control) 0 0;
    transition: transform var(--pw-duration-press) var(--pw-ease-press),
                opacity var(--pw-duration-hover) var(--pw-ease-state);
  }

  .pw-tab:hover { opacity: 0.92; }

  /* Both attributes. See the note in toggle.css. */
  .pw-tab[data-state="active"],
  .pw-tab[aria-selected="true"] {
    --pw-elev: var(--pw-shadow-sunken);
    --pw-elev-fill: var(--pw-fill-sunken);
    background-color: var(--pw-color-base-200);
    font-weight: 600;
    /* The fuse: the trigger drops a pixel and then overlaps the panel by the
       same pixel, so its own bottom edge lands underneath the panel's top
       edge instead of beside it. z-index decides which of the two overlapping
       edges is visible, and it has to be the panel's.

       The obvious version of this was border-bottom-color: transparent, and
       it stopped working the moment the frame became a shadow rather than a
       border. Overlap does not care which one draws the edge. */
    transform: translateY(var(--pw-press-travel));
    margin-bottom: calc(var(--pw-press-travel) * -2);
    position: relative;
    z-index: 0;
  }

  /* An unselected tab is raised, so it has a sink available and should use
     it: it is the one control in this group that was genuinely missing a
     press. The selected tab is excluded because it is already translated by
     the fuse above, and :active would put a second pixel on top of the one
     that lands its bottom edge under the panel. */
  .pw-tab:not([data-state="active"]):not([aria-selected="true"]):active {
    --pw-elev: var(--pw-shadow-sunken);
    --pw-elev-fill: var(--pw-fill-sunken);
    transform: translateY(var(--pw-press-travel));
    opacity: 1;
  }

  .pw-tab:disabled:active,
  .pw-tab[data-disabled]:active { transform: none; }

  .pw-tab-panel { position: relative; z-index: 1; }

  .pw-tab-panel {
    padding: var(--pw-panel-pad);
    background-color: var(--pw-color-base-200);
    --pw-elev: var(--pw-shadow-sunken);
    --pw-elev-fill: var(--pw-fill-sunken);
    border-radius: 0 var(--pw-radius-box) var(--pw-radius-box) var(--pw-radius-box);
    color: var(--pw-color-base-content);
    font-family: var(--pw-font-ui);
    font-size: var(--pw-control-text);
    line-height: var(--pw-leading-body);
  }

  @media (forced-colors: active) {
    .pw-tab[data-state="active"],
    .pw-tab[aria-selected="true"] {
      background-color: Highlight;
      color: HighlightText;
      forced-color-adjust: none;
    }
  }

  @media (prefers-reduced-motion: reduce) {
    .pw-tab { transition-duration: 1ms; }
    .pw-tab:not([data-state="active"]):not([aria-selected="true"]):active {
      transform: translateY(var(--pw-press-travel));
    }

    /* Repeated at its own (0,2,0), or the active tab would spring back up
       and un-fuse from the panel under reduced motion. Both selectors, or
       the aria-only spelling loses its transform here. */
    .pw-tab[data-state="active"],
    .pw-tab[aria-selected="true"] { transform: translateY(var(--pw-press-travel)); }
  }
}

/**
 * WINDOW CHROME.
 *
 * The title bar, its control cluster, and the chassis around them. Generalised
 * out of the dialog rather than written beside it: a dialog IS a window that
 * happens to be modal, and shipping two title bars would guarantee they drift.
 *
 * This plus the menu bar is what makes the kit read as an application rather
 * than as a web page wearing an application's colours.
 *
 * ── The title bar is not a drag handle, and does not pretend to be ────────
 * A real window's title bar moves the window. In a browser there is nothing
 * to move, so this one is inert: no grab cursor, no pointer handlers, no
 * :active state. A control that looks draggable and is not is the same
 * failure as a button that looks pressable and is not, which the states page
 * already had to be relabelled for.
 *
 * A consumer building an actual draggable window surface can add that; the
 * kit does not fake it.
 */

@layer pw.components {
  .pw-window {
    --pw-elev: var(--pw-shadow-raised);
    --pw-elev-fill: var(--pw-fill-raised);
    display: flex;
    flex-direction: column;
    border-radius: var(--pw-radius-box);
    clip-path: var(--pw-clip-box);
    font-family: var(--pw-font-ui);
    color: var(--pw-color-base-content);
  }

  .pw-title-bar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--pw-space-md);
    padding: var(--pw-space-2xs) var(--pw-space-2xs) var(--pw-space-2xs) var(--pw-space-md);
    /* Its own role, not primary. Luna's title bar is the accent and a 1997
       player's is the darkest thing on the chassis; one token cannot be both,
       and while it was primary the button fill and the bar were locked
       together. Both existing skins point titlebar at what they already had,
       so neither changed. */
    background-color: var(--pw-color-titlebar);
    color: var(--pw-color-titlebar-content);
    border-radius: var(--pw-radius-control) var(--pw-radius-control) 0 0;
  }

  .pw-title {
    margin: 0;
    /* Not --pw-control-text: a title is a heading, and a heading that shrank
       with the control density would stop being the largest thing in the bar
       at exactly the size where the bar has least room to spare. */
    font-size: var(--pw-text-subhead);
    line-height: var(--pw-leading-ui);
    /* A whole pixel. In a bar with no title buttons the title's line box IS
       the bar's height, and 19px at 1.2 is 22.8: every window body started
       at .8 of a pixel and every hairline under it smeared across two rows.
       The line above is the fallback where round() is not supported. */
    line-height: round(up, calc(var(--pw-text-subhead) * var(--pw-leading-ui)), 1px);
    font-weight: 600;
    /* A long title truncates rather than wrapping the bar to two lines and
       moving every control in it. */
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  /* The title bar's own ink, for whatever the toolbar slot holds. Metadata
     sets the body ink, which is right in a panel and invisible here: measured
     in the player's "playing", 1.00:1 on paper's bar and under 2.2:1 in five
     other looks. Two classes, so it wins over .pw-meta whatever the import
     order. */
  .pw-title-bar .pw-meta { color: inherit; }

  .pw-title-controls {
    display: flex;
    align-items: center;
    gap: var(--pw-space-3xs);
    flex: 0 0 auto;
  }

  .pw-title-button {
    --pw-elev: var(--pw-shadow-raised);
    --pw-elev-fill: var(--pw-fill-raised);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    /* Square, and the full control height, because it is a target before it
       is a glyph. The 16px mark inside it is small on purpose. */
    width: var(--pw-control-h);
    height: var(--pw-control-h);
    min-width: 0;
    padding: 0;
    border-radius: var(--pw-radius-control);
    background-color: var(--pw-color-base-200);
    color: var(--pw-color-base-content);
    transition: transform var(--pw-duration-press) var(--pw-ease-press),
                opacity var(--pw-duration-hover) var(--pw-ease-state);
  }
  .pw-title-button:hover { opacity: 0.92; }
  .pw-title-button:active {
    --pw-elev: var(--pw-shadow-sunken);
    --pw-elev-fill: var(--pw-fill-sunken);
    transform: translateY(var(--pw-press-travel));
    opacity: 1;
  }

  /* Grows to fill the window. A window given more height than its content,
     by a fixed height or a stretched grid cell, otherwise showed its chassis
     as a strip under the body. */
  .pw-window-body {
    flex-grow: 1;
    padding: var(--pw-panel-pad);
    font-size: var(--pw-text-ui);
    line-height: var(--pw-leading-body);
    background-color: var(--pw-color-base-200);
    border-radius: 0 0 var(--pw-radius-control) var(--pw-radius-control);
    overflow-y: auto;
  }

  @media (forced-colors: active) {
    /* The title bar's ground forces to Canvas and it carries no border, so
       without this the window loses its header and the controls stop being
       grouped with the title. */
    .pw-title-bar { border-bottom: var(--pw-border) solid ButtonBorder; }
  }

  @media (prefers-reduced-motion: reduce) {
    .pw-title-button { transition-duration: 1ms; }
    .pw-title-button:active { transform: translateY(var(--pw-press-travel)); }
  }
}

/**
 * DIALOG / PANEL.
 *
 * The title bar is the one place gloss is on by default. It is the Luna
 * titlebar, and it is the single element in the kit where the highlight is
 * the point rather than an option.
 */

@layer pw.components {
  .pw-overlay {
    position: fixed;
    inset: 0;
    z-index: var(--pw-z-overlay);
    background-color: var(--pw-color-scrim);
    /* A flat scrim, and that is a decision rather than an omission.
       backdrop-filter is the documented jank primitive (shadcn-ui #327,
       Chromium's GPU blur re-composite), it is worst on a fixed
       full-viewport element, and this is exactly that element. The blur is
       available and defaults to 0px; a consumer who wants the glass moment
       opts into it and pays for it knowingly. */
    animation: pw-fade var(--pw-duration-enter) var(--pw-ease-enter);
  }

  @supports (backdrop-filter: blur(1px)) {
    .pw-overlay[data-glass] { backdrop-filter: blur(var(--pw-glass-blur)); }
  }

  .pw-panel {
    --pw-elev: var(--pw-shadow-raised);
    --pw-elev-fill: var(--pw-fill-raised);
    position: fixed;
    z-index: var(--pw-z-panel);
    top: 50%;
    left: 50%;
    width: var(--pw-dialog-w);
    /* Percentages, not 100vw and 100vh.

       A fixed element's percentages resolve against the initial containing
       block, which is the viewport WITHOUT its scrollbars. 100vw includes
       them. On any page long enough to scroll, and with classic rather than
       overlay scrollbars, 100vw is about 15px wider than the space the panel
       has, so the gutter this is trying to reserve quietly shrinks
       to half. Headless Chromium uses overlay scrollbars and would never
       have shown it. */
    max-width: calc(100% - (var(--pw-space-lg) * 2));
    max-height: calc(100% - (var(--pw-space-xl) * 2));
    border-radius: var(--pw-radius-box);
    font-family: var(--pw-font-ui);
    /* The centring translate is part of the animated transform, so the two
       cannot be separate declarations. */
    animation: pw-panel-in var(--pw-duration-enter) var(--pw-ease-enter);
    transform: translate(-50%, -50%);
  }

  /* The dialog's header IS the window's. See css/components/window.css: a
     dialog is a window that happens to be modal, and two title bars would
     drift. What is left here is only what modality adds. */

  .pw-panel-body {
    padding: var(--pw-panel-pad);
    font-size: var(--pw-text-ui);
    line-height: var(--pw-leading-body);
    color: var(--pw-color-base-content);
    overflow-y: auto;
  }

  @keyframes pw-fade {
    from { opacity: 0; }
    to   { opacity: 1; }
  }

  /* transform and opacity only, and the -50% centring has to be carried
     through every frame or the panel flies in from the corner. */
  @keyframes pw-panel-in {
    from { opacity: 0; transform: translate(-50%, -50%) scale(0.98); }
    to   { opacity: 1; transform: translate(-50%, -50%) scale(1); }
  }

  @media (prefers-reduced-motion: reduce) {
    /* Both animating selectors repeated at their own specificity. The state
       change is kept and the duration goes to near zero, rather than the
       animation being removed: removing it would leave the panel with no
       from-state at all. */
    .pw-overlay { animation-duration: 1ms; }
    .pw-panel   { animation-duration: 1ms; }
  }
}

/**
 * SLIDER, single thumb, either orientation.
 *
 * ── Why there is no multi-thumb variant, measured rather than deferred ────
 * HANDOFF.md deferred multi-thumb on the grounds that the WAI-ARIA APG flags
 * unresolved gaps in its own reference pattern. Building the equaliser meant
 * finding out what that means, and the answer changes the design
 * rather than qualifying it.
 *
 * A multi-thumb slider is a RANGE. Its thumbs are the ends of one span and
 * they are ordered. Driven three thumbs at 20, 60 and 40 and pressed
 * ArrowRight sixty times on the middle one, Radix returns 20, 40, 100: it
 * re-sorted, so the thumb being driven moved to a different index and the
 * focused element's aria-valuenow now reads 40, a number belonging to a band
 * nobody touched. The identity of a thumb is not stable under interaction.
 *
 * For a price filter that is correct behaviour, because "the low end" and
 * "the high end" are what the thumbs mean. For an equaliser it is a bug with
 * no workaround: 3kHz is 3kHz forever, and driving it up must not silently
 * reassign it to 6kHz.
 *
 * So an equaliser is not a multi-thumb slider with an accessibility problem.
 * It is N independent sliders sharing a scale, and this file supports the
 * vertical orientation they need. See css/components/equalizer.css.
 *
 * The thumb never sinks. A sunken thumb reads as a grip that has been
 * dropped, not one that is being held, which is backwards. It scales instead.
 * Disabled is the exception, because a dropped grip is what disabled means.
 */

@layer pw.components {
  .pw-slider {
    position: relative;   /* the thumb is positioned against this */
    display: flex;
    align-items: center;
    /* The touch target is the row, not the 8px track. */
    min-height: var(--pw-control-h);
    width: 100%;
    touch-action: none;
  }

  .pw-slider-track {
    --pw-elev: var(--pw-shadow-sunken);
    --pw-elev-fill: var(--pw-fill-sunken);
    position: relative;
    flex-grow: 1;
    background-color: var(--pw-color-base-300);
    border-radius: var(--pw-radius-control);
  }
  /* Horizontal is the unscoped default and vertical is the override, rather
     than one rule per orientation. Scoping both meant a .pw-slider with no
     data-orientation matched neither, so its track had no height and the
     control rendered as a thumb floating in nothing. Radix always sets the
     attribute; a consumer writing the markup by hand does not know to, and
     the demo's own static page did not either. */
  .pw-slider-track { height: var(--pw-track-h); }
  .pw-slider[data-orientation="vertical"] .pw-slider-track {
    width: var(--pw-track-h);
    height: auto;
  }

  .pw-slider-range {
    position: absolute;
    background-color: var(--pw-color-range);
    border-radius: var(--pw-radius-control);
  }
  /* Radix sets the offset and the length inline, on the axis that varies.
     The cross axis is ours, and it has to be scoped per orientation or a
     vertical range inherits height: 100% and fills the whole track. */
  /* Inset from the track's edge by --pw-range-inset. The track draws its
     edge as an inset shadow, which paints under its children, so a range
     flush to the track covered the edge wherever it was and read as wider
     than its groove. Radix sets the main axis inline. */
  .pw-slider-range { top: var(--pw-range-inset); bottom: var(--pw-range-inset); }
  .pw-slider[data-orientation="vertical"] .pw-slider-range {
    left: var(--pw-range-inset);
    right: var(--pw-range-inset);
    /* Radix writes the vertical extent inline, so this only clears the
       horizontal default rather than setting anything. */
    height: auto;
  }

  /* ── Vertical, and the touch cost ────────────────────────────────────────
     THIS IS THE ONE COPY OF THIS ARGUMENT. equalizer.tsx points here.

     The cost is real and it is not solvable, so it is documented rather than
     hidden. A vertical slider captures vertical drag, and vertical drag is
     also how a page scrolls. The `touch-action: none` on .pw-slider forty
     lines up is what takes it, so a column of ten of these is a band of the
     page a finger cannot scroll through. There is no arrangement that gives
     both gestures to one region. Keep the group narrow, leave scrollable page
     either side of it, and say so in the docs.

     The rule is THIS KIT'S, which matters because both copies of this
     paragraph used to credit it to Radix and the credit was wrong. Radix's
     slider ships no touch-action at all; the only touch-action in the
     installed radix-ui is in react-toast. A rule attributed to a dependency is
     a rule nobody here thinks to change, and this one is ours to change: the
     vertical case below narrows it to pan-x for exactly that reason. */
  .pw-slider[data-orientation="vertical"] {
    /* pan-x, not none.
       A vertical slider needs the vertical gesture and nothing else, so
       handing horizontal panning back to the browser costs it nothing and
       buys the container the ability to scroll sideways through it. That
       matters because ten of these is wider than a phone, and the alternative
       is a horizontal scroller a finger can only grab in the gaps between
       bands.

       This is the mirror of the trap on a horizontal rail, where pan-y is
       what kills the sideways gesture. The rule is the same in both
       directions: give the browser the axis the control does not use. */
    touch-action: pan-x;
    flex-direction: column;
    width: var(--pw-control-h);
    min-width: var(--pw-control-h);
    height: 100%;
    min-height: 0;
  }

  .pw-slider-thumb {
    --pw-elev: var(--pw-shadow-raised);
    --pw-elev-fill: var(--pw-fill-raised);
    display: block;
    /* Static, deliberately. Radix does not position the thumb, it wraps it in
       its own absolutely positioned span and moves that. An absolute on the
       thumb as well collapses that wrapper to 0x0, so the wrapper's transform
       has nothing to offset and the grip lands exactly half a thumb width off
       the value it points at. Measured: 11px, on a 22px thumb.

       position: relative rather than static, only so the hit expander has
       something to anchor to. It does not move the box. */
    position: relative;
    width: var(--pw-thumb-w);
    height: var(--pw-thumb-h);
    background-color: var(--pw-color-base-200);
    border-radius: var(--pw-radius-control);
    transition: transform var(--pw-duration-press) var(--pw-ease-press);
  }

  /* The hit area, larger than the grip and invisible. Centred on the grip so
     growing it does not move where the control appears to be. */
  .pw-slider-thumb::after {
    content: '';
    position: absolute;
    top: 50%;
    left: 50%;
    width: var(--pw-thumb-hit);
    height: var(--pw-thumb-hit);
    transform: translate(-50%, -50%);
  }

  /* A vertical grip is the horizontal one turned, so its dimensions swap.
     Reusing the horizontal ones would give a 22 wide by 28 tall grip lying
     across an 8px column, which overhangs the track by seven pixels a side. */
  .pw-slider[data-orientation="vertical"] .pw-slider-thumb {
    width: var(--pw-thumb-h);
    height: var(--pw-thumb-w);
  }

  /* The grab. Scale rather than translate, because the thumb has to stay on
     the value it is pointing at while it responds. */
  .pw-slider-thumb:active { transform: scale(0.96); }

  /* Disabled restyled the range and nothing else, so the enabled and
     disabled thumbs were byte-identical: same face, same bevel, same
     opacity, same cursor. The only difference on the whole control was the
     fill colour, which is colour as the sole affordance. button.css already
     gets this right by killing the press travel and the hover. */
  .pw-slider[data-disabled] .pw-slider-range {
    background-color: var(--pw-color-disabled-content);
  }
  .pw-slider[data-disabled] .pw-slider-thumb {
    /* Sunken, not flat.
       Flat worked, and it left the chrome vocabulary: a surface with no
       bevel at all is not a state of this skin, it is an absence of it, and
       it read as a component from a different kit.

       Sunken says the right thing, and the reasoning was already written
       down in this file in the opposite direction. The note above the grab
       state says never to sink the thumb on :active, because a sunken thumb
       reads as a dropped grip rather than a held one. That is exactly what a
       disabled slider is. The shape rejected for the live state is the
       correct meaning for the dead one. */
    --pw-elev: var(--pw-shadow-sunken);
    --pw-elev-fill: var(--pw-fill-sunken);
    background-color: var(--pw-color-base-300);
    cursor: default;
  }
  .pw-slider[data-disabled] .pw-slider-thumb:active { transform: none; }

  /* The range is neither in the elevation list nor the forced-colors border
     list, so in forced colors it becomes the same Canvas as the track and
     the slider stops reading as a level. The thumb still carries the value,
     which is why this is not as bad as the readout, but a slider whose fill
     is invisible is a marker on a rail. */
  @media (forced-colors: active) {
    .pw-slider-range { background-color: Highlight; forced-color-adjust: none; }
  }

  @media (prefers-reduced-motion: reduce) {
    .pw-slider-thumb { transition-duration: 1ms; }
    .pw-slider-thumb:active { transform: scale(0.96); }
  }
}

/**
 * TRANSPORT + SEEK.
 *
 * A toolbar of icon-only buttons, and a position bar.
 *
 * Two decisions shape what this file draws, and both are argued once, in
 * src/transport.tsx: why icon-only is allowed here and nowhere else, and why
 * a scrubbable position and a watch-only one are two components rather than
 * one prop. Read them there.
 *
 * They were argued here as well, at the same length in different words, which
 * is how a comment becomes a second source of truth: the two copies drift and
 * a reader has no way to tell which one the code kept. This file explains its
 * own selectors and leaves the rest to the component.
 */

@layer pw.components {
  .pw-transport {
    display: inline-flex;
    align-items: center;
    gap: var(--pw-space-3xs);
  }

  .pw-transport-button {
    --pw-elev: var(--pw-shadow-raised);
    --pw-elev-fill: var(--pw-fill-raised);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: var(--pw-control-h);
    height: var(--pw-control-h);
    min-width: 0;
    padding: 0;
    border-radius: var(--pw-radius-control);
    background-color: var(--pw-color-base-200);
    color: var(--pw-color-base-content);
    transition: transform var(--pw-duration-press) var(--pw-ease-press),
                opacity var(--pw-duration-hover) var(--pw-ease-state);
  }
  .pw-transport-button:hover { opacity: 0.92; }
  .pw-transport-button:active {
    --pw-elev: var(--pw-shadow-sunken);
    --pw-elev-fill: var(--pw-fill-sunken);
    transform: translateY(var(--pw-press-travel));
    opacity: 1;
  }
  .pw-transport-button:disabled,
  .pw-transport-button[aria-disabled="true"] {
    color: var(--pw-color-disabled-content);
    cursor: default;
  }
  .pw-transport-button:disabled:hover,
  .pw-transport-button[aria-disabled="true"]:hover { opacity: 1; }
  .pw-transport-button:disabled:active,
  .pw-transport-button[aria-disabled="true"]:active { transform: none; }

  /* The progress half of the seek pair. The slider half is the existing
     .pw-slider, unchanged: a scrubbable position IS a slider and does not
     need a second implementation. */
  .pw-progress {
    --pw-elev: var(--pw-shadow-sunken);
    --pw-elev-fill: var(--pw-fill-sunken);
    position: relative;
    display: block;
    width: 100%;
    height: var(--pw-track-h);
    background-color: var(--pw-color-base-300);
    border-radius: var(--pw-radius-control);
  }

  .pw-progress-fill {
    display: block;
    height: 100%;
    /* The track used to clip this with overflow: hidden, which put a clip on a
       shared-slot member. Rounding the fill itself keeps the corners and takes
       the clip off the surface that carries the elevation. */
    border-radius: inherit;
    background-color: var(--pw-color-range);
    /* Width, which is a layout property, would be the obvious way to animate
       this and would drop it off the compositor on every tick. transform on
       a full-width bar costs nothing and reads identically. */
    transform-origin: left center;
    transition: transform var(--pw-duration-base) var(--pw-ease-state);
  }

  @media (forced-colors: active) {
    .pw-progress-fill { background-color: Highlight; forced-color-adjust: none; }
  }

  @media (prefers-reduced-motion: reduce) {
    .pw-transport-button { transition-duration: 1ms; }
    .pw-transport-button:active { transform: translateY(var(--pw-press-travel)); }
    /* The fill still moves to the new value, it just stops easing there.
       Removing the transform would freeze the bar at zero. */
    .pw-progress-fill { transition-duration: 1ms; }
  }
}

/**
 * EQUALIZER.
 *
 * A group of independent vertical sliders sharing one scale, a zero line and
 * a frame. Not a multi-thumb slider: css/components/slider.css records the
 * measurement that rules that out, and it is a correctness argument rather
 * than an accessibility compromise.
 *
 * ── The zero line ────────────────────────────────────────────────────────
 * An equaliser's scale is signed, and the thing that makes it readable at a
 * glance is not the numbers, it is being able to see which bands are cut and
 * which are boosted without reading any. That is one rule across the well at
 * the zero position, and it is the reason this component exists at all
 * rather than being ten sliders a consumer arranges themselves.
 *
 * It is drawn on the well, behind the tracks, rather than per band, so it is
 * continuous. Ten separate one-pixel rules with nine gaps is not a line.
 */

@layer pw.components {
  .pw-eq {
    --pw-elev: var(--pw-shadow-sunken);
    --pw-elev-fill: var(--pw-fill-sunken);
    display: flex;
    align-items: stretch;
    gap: var(--pw-eq-gap);
    padding: var(--pw-space-md) var(--pw-space-sm);
    /* Ten bands at a 44px touch target is 476px, which is wider than a
       phone. Scrolling the group sideways is the only answer that keeps
       every band tappable: narrowing the columns to fit would put them under
       the target floor, and an equaliser that wraps to two rows is not an
       equaliser. The vertical sliders inside hand the horizontal axis back
       to the browser so this scrolls under a finger. */
    max-width: 100%;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    background-color: var(--pw-color-base-300);
    border-radius: var(--pw-radius-box);
    clip-path: var(--pw-clip-box);
  }

  .pw-eq-well {
    position: relative;
    display: flex;
    align-items: stretch;
    gap: var(--pw-eq-gap);
    height: var(--pw-eq-height);
  }

  /* The zero line. A pseudo-element on the well rather than a border on
     anything, so it spans the bands and the gaps between them alike.

     ── Which box the fraction is of ────────────────────────────────────────
     The well is taller than the fader travel, because each band stacks a
     slider and its frequency label. So a fraction of the WELL is not where
     zero is: measured at 160px well and 144px track, the rule drew at 80px
     while the thumb and the fill both put zero at 72px. Eight pixels, which
     is exactly half the label row plus its gap, and it read as a zero line
     that was not level with the faders sitting on it.

     The thumb and the fill were right and agreed with each other. Radix
     positions the thumb along the track, and .pw-eq-fill is inside the track,
     so both are fractions of the travel. Only the rule was measuring the
     wrong box, because it is the one thing drawn on the well.

     So take the label row back off before applying the fraction. It is the
     band's gap plus the label's own height, and --pw-eq-label-space in
     structural.css is exactly that, derived from the two tokens the layout
     below uses. Both move per skin and per density and this follows
     them. check:cssom measures the rule against the band on zero, under every
     skin, so this cannot drift back. */
  .pw-eq-well[data-zero]::before {
    content: '';
    position: absolute;
    left: 0;
    right: 0;
    /* Set inline as a unitless fraction by the component, because where zero
       sits depends on the range and only the component knows it. A fraction
       rather than a percentage so it can be applied to the travel here,
       rather than to whatever box it happens to be declared on. */
    top: calc((100% - var(--pw-eq-label-space)) * var(--pw-eq-zero, 0.5));
    height: var(--pw-border);
    background-color: var(--pw-bevel-boundary);
    opacity: 0.6;
    pointer-events: none;
  }

  /* The band's fill. Positioned by the component between the zero line and
     the value, because which way it travels depends on the sign and CSS
     cannot know it. */
  .pw-eq-fill {
    position: absolute;
    /* Off the track's edge, for the reason slider.css gives for the range. */
    left: var(--pw-range-inset);
    right: var(--pw-range-inset);
    background-color: var(--pw-color-range);
    border-radius: var(--pw-radius-control);
  }

  @media (forced-colors: active) {
    .pw-eq-fill { background-color: Highlight; forced-color-adjust: none; }
  }

  /* Thin along the travel. The 44px hit area is unaffected: it is the
     ::after expander in slider.css, which is centred on the grip and sized
     independently of it.

     The selector matches slider.css's own vertical rule rather than being
     shorter than it. Both live in the same layer, so the longer selector
     wins on specificity regardless of import order, and .pw-eq
     .pw-slider-thumb at (0,2,0) simply lost to (0,3,0) and did nothing. */
  .pw-eq .pw-slider[data-orientation="vertical"] .pw-slider-thumb {
    height: var(--pw-fader-h);
  }

  .pw-eq-band {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--pw-space-2xs);
    height: 100%;
  }

  .pw-eq-label {
    flex: 0 0 auto;
    font-family: var(--pw-font-mono);
    font-size: var(--pw-text-micro);
    line-height: 1;
    color: var(--pw-color-base-content);
    /* The frequency labels are the one place in the kit where a row of
       numbers has to align to a grid, so they are tabular and monospace. */
    font-variant-numeric: tabular-nums;
  }

  @media (forced-colors: active) {
    .pw-eq-well[data-zero]::before {
      background-color: CanvasText;
      opacity: 1;
      forced-color-adjust: none;
    }
  }
}

/**
 * MENU BAR.
 *
 * The strip under a title bar, and its dropdowns. This plus the window chrome
 * is what makes the kit read as an application rather than as a web page
 * wearing an application's colours.
 *
 * ── The bar is flat and the menus are raised ─────────────────────────────
 * Every other surface in this kit carries a bevel. The bar does not, and that
 * is the period behaviour rather than an omission: a menu bar is part of the
 * window's face, not a control sitting on it, and giving each trigger a
 * raised edge would turn File, Play and Options into five buttons in a row.
 * The trigger only takes a treatment when its menu is open, and then it
 * inverts rather than sinking, which is what Windows did and what
 * distinguishes "this menu is showing" from "this button is pressed".
 *
 * ── The separator is engraved, not drawn ─────────────────────────────────
 * A menu separator in this era is two lines: a dark one with a light one
 * directly under it, which reads as a groove cut into the face rather than a
 * rule painted on it. That is the same two-tone logic as the bevel, at one
 * pixel, so it uses the same tokens and re-pitches with the theme for free.
 */

@layer pw.components {
  .pw-menubar {
    display: flex;
    align-items: stretch;
    gap: var(--pw-space-3xs);
    padding: var(--pw-space-3xs) var(--pw-space-2xs);
    background-color: var(--pw-color-base-200);
    font-family: var(--pw-font-ui);
  }

  .pw-menubar-trigger {
    display: inline-flex;
    align-items: center;
    /* Menu labels are short and a 75px minimum would space them like a
       toolbar. The height still comes from the control scale, so the target
       holds even though the width does not. */
    min-width: 0;
    min-height: var(--pw-control-h);
    padding: 0 var(--pw-control-pad-x);
    border-radius: var(--pw-radius-control);
    background: none;
    color: var(--pw-color-base-content);
    font-size: var(--pw-control-text);
    transition: opacity var(--pw-duration-hover) var(--pw-ease-state);
  }
  .pw-menubar-trigger:hover { opacity: 0.72; }

  /* Open inverts. Not a sunken bevel: a pressed button and an open menu are
     different states and reading the same would be a lie about which. */
  .pw-menubar-trigger[data-state="open"] {
    background-color: var(--pw-color-primary);
    color: var(--pw-color-primary-content);
    opacity: 1;
  }

  .pw-menu {
    --pw-elev: var(--pw-shadow-raised);
    --pw-elev-fill: var(--pw-fill-raised);
    min-width: 12rem;
    padding: var(--pw-space-3xs) 0;
    background-color: var(--pw-color-base-200);
    border-radius: var(--pw-radius-control);
    clip-path: var(--pw-clip-control);
    font-family: var(--pw-font-ui);
    font-size: var(--pw-control-text);
    color: var(--pw-color-base-content);
    /* transform and opacity only, and the origin follows the trigger so the
       menu grows out of the thing that opened it rather than from its own
       middle. Radix supplies the origin. */
    animation: pw-menu-in var(--pw-duration-fast) var(--pw-ease-enter);
    transform-origin: var(--radix-menubar-content-transform-origin);
  }

  .pw-menu-item {
    display: flex;
    align-items: center;
    gap: var(--pw-control-gap);
    min-height: var(--pw-control-h);
    /* The left gutter is where a check or a radio dot lands. It is reserved
       on every item, checkable or not, because a menu whose text shifts
       sideways when one item happens to be checkable is a menu that moves
       under the pointer. */
    padding: 0 var(--pw-control-pad-x) 0 var(--pw-menu-gutter);
    position: relative;
    cursor: default;
    user-select: none;
  }

  /* Radix marks the item the keyboard or pointer is on with data-highlighted,
     which is one state for both. That is correct here: a menu has a single
     current item however you arrived at it. */
  .pw-menu-item[data-highlighted] {
    background-color: var(--pw-selected-bg);
    color: var(--pw-selected-content);
    outline: none;
  }

  /* The item the keyboard or pointer is on. The skin decides whether this paints anything: --pw-selected-mark
     is none by default, so this is an empty box on chrome and four corner
     brackets on cyber. */
    .pw-menu-item[data-highlighted]::before {
    content: '';
    position: absolute;
    inset: var(--pw-bracket-inset);
    pointer-events: none;
    background: var(--pw-selected-mark);
  }

  .pw-menu-item[data-disabled] {
    color: var(--pw-color-disabled-content);
  }
  .pw-menu-item[data-disabled][data-highlighted] {
    background: none;
    color: var(--pw-color-disabled-content);
  }

  .pw-menu-indicator {
    position: absolute;
    left: var(--pw-space-sm);
    display: inline-flex;
    align-items: center;
  }

  /* Right-aligned, and pushed there by margin rather than by justify-content,
     so an item with no shortcut does not centre its label. */
  .pw-menu-shortcut {
    margin-left: auto;
    padding-left: var(--pw-space-lg);
    font-size: var(--pw-text-micro);
    opacity: 0.7;
    font-variant-numeric: tabular-nums;
  }
  .pw-menu-item[data-highlighted] .pw-menu-shortcut { opacity: 0.85; }

  .pw-menu-label {
    padding: var(--pw-space-2xs) var(--pw-control-pad-x) var(--pw-space-2xs) var(--pw-menu-gutter);
    font-size: var(--pw-text-micro);
    opacity: 0.7;
  }

  /* Two lines, dark over light: a groove cut into the face rather than a rule
     painted on it. The same two-tone logic as the bevel, at one pixel. */
  .pw-menu-separator {
    height: 0;
    margin: var(--pw-space-2xs) var(--pw-space-sm);
    border-top: var(--pw-border) solid var(--pw-bevel-shade);
    border-bottom: var(--pw-border) solid var(--pw-bevel-light);
  }

  @keyframes pw-menu-in {
    from { opacity: 0; transform: scale(0.97); }
    to   { opacity: 1; transform: scale(1); }
  }

  @media (forced-colors: active) {
    .pw-menubar-trigger[data-state="open"],
    .pw-menu-item[data-highlighted] {
      background-color: Highlight;
      color: HighlightText;
      forced-color-adjust: none;
    }
    /* A groove drawn in two greys is two greys in forced colors. */
    .pw-menu-separator {
      border-top-color: CanvasText;
      border-bottom-color: Canvas;
    }
  }

  @media (prefers-reduced-motion: reduce) {
    .pw-menubar-trigger { transition-duration: 1ms; }
    .pw-menu { animation-duration: 1ms; }
  }
}

/**
 * LIST.
 *
 * A selectable list of rows: a playlist, a file list, a queue.
 *
 * ── Two states, not one ──────────────────────────────────────────────────
 * A playlist row can be selected and it can be the one playing, and they are
 * independent: you scroll a selection past the playing track all the time.
 * Collapsing them into one highlight is the mistake this component exists to
 * avoid, so selection inverts the row and playing marks it, and a row can
 * carry both at once.
 *
 * ── The marker is a mark, not a colour ───────────────────────────────────
 * Winamp's playlist colour-codes rows and that is the one thing from the
 * reference this kit does not take. Colour alone cannot say which row is
 * playing to a reader who cannot see it, and cannot say it at all in forced
 * colors. The playing row gets an icon in a reserved gutter and a weight
 * change, and the colour is the third signal rather than the only one.
 */

@layer pw.components {
  .pw-list {
    --pw-elev: var(--pw-shadow-sunken);
    --pw-elev-fill: var(--pw-fill-sunken);
    display: block;
    margin: 0;
    padding: 0;
    overflow-y: auto;
    max-height: var(--pw-list-h);
    background-color: var(--pw-color-base-300);
    border-radius: var(--pw-radius-control);
    font-family: var(--pw-font-ui);
    font-size: var(--pw-control-text);
    color: var(--pw-color-base-content);
    list-style: none;
  }

  .pw-list-item {
    display: flex;
    align-items: center;
    gap: var(--pw-control-gap);
    min-height: var(--pw-control-h);
    /* The gutter is reserved on every row, playing or not, for the same
       reason a menu reserves one: a list whose text shifts sideways when the
       track changes is a list that moves while you are reading it. */
    padding: 0 var(--pw-control-pad-x) 0 var(--pw-menu-gutter);
    position: relative;
    cursor: default;
    user-select: none;
  }

  /* Selection inverts. This is the only place in the kit where a full-width
     inversion is right: a row IS the control, so there is nothing smaller to
     mark. */
  .pw-list-item[aria-selected="true"] {
    background-color: var(--pw-selected-bg);
    color: var(--pw-selected-content);
  }

  /* The selected row. The skin decides whether this paints anything: --pw-selected-mark
     is none by default, so this is an empty box on chrome and four corner
     brackets on cyber. */
    .pw-list-item[aria-selected="true"]::before {
    content: '';
    position: absolute;
    inset: var(--pw-bracket-inset);
    pointer-events: none;
    background: var(--pw-selected-mark);
  }

  /* The row the keyboard is on, which is not necessarily the selected one.
     A quieter band, so moving through a list does not look like selecting
     every row on the way.

     Only while the list itself has visible focus, and that condition is not
     decoration. [data-active] is seeded at mount so aria-activedescendant has
     a row to point at from the first render, and painting it unconditionally
     put a 2px focus ring on a row of every list on the page before anyone had
     touched anything. reset.css states the opposite as this kit's own rule,
     three files away, and this was the one place breaking it.

     The condition sits on the container because the container is what takes
     focus: .pw-list carries tabindex and aria-activedescendant, and the rows
     are not focusable at all, so :focus-visible can never match a row. */
  .pw-list:focus-visible .pw-list-item[data-active] {
    outline: var(--pw-focus-width) solid var(--pw-color-focus);
    outline-offset: calc(var(--pw-focus-width) * -1);
  }
  .pw-list:focus-visible .pw-list-item[aria-selected="true"][data-active] {
    outline-color: var(--pw-color-focus-contrast);
  }

  .pw-list-marker {
    position: absolute;
    left: var(--pw-space-sm);
    display: inline-flex;
    align-items: center;
  }

  .pw-list-item[aria-current="true"] { font-weight: 600; }

  .pw-list-primary {
    flex: 1 1 auto;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  /* Right-aligned and tabular, because a column of durations that does not
     line up is a column of noise. */
  .pw-list-secondary {
    flex: 0 0 auto;
    padding-left: var(--pw-space-lg);
    font-variant-numeric: tabular-nums;
    opacity: 0.75;
  }
  .pw-list-item[aria-selected="true"] .pw-list-secondary { opacity: 0.85; }

  @media (forced-colors: active) {
    .pw-list-item[aria-selected="true"] {
      background-color: Highlight;
      color: HighlightText;
      forced-color-adjust: none;
    }
  }
}

/**
 * FORM CONTROLS: text field, textarea, checkbox, radio, select, label.
 *
 * The kit had none of these, which is a larger hole than it sounds: they are
 * the first thing anyone evaluating a design system looks for, and every peer
 * leads with them.
 *
 * ── The text field is the one that was still missing ─────────────────────
 * Three of the four shipped and the fourth did not, so the kit could label a
 * choice and could not take a name. Building an ordinary settings panel
 * against the published package found it in the first minute: five bare
 * <input>s, browser default, and on the dark cyber skin they were the
 * brightest thing on the page.
 *
 * There is no Radix primitive here and there is not meant to be. An input is
 * an input, its interaction is the platform's, and neither Radix nor shadcn
 * wraps one. That is what makes this the cheapest thing in the kit to have
 * been missing: no dependency, no ARIA to own, and it works with no React at
 * all, which is the half of the audience the architecture was built for.
 *
 * ── The radio is round, and that is a deliberate break ───────────────────
 * Everything else here is square at a 2px radius, and that rule is held
 * everywhere except this one control. The shape of a radio IS its meaning: it
 * is the only thing distinguishing "choose one of these" from "choose any of
 * these" before you have read a word or tried to click twice. A square radio
 * beside a square checkbox removes the distinction and leaves the behaviour
 * to be discovered.
 *
 * Windows drew them round from 3.1 onward and so does every platform since.
 * This is a convention doing semantic work rather than decoration, which is
 * the test for when the house rule bends.
 *
 * ── The combo box is sunken with a raised button ─────────────────────────
 * A period combo box is a sunken field with a separate raised drop button on
 * its right edge, not a flat box with an arrow floating in it. The two
 * treatments are the whole reason it reads as a control you can open rather
 * than a label with a decoration.
 */

@layer pw.components {
  /* ── The row a control sits in ──────────────────────────────────────── */
  .pw-field {
    display: inline-flex;
    align-items: center;
    gap: var(--pw-control-gap);
    min-height: var(--pw-control-h);
    font-family: var(--pw-font-ui);
    font-size: var(--pw-control-text);
    color: var(--pw-color-base-content);
  }

  .pw-label {
    /* The whole row is the target, not the 16px box, which is what makes a
       checkbox usable without aiming. */
    cursor: pointer;
    user-select: none;
  }
  .pw-field:has(:disabled) .pw-label,
  .pw-field[data-disabled] .pw-label {
    color: var(--pw-color-disabled-content);
    cursor: default;
  }

  /* ── Text field and textarea ────────────────────────────────────────── */
  .pw-input,
  .pw-textarea {
    /* A well, and the same well the combo box's field half is, because the two
       sit in the same form. A text field raised beside a sunken select would
       be two vocabularies in one row. */
    --pw-elev: var(--pw-shadow-sunken);
    --pw-elev-fill: var(--pw-fill-sunken);
    display: inline-block;
    min-width: var(--pw-control-min-w);
    /* The overflow gate runs at 320px and an <input> sizes itself from its
       size attribute rather than from its container, so without this it is
       about 170px wide whatever it has been put inside. */
    max-width: 100%;
    background-color: var(--pw-color-base-100);
    color: var(--pw-color-base-content);
    border-radius: var(--pw-radius-control);
    /* No clip-path. It is in reset.css's focus-ring list, and clip-path clips
       an outline. bevel.css opts every ringed control out; a line here would
       win it back, in a later layer, and take the ring off under any notched
       skin. Three components were doing exactly that. */
    cursor: text;
  }

  .pw-input {
    min-height: var(--pw-control-h);
    /* Horizontal only. A single-line input centres its own text in whatever
       height it is given, so vertical padding would only fight the min-height
       that makes the target 44px. */
    padding: 0 var(--pw-control-pad-x);
  }

  .pw-textarea {
    /* Two rows before anyone drags it. */
    min-height: calc(var(--pw-control-h) * 2);
    padding: var(--pw-space-2xs) var(--pw-control-pad-x);
    /* Vertical only: a horizontal drag takes it out of the column it was laid
       out in, and a form is a column. */
    resize: vertical;
    line-height: var(--pw-leading-ui);
  }

  /* The placeholder takes the body ink, at full strength, and says it is a
     placeholder by being italic.

     The obvious treatment is a quieter ink, and it was measured rather than
     assumed: --pw-color-disabled-content on the field's own ground clears 4.5
     in three of the six palettes this kit ships and misses it in the other
     three, at 4.29, 4.44 and 3.83. A placeholder is text in an enabled
     control, so 1.4.3 reaches it and the disabled tier's 3.0 does not apply.
     Adding a seventh rung to six palettes to land just over the floor is the
     same trade the metadata ink was offered and refused.

     The .pw-select placeholder was doing it with opacity: 0.7, which is worse
     than a quiet ink rather than better: check-colour reads a computed color
     and composites its alpha, and element opacity is not alpha, so the gate
     measured the full-strength value and the real ratio was never checked at
     all. Both placeholders are the same treatment now, and it is one the gate
     can see. */
  .pw-input::placeholder,
  .pw-textarea::placeholder {
    color: var(--pw-color-base-content);
    font-style: italic;
    /* Firefox dims placeholders by default. */
    opacity: 1;
  }

  .pw-input:disabled,
  .pw-textarea:disabled {
    color: var(--pw-color-disabled-content);
    cursor: default;
  }

  /* Invalid draws a ring rather than tinting the field, for the reason the
     cyber skin's own palette forces: error, success and warning all resolve to
     one cyan pair there, so a tint would say nothing. A ring thicker than the
     resting boundary is a shape difference, which is what the rule about no
     state being carried by colour alone is asking for.

     Listed before the sunken stack, because box-shadow paints the first entry
     on top. Behind it the bevel would only show at the corners. */
  .pw-input[aria-invalid="true"],
  .pw-textarea[aria-invalid="true"] {
    --pw-elev:
      inset 0 0 0 var(--pw-invalid-ring) var(--pw-color-error-content),
      var(--pw-shadow-sunken);
  }

  /* ── Checkbox and radio share a box ─────────────────────────────────── */
  .pw-checkbox,
  .pw-radio {
    /* Sunken at REST. These are wells you put a mark into, not buttons, which
       is why neither can answer a press by sinking: there is nowhere further
       down to go. Windows answered it by lightening the well's face, and this
       kit will not let colour be the only carrier of a state, so the press is
       the same pixel drop every other control uses. */
    --pw-elev: var(--pw-shadow-sunken);
    --pw-elev-fill: var(--pw-fill-sunken);
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    width: var(--pw-box-size);
    height: var(--pw-box-size);
    padding: 0;
    background-color: var(--pw-color-base-300);
    color: var(--pw-color-base-content);
    cursor: pointer;
  }

  .pw-checkbox { border-radius: var(--pw-radius-control); }
  /* The one round thing in the kit. See the header. */
  .pw-radio { border-radius: 50%; }

  /* The box is smaller than a touch target, so the target is a pseudo
     element centred on it. Same split as the slider grip and the burger on
     the portfolio: the ink is small, the thing a finger hits is not. */
  .pw-checkbox::after,
  .pw-radio::after {
    content: '';
    position: absolute;
    top: 50%;
    left: 50%;
    width: var(--pw-control-h);
    height: var(--pw-control-h);
    transform: translate(-50%, -50%);
  }

  .pw-checkbox:disabled,
  .pw-radio:disabled {
    color: var(--pw-color-disabled-content);
    cursor: default;
  }

  /* Radix renders the indicator only when the control is on, so the box is
     genuinely empty rather than holding a hidden mark. */
  .pw-box-indicator {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    pointer-events: none;
  }

  /* A checkbox renders both its marks and this shows one. Keyed off the
     data-state Radix writes rather than chosen in JS, so an uncontrolled
     checkbox that starts indeterminate shows a dash instead of a tick. */
  .pw-box-indicator[data-state="checked"] [data-mark="minus"],
  .pw-box-indicator[data-state="indeterminate"] [data-mark="check"] {
    display: none;
  }

  /* The group stacks its radios, and it needs a rule because the component
     emits this class unconditionally. A class a component always renders and
     no stylesheet ever matches is either dead or a stylesheet that did not
     parse, and from the outside those look the same. */
  .pw-radio-group {
    display: flex;
    flex-direction: column;
    gap: var(--pw-space-2xs);
  }

  /* ── Select ─────────────────────────────────────────────────────────── */
  .pw-select {
    --pw-elev: var(--pw-shadow-sunken);
    --pw-elev-fill: var(--pw-fill-sunken);
    display: inline-flex;
    align-items: center;
    gap: var(--pw-control-gap);
    min-height: var(--pw-control-h);
    min-width: var(--pw-control-min-w);
    /* No right padding: the drop button occupies that edge. */
    padding: 0 0 0 var(--pw-control-pad-x);
    background-color: var(--pw-color-base-100);
    color: var(--pw-color-base-content);
    font-family: var(--pw-font-ui);
    font-size: var(--pw-control-text);
    border-radius: var(--pw-radius-control);
    cursor: default;
    text-align: left;
  }

  .pw-select-value {
    flex: 1 1 auto;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  /* Raised against the sunken field: the two treatments are what make it
     read as a button on a well rather than an arrow drawn on a box. */
  .pw-select-button {
    --pw-elev: var(--pw-shadow-raised);
    --pw-elev-fill: var(--pw-fill-raised);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    align-self: stretch;
    width: var(--pw-control-h);
    margin: var(--pw-bevel-2);
    background-color: var(--pw-color-base-200);
    border-radius: var(--pw-radius-control);
    pointer-events: none;
  }

  /* The drop button is what carries both states, and they are the same
     picture on purpose: pressing shows what opening will do. The field itself
     cannot sink because it is already a well, which is also why this is not
     on .pw-select. */
  .pw-select[data-state="open"] .pw-select-button,
  .pw-select:active .pw-select-button {
    --pw-elev: var(--pw-shadow-sunken);
    --pw-elev-fill: var(--pw-fill-sunken);
  }

  .pw-checkbox:active,
  .pw-radio:active { transform: translateY(var(--pw-press-travel)); }

  .pw-checkbox:disabled:active,
  .pw-radio:disabled:active,
  .pw-checkbox[data-disabled]:active,
  .pw-radio[data-disabled]:active { transform: none; }

  /* A media query adds no specificity, so a shorter selector here would lose
     to the rules above. The press survives reduced motion because the shape
     change IS the affordance; only its duration goes. */
  @media (prefers-reduced-motion: reduce) {
    .pw-checkbox:active,
    .pw-radio:active { transform: translateY(var(--pw-press-travel)); }
  }

  .pw-select:disabled { color: var(--pw-color-disabled-content); }
  /* The same placeholder treatment as the text field, and for the reason given
     up there: this was opacity: 0.7, and element opacity is invisible to the
     contrast gate, so the ratio it was painting was never measured. */
  .pw-select[data-placeholder] .pw-select-value { font-style: italic; }

  /* The open list reuses the menu's surface, because it is one. */
  .pw-select-list {
    --pw-elev: var(--pw-shadow-raised);
    --pw-elev-fill: var(--pw-fill-raised);
    min-width: var(--radix-select-trigger-width);
    max-height: var(--radix-select-content-available-height);
    padding: var(--pw-space-3xs) 0;
    background-color: var(--pw-color-base-200);
    border-radius: var(--pw-radius-control);
    clip-path: var(--pw-clip-control);
    font-family: var(--pw-font-ui);
    font-size: var(--pw-control-text);
    color: var(--pw-color-base-content);
  }

  .pw-select-item {
    display: flex;
    align-items: center;
    gap: var(--pw-control-gap);
    min-height: var(--pw-control-h);
    padding: 0 var(--pw-control-pad-x) 0 var(--pw-menu-gutter);
    position: relative;
    cursor: default;
    user-select: none;
    outline: none;
  }
  /* The highlighted option, painted through the selection slot rather than
     out of the palette. It used to name --pw-color-primary directly, which is
     what .pw-list-item and .pw-menu-item both stopped doing when the slot was
     introduced, so the cyber skin's corner brackets reached a list and a menu
     and never reached an open select. */
  .pw-select-item[data-highlighted] {
    background-color: var(--pw-selected-bg);
    color: var(--pw-selected-content);
  }

  .pw-select-item[data-highlighted]::before {
    content: '';
    position: absolute;
    inset: var(--pw-bracket-inset);
    pointer-events: none;
    background: var(--pw-selected-mark);
  }
  .pw-select-item[data-disabled] { color: var(--pw-color-disabled-content); }

  .pw-select-indicator {
    position: absolute;
    left: var(--pw-space-sm);
    display: inline-flex;
    align-items: center;
  }

  @media (forced-colors: active) {
    .pw-select-item[data-highlighted] {
      background-color: Highlight;
      color: HighlightText;
      forced-color-adjust: none;
    }
  }
}

/**
 * SWITCH.
 *
 * Not a toggle, and the difference is worth stating because the kit now ships
 * both. A toggle is a button that stays pressed: it answers "is this mode on",
 * and its own face carries the state. A switch is a thing you throw, and the
 * state is WHERE THE THUMB IS. That is why one is a <button aria-pressed> and
 * the other is a <button role="switch" aria-checked>, and why a switch reads
 * correctly at a glance from across a settings panel while a pressed button
 * does not.
 *
 * ── Position is the carrier, and it has to be ────────────────────────────
 * The track tints when it is on, and that tint is the second signal rather
 * than the first. It has to be, twice over. The house rule is that no state
 * in this kit is carried by colour alone, and the cyber skin makes that
 * concrete rather than theoretical: it resolves error, success and warning to
 * one accent on purpose, so a skin can arrive where the tint says nothing.
 * The thumb has moved half the track's width either way.
 *
 * ── The travel is one expression at any padding ──────────────────────────
 * The thumb is square and as tall as the track's inside, so its width is the
 * track height minus twice the padding, and the distance it has to cover is
 * the track width minus twice the padding minus that. The paddings cancel:
 * the travel is always width minus height. Which matters because the padding
 * is --pw-bevel-1, so it is a pixel under the chrome skin and zero under a
 * flat one, and the same declaration has to be right for both.
 */

@layer pw.components {
  .pw-switch {
    /* A well with something sitting in it, exactly as the checkbox is. */
    --pw-elev: var(--pw-shadow-sunken);
    --pw-elev-fill: var(--pw-fill-sunken);
    position: relative;
    display: inline-flex;
    align-items: center;
    flex: 0 0 auto;
    width: var(--pw-switch-w);
    height: var(--pw-switch-h);
    padding: var(--pw-bevel-1);
    background-color: var(--pw-color-base-300);
    border-radius: var(--pw-radius-control);
    cursor: pointer;
  }

  /* The ink is 36x20 and a finger is not. Same split as the checkbox, the
     radio and the slider grip: a centred ::after carries the target, which is
     also why this control is on the corner-clip opt-out list. */
  .pw-switch::after {
    content: '';
    position: absolute;
    top: 50%;
    left: 50%;
    width: max(100%, var(--pw-control-h));
    height: var(--pw-control-h);
    transform: translate(-50%, -50%);
  }

  .pw-switch-thumb {
    --pw-elev: var(--pw-shadow-raised);
    --pw-elev-fill: var(--pw-fill-raised);
    display: block;
    height: 100%;
    aspect-ratio: 1;
    background-color: var(--pw-color-base-200);
    border-radius: var(--pw-radius-control);
    /* No box-shadow here. The thumb is in the shared slot, which declares
       box-shadow: var(--pw-elev), var(--pw-focus-halo), and pw.components
       sorts after pw.treatment, so saying it again here won and dropped the
       halo layer. Nothing was visibly wrong, because the thumb is not
       focusable and the halo is only set on :focus-visible, but it is the
       fourth time a component has redeclared a property the slot owns. */
    transition: transform var(--pw-duration-fast) var(--pw-ease-state);
  }

  /* Both spellings, for the reason the toggle gives: the CSS keyed off one
     and the accessibility tree off the other, with nothing binding them, and
     this kit also ships as standalone CSS where a consumer wires the
     attributes by hand. */
  .pw-switch[data-state="checked"],
  .pw-switch[aria-checked="true"] {
    background-color: var(--pw-color-primary);
  }

  .pw-switch[data-state="checked"] .pw-switch-thumb,
  .pw-switch[aria-checked="true"] .pw-switch-thumb {
    transform: translateX(calc(var(--pw-switch-w) - var(--pw-switch-h)));
  }

  .pw-switch:disabled,
  .pw-switch[data-disabled] { cursor: default; }

  /* The travel survives, only its duration goes. The rule this kit follows is
     that a state change is not motion for its own sake: a switch that does not
     move has not told anyone anything. */
  @media (prefers-reduced-motion: reduce) {
    .pw-switch-thumb { transition-duration: 1ms; }
  }

  @media (forced-colors: active) {
    /* background-color is forced and box-shadow is stripped, so the tint that
       says "on" is gone and the thumb's own edge with it. The position still
       reads, and a border puts the thumb back. */
    .pw-switch-thumb { border: var(--pw-border) solid ButtonText; }
  }
}

/**
 * COLLAPSIBLE.
 *
 * A disclosure row and the thing it discloses. Radix owns the aria-expanded
 * and aria-controls pair, which is the part that is easy to write and easy to
 * write wrong: a trigger whose aria-controls points at an id that is not in
 * the document looks perfectly correct in the markup and tells a screen reader
 * nothing.
 *
 * ── The height is not animated, and that is the house rule rather than a
 *    shortcut ─────────────────────────────────────────────────────────────
 * Radix publishes --radix-collapsible-content-height precisely so the open and
 * close can be tweened, and every kit in this genre uses it. This one does
 * not, because height is a layout property: animating it reflows the document
 * on every frame, and the kit's motion rule is transform and opacity only.
 *
 * The chevron turns instead. That is a transform, it is the affordance anyone
 * reads, and it costs one composited property rather than a reflow.
 */

@layer pw.components {
  .pw-collapsible { display: block; }

  /* Flat, like the menu bar's trigger. It is a row you press, not a control
     sitting on a surface, and a bevel here would make every disclosure look
     like a button that had been left out of a toolbar. The focus ring still
     reaches it: reset.css draws that for the classes it lists, whether or not
     they are in the elevation slot. */
  .pw-collapsible-trigger {
    display: flex;
    align-items: center;
    gap: var(--pw-control-gap);
    width: 100%;
    min-height: var(--pw-control-h);
    padding: 0 var(--pw-control-pad-x) 0 0;
    background: none;
    color: var(--pw-color-base-content);
    text-align: left;
  }

  .pw-collapsible-trigger:disabled,
  .pw-collapsible-trigger[data-disabled] {
    color: var(--pw-color-disabled-content);
    cursor: default;
  }

  /* The one moving part. Pointing right when shut and down when open is the
     convention every file tree has used since Explorer, and a quarter turn is
     exact on a square lattice, which the icon sheet cares about: the chevron
     is pixel art and 90 degrees resamples nothing. */
  .pw-collapsible-trigger .pw-icon {
    transition: transform var(--pw-duration-fast) var(--pw-ease-state);
  }

  .pw-collapsible-trigger[data-state="open"] .pw-icon,
  .pw-collapsible-trigger[aria-expanded="true"] .pw-icon {
    transform: rotate(90deg);
  }

  .pw-collapsible-content { display: block; }

  @media (prefers-reduced-motion: reduce) {
    .pw-collapsible-trigger .pw-icon { transition-duration: 1ms; }
  }
}

/**
 * TOOLBAR.
 *
 * A row of controls that behaves as one tab stop, with the arrow keys moving
 * between them. That is the whole point and Radix owns all of it: a toolbar
 * with eleven buttons is eleven tab stops without it, and eleven tab stops
 * between a user and the next thing on the page is how a keyboard user learns
 * to avoid a toolbar.
 *
 * Flat, like the menu bar, and for the same reason. The controls inside it
 * are already bevelled; a bevelled tray under them would be a second frame
 * around a frame, which is the mistake the window chrome exists to avoid
 * making twice.
 *
 * .pw-transport is the same primitive with a fixed set of buttons in it. This
 * is the general one, for a consumer's own row.
 */

@layer pw.components {
  .pw-toolbar {
    display: flex;
    align-items: center;
    gap: var(--pw-control-gap);
    /* Radix puts outline: none inline on the root, which is correct: the
       roving tabindex means focus lands on a button, never on the tray. */
  }

  .pw-toolbar[data-orientation="vertical"] { flex-direction: column; align-items: stretch; }
}

/**
 * SEPARATOR.
 *
 * The one component that reads --pw-color-divider, and that token's whole
 * reason for existing. It was declared in every palette from the start and
 * read by nothing, sitting on the reserved-token list with the note that this
 * kit's own separator is engraved from the bevel pair rather than drawn flat.
 *
 * Both things are true and they are different jobs. .pw-menu-separator sits
 * inside a bevelled popup, where a groove between two rows is the period
 * drawing; it stays engraved. This one is the grouping line a consumer puts
 * between sections of their own layout, which is exactly what the token's own
 * comment called it: "a semantic slot for consumers".
 *
 * That tier is 2:1, not 3:1, and the reason is written into the contrast gate:
 * removing a grouping line identifies nothing and changes no state, so 1.4.11
 * does not reach it. An earlier version of check-colour floored every border
 * at 3 and reported the whole tier as forty failures the day it was added.
 *
 * Drawn as a border rather than a background, because that is the property
 * check-colour reads to decide something is a line at all. What it will not
 * do is measure this off the page: it skips anything under 2px in either
 * direction and a hairline rule is 1. The declared pair in
 * tastecheck.config.json is what holds the ink, and it holds it against both
 * grounds in all seven themes.
 */

@layer pw.components {
  .pw-separator {
    flex: 0 0 auto;
    border: 0;
  }

  .pw-separator[data-orientation="horizontal"] {
    height: 0;
    width: 100%;
    border-top: var(--pw-border) solid var(--pw-color-divider);
  }

  .pw-separator[data-orientation="vertical"] {
    width: 0;
    align-self: stretch;
    /* A vertical rule with nothing tall beside it is invisible, and a toolbar
       separator is the common case. */
    min-height: var(--pw-space-lg);
    border-left: var(--pw-border) solid var(--pw-color-divider);
  }

  /* Forced colors keeps border and strips almost everything else, which is
     the one place this kit's shadow-only edges need a real border. Here the
     border is already the whole thing, so it only needs a system ink. */
  @media (forced-colors: active) {
    .pw-separator { border-color: CanvasText; }
  }
}

/**
 * TOOLTIP.
 *
 * A raised surface, the same one the menu is, because it is the same thing:
 * something that appears above the page and is not part of it.
 *
 * ── No arrow ─────────────────────────────────────────────────────────────
 * Radix ships Tooltip.Arrow and it renders an <svg>. This kit's every edge is
 * an inset box-shadow, and an SVG triangle cannot take one, so a skinned arrow
 * would be a second, hand-drawn description of the bevel that no token
 * controls and no gate measures. It would go wrong the first time a skin
 * changed the frame. The tooltip is placed with an offset instead, which is
 * what the reference lineage did: a period tooltip is a box near the pointer,
 * not a speech bubble.
 *
 * ── What a tooltip is not ────────────────────────────────────────────────
 * It is not a label. A control whose only name is its tooltip has no name to
 * a touch user, who cannot hover, and no name until focus for anyone else.
 * The kit's own icon-only buttons all carry aria-label for that reason and a
 * tooltip on top of one is a convenience, not the name. Documented rather
 * than enforced, because a component cannot tell which case it is in.
 */

@layer pw.components {
  .pw-tooltip {
    --pw-elev: var(--pw-shadow-raised);
    --pw-elev-fill: var(--pw-fill-raised);
    max-width: 20rem;
    padding: var(--pw-space-2xs) var(--pw-space-sm);
    background-color: var(--pw-color-base-200);
    color: var(--pw-color-base-content);
    font-family: var(--pw-font-ui);
    font-size: var(--pw-text-micro);
    line-height: var(--pw-leading-ui);
    border-radius: var(--pw-radius-control);
    /* Clipped, unlike the controls: nothing here takes focus, so there is no
       outline for a notch to eat. */
    clip-path: var(--pw-clip-control);
    /* Radix measures the trigger and positions this; the kit only says how
       far off it sits. */
    z-index: var(--pw-z-panel);
  }

  /* Enter only. A tooltip that fades out holds a box over the thing you just
     moved the pointer to, and the exit tokens are on the reserved list because
     nothing in this kit has yet had a reason to animate out. This is not it. */
  .pw-tooltip[data-state="delayed-open"] {
    animation: pw-tooltip-in var(--pw-duration-fast) var(--pw-ease-enter);
  }

  @keyframes pw-tooltip-in {
    from { opacity: 0; transform: scale(0.98); }
    to   { opacity: 1; transform: none; }
  }

  @media (prefers-reduced-motion: reduce) {
    .pw-tooltip[data-state="delayed-open"] { animation-duration: 1ms; }
  }
}

/**
 * STATUS BADGE.
 *
 * An XP status-bar cell: sunken, small, and never a floating pill.
 *
 * This is the easiest component in the kit to fail the "colour is never the
 * only affordance" rule, because a coloured dot is the obvious way to draw
 * it and a coloured dot is exactly the failure. Every badge renders a glyph
 * and a word. Success is a check and the word OK, not a green circle.
 */

@layer pw.components {

  /* The mark is pulled in by its own empty cell, so what the eye measures is
     ink to word rather than box to word.

     Every icon is a 16x16 cell and almost none of them fill it: the check is
     12 pixels of ink with 2 either side, the exclamation is 2 with 7. With one
     gap value for all of them the badges measured identically and read nothing
     alike, 10px of inset before the check against 15 before the exclamation
     and a 6px gap after one against 11 after the other. Reported as
     inconsistent padding, which it was, just not in the padding.

     The bearings come from the sheet through the Icon component rather than
     from a list here, so a redrawn glyph brings its own correction and a new
     one needs no rule.

     The arithmetic used to be two calc()s right here, which made this the one
     selector in the kit that had them. It is in icon.css now and this is the
     switch. */
  .pw-badge {
    --pw-icon-bearing: 1;
    --pw-elev: var(--pw-shadow-sunken);
    --pw-elev-fill: var(--pw-fill-sunken);
    display: inline-flex;
    align-items: center;
    gap: var(--pw-space-2xs);
    min-height: var(--pw-badge-h);
    padding: 0 var(--pw-space-sm);
    /* Flex centres the label's line box, not its capitals, and each skin's
       face sits differently in it: measured, cyber's caps were 1.5px high and
       dialup's a quarter low. Top padding moves the centred content down by
       half of itself, so a skin's whole-pixel drop is doubled here. */
    padding-top: calc(var(--pw-badge-text-drop) * 2);
    font-family: var(--pw-font-ui);
    font-size: var(--pw-text-micro);
    line-height: var(--pw-leading-ui);
    border-radius: var(--pw-radius-control);
  }

  .pw-badge[data-status="success"] {
    background-color: var(--pw-color-success);
    color: var(--pw-color-success-content);
  }
  .pw-badge[data-status="warning"] {
    background-color: var(--pw-color-warning);
    color: var(--pw-color-warning-content);
  }
  .pw-badge[data-status="error"] {
    background-color: var(--pw-color-error);
    color: var(--pw-color-error-content);
  }

  /* The mark is an icon on the shared sheet, painted in currentColor, so the
     pair that was measured is the pair that is painted and the badge does not
     need to know an icon is there. */
  .pw-badge .pw-icon {
    /* A 16px mark next to 12px text sits a pixel high on the cap line. */
    margin-block: -1px;
    /* And then the skin's own correction, relative to the text, because a
       glyph's ink and a face's caps do not move together. */
    translate: 0 var(--pw-badge-mark-drop);
  }
}

/**
 * METADATA.
 *
 * The coordinate readouts, serial strings and status lines that every screen
 * in this genre carries in its corners. A block of label and value pairs set
 * small, tracked, and quiet.
 *
 * It is a <dl> because that is what it is. A readout is field and value, and
 * the one markup element for field and value pairs has been in HTML since
 * 1993. A stack of divs with a colon in the text reads identically to a
 * sighted user and gives a screen reader nothing to walk.
 *
 * Every value is tabular. These are numbers in a column: a serial that jumps
 * a pixel sideways when a 1 replaces a 7 is the kind of thing nobody can name
 * and everybody notices.
 *
 * On the quiet: the reference screens draw this at around 8px and something
 * like 2.5:1, and that is the one thing from them this kit does not copy.
 * 1.4.3 applies to small text and this is small text. The quiet comes from
 * size and tracking, and --pw-color-meta-content stays at the floor rather
 * than under it. There is a measured pair in tastecheck.config.json holding
 * it there.
 */

@layer pw.components {
  .pw-meta {
    display: grid;
    /* The label column is as wide as its widest label and no wider, which is
       what keeps the values in one column without a hard-coded width that a
       longer label would break. */
    grid-template-columns: auto 1fr;
    gap: var(--pw-space-3xs) var(--pw-space-xs);
    margin: 0;
    font-family: var(--pw-font-mono);
    font-size: var(--pw-text-micro);
    line-height: var(--pw-leading-ui);
    letter-spacing: var(--pw-tracking-meta);
    /* The body ink, not a quieter one, and that is the decision in this file.
       
       The reference screens draw metadata at around 8px and something like
       2.5:1. Copying the size is one thing; copying the contrast would put
       small text under 1.4.3, and a kit that measures fifteen thousand
       painted pairs does not get an exemption for the pair it would most like
       to have one for.
       
       A quieter ink was tried and abandoned. It needed a new rung in four
       palettes, and in three of them the only rung that cleared the floor was
       barely quieter than the body ink anyway: 4.37:1 in the deck's light
       theme against a 4.5 floor, which is a fail dressed as a design. So the
       quiet comes from size, tracking, and the label's own step back, and the
       colour does not participate at all. */
    color: var(--pw-color-base-content);
  }

  /* Each pair is its own row, so a value that wraps takes its label's row with
     it rather than sliding the whole column up. */
  .pw-meta-item {
    display: grid;
    grid-column: 1 / -1;
    grid-template-columns: subgrid;
  }

  .pw-meta-label {
    margin: 0;
    /* The label is the quieter half of an already quiet block, and it earns
       that by being the half you do not read: you scan for the value. */
    opacity: var(--pw-meta-label-opacity);
  }

  .pw-meta-value {
    margin: 0;
    font-variant-numeric: tabular-nums;
    /* A serial or a coordinate is one unbroken token and breaking it mid-run
       makes it unreadable, but a long status string still has to wrap. */
    overflow-wrap: anywhere;
  }

  /* Laid out in a row instead, for a footer strip rather than a corner block.
     Same pairs, same type, no grid: the pairs sit inline and wrap. */
  .pw-meta[data-layout="inline"] {
    display: flex;
    flex-wrap: wrap;
    gap: var(--pw-space-3xs) var(--pw-space-md);
  }

  .pw-meta[data-layout="inline"] .pw-meta-item {
    display: flex;
    gap: var(--pw-space-2xs);
    grid-column: auto;
    grid-template-columns: none;
  }
}

/**
 * LCD READOUT: the one genuinely custom piece.
 *
 * Every other component in the kit borrows an interaction from Radix and an
 * ARIA pattern from the APG. This one has neither to borrow, because a
 * segment display is not a control, it is an image of text.
 *
 * That is also its whole accessibility problem, and the answer is in the
 * markup rather than here. src/readout.tsx carries the argument: the role,
 * why it is not a live region, and why the pause control disappears under
 * prefers-reduced-motion. This file draws it.
 *
 * Do not restate that argument here. It was stated here, in its own words,
 * describing a visually hidden mirror span that the role replaced, and the
 * description outlived the code by two releases. One decision, one copy.
 *
 * ── Why the cell index arrives as a unitless integer ──────────────────────
 * The component writes --pw-cell-x: 4, not background-position: -27px 0.
 * Two reasons, and they agree. taste-check's treatments check flags any
 * literal carrying px, rem or em inside markup as a one-off value, which is
 * exactly what "-27px" would be. And every pixel dimension in this kit
 * belongs in a stylesheet, so the arithmetic lives here where the cell size
 * is already declared. CSS mod() and round() could split a single index into
 * a row and a column, but they only reached Chrome in 125, and the character
 * map has to exist in the component anyway, so it emits both integers.
 */

@layer pw.components {
  :root,
  [data-skin="chrome"],
  [data-theme="light"] {
    /* Geometry, from Strider's 1998 spec. See assets/README.md. */
    --pw-lcd-digit-w: 9px;
    --pw-lcd-digit-h: 13px;
    --pw-lcd-digit-cols: 14;
    --pw-lcd-glyph-w: 5px;
    --pw-lcd-glyph-h: 6px;
    --pw-lcd-glyph-cols: 31;
    --pw-lcd-glyph-rows: 2;

    /* One knob rescales the readout. It must stay a whole number: the sheet
       is a pixel grid, and a fractional scale puts cell edges between device
       pixels, which tears the glyph rather than softening it. 3 puts a digit
       at 27x39 and a glyph at 15x18. */
    --pw-lcd-scale: 3;

    /* A default width for a scrolling readout. It is a token rather than a
       number the caller writes inline, which is not pedantry: the demo set
       width: 22rem on the element and the treatments check caught it as a
       one-off value in markup, which is exactly what it was. */
    --pw-lcd-marquee-w: 22rem;

    --pw-lcd-sheet-digits: url("../assets/lcd-digits.svg");
    --pw-lcd-sheet-glyphs: url("../assets/lcd-glyphs.svg");
  }

  .pw-lcd {
    --pw-elev: var(--pw-shadow-sunken);
    --pw-elev-fill: var(--pw-fill-sunken);
    /* The readout is set deeper than anything else in the kit. It is a
       window into a box, not a face on one. The depth is structural.css's
       --pw-lcd-depth rather than a literal here, because a literal in this
       layer is unreachable from pw.tokens and two skins spent a release
       flattening everything except this. */
    --pw-bevel-depth: var(--pw-lcd-depth);
    display: inline-flex;
    align-items: center;
    gap: calc(var(--pw-lcd-scale) * 1px);
    padding: var(--pw-space-sm) var(--pw-space-md);
    background-color: var(--pw-color-lcd);
    border-radius: var(--pw-radius-control);
  }

  .pw-lcd-render {
    display: inline-flex;
    align-items: center;
    gap: calc(var(--pw-lcd-scale) * 1px);
  }

  .pw-lcd-cell {
    display: inline-block;
    /* Set per cell by the component, in markup, as plain integers. */
    --pw-cell-x: 0;
    --pw-cell-y: 0;
    background-color: var(--pw-color-lcd-content);
    mask-repeat: no-repeat;
    -webkit-mask-repeat: no-repeat;
    /* The phosphor bloom. drop-shadow rather than box-shadow because the ink
       is a mask, so the glow has to follow the glyph's own shape instead of
       its rectangle. At --pw-glow-intensity 0 the radius computes to 0 and
       this paints nothing. */
    filter: drop-shadow(0 0
      calc(var(--pw-glow-radius) * var(--pw-glow-intensity))
      var(--pw-glow-color));
  }

  .pw-lcd-cell[data-sheet="digits"] {
    width:  calc(var(--pw-lcd-digit-w) * var(--pw-lcd-scale));
    height: calc(var(--pw-lcd-digit-h) * var(--pw-lcd-scale));
    mask-image: var(--pw-lcd-sheet-digits);
    -webkit-mask-image: var(--pw-lcd-sheet-digits);
    mask-size: calc(var(--pw-lcd-digit-cols) * var(--pw-lcd-digit-w) * var(--pw-lcd-scale)) auto;
    -webkit-mask-size: calc(var(--pw-lcd-digit-cols) * var(--pw-lcd-digit-w) * var(--pw-lcd-scale)) auto;
    mask-position: calc(var(--pw-cell-x) * var(--pw-lcd-digit-w) * var(--pw-lcd-scale) * -1) 0;
    -webkit-mask-position: calc(var(--pw-cell-x) * var(--pw-lcd-digit-w) * var(--pw-lcd-scale) * -1) 0;
  }

  .pw-lcd-cell[data-sheet="glyphs"] {
    width:  calc(var(--pw-lcd-glyph-w) * var(--pw-lcd-scale));
    height: calc(var(--pw-lcd-glyph-h) * var(--pw-lcd-scale));
    mask-image: var(--pw-lcd-sheet-glyphs);
    -webkit-mask-image: var(--pw-lcd-sheet-glyphs);
    mask-size:
      calc(var(--pw-lcd-glyph-cols) * var(--pw-lcd-glyph-w) * var(--pw-lcd-scale))
      calc(var(--pw-lcd-glyph-rows) * var(--pw-lcd-glyph-h) * var(--pw-lcd-scale));
    -webkit-mask-size:
      calc(var(--pw-lcd-glyph-cols) * var(--pw-lcd-glyph-w) * var(--pw-lcd-scale))
      calc(var(--pw-lcd-glyph-rows) * var(--pw-lcd-glyph-h) * var(--pw-lcd-scale));
    mask-position:
      calc(var(--pw-cell-x) * var(--pw-lcd-glyph-w) * var(--pw-lcd-scale) * -1)
      calc(var(--pw-cell-y) * var(--pw-lcd-glyph-h) * var(--pw-lcd-scale) * -1);
    -webkit-mask-position:
      calc(var(--pw-cell-x) * var(--pw-lcd-glyph-w) * var(--pw-lcd-scale) * -1)
      calc(var(--pw-cell-y) * var(--pw-lcd-glyph-h) * var(--pw-lcd-scale) * -1);
  }

  /* The scrolling title. A marquee is continuous motion, so the duration
     table does not govern it, and it translates rather than animating a
     scroll offset so it stays on the compositor. */
  .pw-lcd-marquee {
    width: min(var(--pw-lcd-marquee-w), 100%);
  }

  /* The clip lives here rather than on the readout, so the pause control
     standing beside the scrolling copy is not clipped along with it. */
  .pw-lcd-window {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
  }

  .pw-lcd-marquee .pw-lcd-render {
    animation: pw-lcd-scroll var(--pw-duration-marquee) linear infinite;
  }

  /* Hover is a convenience for a pointer. It is NOT the WCAG 2.2.2
     mechanism, because a keyboard, switch or touch user cannot trigger it. */
  .pw-lcd-marquee:hover .pw-lcd-render {
    animation-play-state: paused;
  }

  /* The mechanism. Set by the pause control, and it outranks hover in both
     directions, which is the whole reason it is a three-state attribute
     rather than a boolean.

     There was a :focus-within here, meant to catch a keyboard user who had
     tabbed to the control. It made the control unable to do its job: the
     pause button lives inside the marquee, so pressing resume left focus on
     the button, focus-within held the animation paused, and the resume button
     could never resume. Removed rather than worked around, because with a
     real control in the markup it was solving a problem that no longer
     exists.

     Hover had a quieter version of the same fault. A viewer who presses
     resume while the pointer is still over the readout, which is every mouse
     user who presses it, would see nothing happen until they moved away.
     Untouched means hover applies; once the viewer has taken a decision, the
     decision wins. Equal specificity to the hover rule, and later. */
  .pw-lcd[data-scroll="paused"] .pw-lcd-render {
    animation-play-state: paused;
  }
  .pw-lcd[data-scroll="running"] .pw-lcd-render {
    animation-play-state: running;
  }

  /* The pause control. Same split as the slider grip: the ink is small, the
     thing a finger hits is --pw-control-h. Its colours come from the readout
     rather than the page, because it sits on the lit display. */
  .pw-lcd-pause {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: var(--pw-control-h);
    height: var(--pw-control-h);
    margin: calc(var(--pw-space-sm) * -1) calc(var(--pw-space-xs) * -1)
            calc(var(--pw-space-sm) * -1) 0;
    padding: 0;
    appearance: none;
    background: none;
    border: 0;
    border-radius: var(--pw-radius-control);
    color: var(--pw-color-lcd-content);
    font-family: var(--pw-font-ui);
    font-size: var(--pw-text-ui);
    line-height: 1;
    cursor: pointer;
    transition: opacity var(--pw-duration-hover) var(--pw-ease-state);
  }
  .pw-lcd-pause:hover { opacity: 0.75; }
  .pw-lcd-pause:focus-visible {
    outline: var(--pw-focus-width) solid var(--pw-color-lcd-content);
    outline-offset: calc(var(--pw-focus-offset) * -1);
  }

  @keyframes pw-lcd-scroll {
    from { transform: translateX(0); }
    to   { transform: translateX(-50%); }
  }

  /* In forced colors the cell's background-color is overridden to Canvas,
     and so is the readout's ground, so ink and ground become the same
     system colour and all four displays render as empty boxes. The
     forced-colors block in bevel.css restores a border for the nine classes
     in its list, which does not help here: the problem is not a missing
     boundary, it is that the content is gone.

     Who this reaches is worth being exact about. The string is on the
     role="img" host as its accessible name, so a screen reader reader still
     gets "04:21" whatever the cells do. It is the sighted low-vision user,
     the one forced-colors exists for, who is left with four empty boxes,
     which makes it the worst place in the kit to have missed.

     (That paragraph used to reason from a visually hidden mirror span. The
     mirror was deleted when the role took over and the sentence was not, so
     it went on explaining the behaviour of markup that had left.)

     The glow goes too: drop-shadow on a forced colour paints a second smear
     around every glyph. */
  @media (forced-colors: active) {
    .pw-lcd      { background-color: Canvas; }
    .pw-lcd-cell { background-color: CanvasText; filter: none; }
  }

  @media (prefers-reduced-motion: reduce) {
    /* Continuous motion stops outright rather than speeding up. A marquee at
       1ms is not a reduced marquee, it is a strobe. The selector is repeated
       from above at its own specificity, because the media query adds none. */
    .pw-lcd-marquee .pw-lcd-render {
      animation: none;
      transform: none;
    }

    /* And the pause control goes with it, because there is no longer anything
       for it to pause. It shipped rendered, focusable, and inert: a keyboard
       user could tab to a button that did nothing, which is the exact thing
       window.tsx refuses to do with a window control that has no handler.

       WCAG 2.2.2 asks for a mechanism to pause content that moves for more
       than five seconds. Nothing moves here, so no mechanism is owed, and the
       button is not a cheap extra: it is a named control with no function.

       display, not visibility or a clip, because only display and visibility
       take an element out of BOTH the tab order and the accessibility tree.
       Hiding it any other way is what leaves a named control focusable and
       invisible, which is worse than either. */
    .pw-lcd-pause { display: none; }
  }
}

/**
 * VISUALISER: the spectrum analyser.
 *
 * The last piece of the console the chrome skin is drawing. The kit already
 * ships a transport, a seek bar, an equaliser and a segment readout, and the
 * analyser is the one everybody pictures first.
 *
 * ── The gradient is sampled by height, not stretched to it ───────────────
 * This is the whole technique and it is what makes it read as the original
 * rather than as a bar chart. Winamp coloured each ROW of the display, not
 * each bar: a band reaching the top is green at the bottom and red at the top,
 * and a short band is green all the way up because it never reaches the rows
 * where the ramp turns.
 *
 * A gradient painted on a short bar at background-size: 100% 100% does the
 * opposite: it squashes the whole ramp into whatever height the bar has, so
 * every band shows every colour and the ramp stops meaning level at all. So
 * the bar's background is sized to the FULL column and anchored to the bottom,
 * and the bar's own height clips it. One declaration, and it is the difference
 * between a spectrum analyser and a stacked bar chart.
 *
 * ── The peak marker is in the band, not the bar ──────────────────────────
 * It floats above the bar it belongs to and has to position against the full
 * column, which is what the band is for.
 *
 * ── No motion here ───────────────────────────────────────────────────────
 * Nothing in this file animates. The component redraws when its values change
 * and the values come from the consumer, so WCAG 2.2.2 lands where the motion
 * is rather than on a kit that cannot see it. The marquee is the opposite case
 * and ships its own pause control, which is why the two are worth comparing
 * before anyone adds a timer here.
 */

@layer pw.components {
  .pw-visualiser {
    /* A well, like the equaliser's. The display is set into the chassis. */
    --pw-elev: var(--pw-shadow-sunken);
    --pw-elev-fill: var(--pw-fill-sunken);
    /* The height of one column, which is what the gradient is sized to. */
    --pw-vis-col: calc(var(--pw-vis-h) - (var(--pw-vis-gap) * 2));
    display: flex;
    align-items: flex-end;
    gap: var(--pw-vis-gap);
    height: var(--pw-vis-h);
    padding: var(--pw-vis-gap);
    background-color: var(--pw-color-vis-bg);
    /* The dot grid, which is line 1 of the palette this borrows its structure
       from. Drawn rather than tiled from an image: one gradient, no request,
       and it scales with the pitch token.

       It goes in the ornament slot rather than in a background-image of its
       own, and that is a fix rather than a preference. Declaring
       background-image here put a component rule in pw.components over the
       shared stack in pw.treatment, so this surface silently dropped the
       elevation fill and the texture: the cyber skin's scanlines never reached
       the one thing in the kit that is a screen. */
    --pw-ornament: radial-gradient(var(--pw-color-vis-grid) 1px, transparent 1px);
    --pw-ornament-size: var(--pw-vis-grid-pitch) var(--pw-vis-grid-pitch);
  }

  .pw-visualiser-band {
    position: relative;
    flex: 1 1 0;
    height: 100%;
    min-width: var(--pw-border);
  }

  .pw-visualiser-bar {
    position: absolute;
    inset: auto 0 0 0;
    /* Set per band from markup, as a plain fraction. Declared here rather than
       left to a var() fallback for the reason icon.css gives about its cell
       coordinates: a var() with no declaration anywhere is the cheapest bug in
       this project to introduce, it renders as nothing, and the token gate
       cannot see it unless somebody declares it. */
    --pw-vis-level: 0;
    height: calc(var(--pw-vis-level) * 100%);
    background-image: linear-gradient(
      to top,
      var(--pw-color-vis-low),
      var(--pw-color-vis-mid),
      var(--pw-color-vis-high)
    );
    /* See the header. Sized to the column and anchored to its floor, so the
       bar shows the slice of the ramp its level reaches. */
    background-size: 100% var(--pw-vis-col);
    background-position: left bottom;
    background-repeat: no-repeat;
  }

  .pw-visualiser-peak {
    position: absolute;
    left: 0;
    right: 0;
    --pw-vis-hold: 0;
    height: var(--pw-border);
    bottom: calc(var(--pw-vis-hold) * 100%);
    background-color: var(--pw-color-vis-peak);
  }

  /* Forced colors strips background-color and background-image both, so the
     analyser would be an empty box. Bars become bordered instead: the shape is
     the information and the ramp never was. */
  @media (forced-colors: active) {
    .pw-visualiser { border: var(--pw-border) solid CanvasText; }
    .pw-visualiser-bar { border: var(--pw-border) solid CanvasText; }
    .pw-visualiser-peak { background-color: Highlight; forced-color-adjust: none; }
  }
}

/* The skin layer. One directory per skin, one file per component it
   restructures, so a component file never learns a skin's name and a skin's
   whole surface area is one directory. Nothing here may add an element or
   take one away: see the contract above. */
/* Generated: the bearings this skin's own sprite sheet moves. */
/**
 * What the cyber skin's own sprite sheet does to the optical gaps.
 *
 * GENERATED from assets/icon-font.cyber.mjs by scripts/build-icons-index.mjs.
 * Do not edit by hand: edit the font data and run `npm run generate`.
 *
 * Cells are not here and never will be. Every sheet shares ICON_ORDER, so the
 * cell a name resolves to is the same under every skin and the base index
 * already carries it. Only the ink bearings move, and only for the 31 names
 * whose drawing changed width.
 */

@layer pw.skin {
  [data-skin="cyber"] .pw-icon[data-icon="play"] {
    --pw-icon-ink-l: 6;
    --pw-icon-ink-r: 4;
  }

  [data-skin="cyber"] .pw-icon[data-icon="pause"] {
    --pw-icon-ink-l: 6;
    --pw-icon-ink-r: 5;
  }

  [data-skin="cyber"] .pw-icon[data-icon="stop"] {
    --pw-icon-ink-l: 3;
    --pw-icon-ink-r: 2;
  }

  [data-skin="cyber"] .pw-icon[data-icon="previous"] {
    --pw-icon-ink-l: 4;
    --pw-icon-ink-r: 3;
  }

  [data-skin="cyber"] .pw-icon[data-icon="next"] {
    --pw-icon-ink-l: 4;
    --pw-icon-ink-r: 3;
  }

  [data-skin="cyber"] .pw-icon[data-icon="eject"] {
    --pw-icon-ink-l: 3;
    --pw-icon-ink-r: 2;
  }

  [data-skin="cyber"] .pw-icon[data-icon="close"] {
    --pw-icon-ink-l: 3;
    --pw-icon-ink-r: 2;
  }

  [data-skin="cyber"] .pw-icon[data-icon="minimize"] {
    --pw-icon-ink-l: 3;
    --pw-icon-ink-r: 2;
  }

  [data-skin="cyber"] .pw-icon[data-icon="maximize"] {
    --pw-icon-ink-l: 3;
    --pw-icon-ink-r: 2;
  }

  [data-skin="cyber"] .pw-icon[data-icon="restore"] {
    --pw-icon-ink-l: 3;
    --pw-icon-ink-r: 2;
  }

  [data-skin="cyber"] .pw-icon[data-icon="chevron-down"] {
    --pw-icon-ink-l: 4;
    --pw-icon-ink-r: 3;
  }

  [data-skin="cyber"] .pw-icon[data-icon="chevron-up"] {
    --pw-icon-ink-l: 4;
    --pw-icon-ink-r: 3;
  }

  [data-skin="cyber"] .pw-icon[data-icon="chevron-right"] {
    --pw-icon-ink-l: 6;
    --pw-icon-ink-r: 5;
  }

  [data-skin="cyber"] .pw-icon[data-icon="chevron-left"] {
    --pw-icon-ink-l: 6;
    --pw-icon-ink-r: 5;
  }

  [data-skin="cyber"] .pw-icon[data-icon="check"] {
    --pw-icon-ink-l: 3;
    --pw-icon-ink-r: 2;
  }

  [data-skin="cyber"] .pw-icon[data-icon="exclamation"] {
    --pw-icon-ink-l: 8;
    --pw-icon-ink-r: 7;
  }

  [data-skin="cyber"] .pw-icon[data-icon="dot"] {
    --pw-icon-ink-l: 6;
    --pw-icon-ink-r: 5;
  }

  [data-skin="cyber"] .pw-icon[data-icon="minus"] {
    --pw-icon-ink-l: 4;
    --pw-icon-ink-r: 3;
  }

  [data-skin="cyber"] .pw-icon[data-icon="plus"] {
    --pw-icon-ink-l: 4;
    --pw-icon-ink-r: 3;
  }

  [data-skin="cyber"] .pw-icon[data-icon="caret-down"] {
    --pw-icon-ink-l: 4;
    --pw-icon-ink-r: 3;
  }

  [data-skin="cyber"] .pw-icon[data-icon="info"] {
    --pw-icon-ink-l: 8;
    --pw-icon-ink-r: 7;
  }

  [data-skin="cyber"] .pw-icon[data-icon="question"] {
    --pw-icon-ink-l: 4;
    --pw-icon-ink-r: 3;
  }

  [data-skin="cyber"] .pw-icon[data-icon="ellipsis"] {
    --pw-icon-ink-l: 4;
    --pw-icon-ink-r: 3;
  }

  [data-skin="cyber"] .pw-icon[data-icon="spinner-1"] {
    --pw-icon-ink-l: 7;
    --pw-icon-ink-r: 6;
  }

  [data-skin="cyber"] .pw-icon[data-icon="spinner-2"] {
    --pw-icon-ink-l: 11;
    --pw-icon-ink-r: 2;
  }

  [data-skin="cyber"] .pw-icon[data-icon="spinner-3"] {
    --pw-icon-ink-l: 13;
    --pw-icon-ink-r: 2;
  }

  [data-skin="cyber"] .pw-icon[data-icon="spinner-4"] {
    --pw-icon-ink-l: 11;
    --pw-icon-ink-r: 2;
  }

  [data-skin="cyber"] .pw-icon[data-icon="spinner-5"] {
    --pw-icon-ink-l: 7;
    --pw-icon-ink-r: 6;
  }

  [data-skin="cyber"] .pw-icon[data-icon="spinner-6"] {
    --pw-icon-ink-l: 3;
    --pw-icon-ink-r: 10;
  }

  [data-skin="cyber"] .pw-icon[data-icon="spinner-7"] {
    --pw-icon-ink-l: 3;
    --pw-icon-ink-r: 12;
  }

  [data-skin="cyber"] .pw-icon[data-icon="spinner-8"] {
    --pw-icon-ink-l: 3;
    --pw-icon-ink-r: 10;
  }

  [data-skin="cyber"] .pw-spinner .pw-icon {
    --pw-icon-ink-l: 3;
    --pw-icon-ink-r: 2;
  }
}

/**
 * SKIN: the select, under cyber.
 *
 * field.css says outright what the select is: "a sunken field with a raised
 * drop button on its right edge". That is a period combo box, it is correct
 * for chrome, and it is a thing a HUD has never had. A HUD's field is a
 * bracketed run of text with a caret at the end, and the caret is a mark
 * rather than an object sitting on the surface.
 *
 * This is presentation only. The DOM is the same DOM: .pw-select-button is a
 * <span> with pointer-events: none, so it carries no name, takes no focus,
 * and nothing here changes what a screen reader or the keyboard sees. Radix
 * still owns the combobox entire.
 *
 * The button keeps its box. The field pays for it with asymmetric padding
 * (field.css: "No right padding: the drop button occupies that edge"), so
 * collapsing the box to nothing would pull the caret onto the text.
 */
@layer pw.skin {
  [data-skin="cyber"] .pw-select-button {
    /* No elevation. The caret is drawn on the field, not raised off it, so
       the slot gets nothing to paint rather than a flattened shadow. */
    --pw-elev: none;
    --pw-elev-fill: none;
    background-color: transparent;
    /* The bevel's own thickness, which was insetting a raised object from
       the well around it. There is no object now. */
    margin: 0;
    /* The bracket that replaces the button's right edge. One hairline in the
       accent, matching the arm weight the selection brackets use, so a field
       and a selected row are marked by the same vocabulary.

       In the ornament slot rather than a border-left, which is what this was
       first. A border is outside the box unless something says otherwise, and
       measuring it found the button 46px wide against chrome's 44, so the
       caret cell moved between skins for no reason anyone asked for. The
       ornament is a background layer the slot already composites, so it costs
       no width at all. */
    --pw-ornament: linear-gradient(
      to right,
      var(--pw-color-primary) var(--pw-bracket-weight),
      transparent var(--pw-bracket-weight)
    );
  }

  /* Open and pressed look exactly like closed here, and that is the decision.

     There was a rule that said otherwise. It reached for --pw-bracket-arm,
     which is the 10px LENGTH of a bracket's arm, where it wanted
     --pw-bracket-weight, the 2px thickness, so the hairline came out as a
     10px slab of accent with the caret sitting on it. The comment claimed the
     bracket brightened. It did not brighten, it swelled.

     Rather than pick a new width, the rule goes. The chrome skin marks open by
     sinking the drop button, and that is available to it because it HAS a
     raised button to sink. This skin took the button away on purpose: the
     caret is a mark on the field, not an object on top of it. What is left to
     move is the hairline, and every version of moving it was either invisible
     or too loud. Cyber also has one accent and no brighter rung to reach for,
     so "brighter" would have cost two new primitives to say something the
     open listbox is already saying an inch below.

     The state is not unmarked. Radix opens the listbox, which is the largest
     thing on the screen, and aria-expanded carries it for anyone not looking.
     Nothing here was ever load-bearing. */
}

/**
 * SKIN: the notched corners, under cyber, with their edge drawn.
 *
 * skin.cyber.css cuts two corners off thirteen surfaces with --pw-clip-control
 * and --pw-clip-box. clip-path cuts; it does not draw. Every one of those
 * surfaces draws its edge as an inset box-shadow along the box's straight
 * sides, so at each cut the edge simply stopped and the surface showed an
 * open corner. On the analyser, where the dot grid and the bars run up to
 * it, that read as a rendering fault.
 *
 * corner-shape: bevel draws the same cut as part of the box, and an inset
 * shadow follows it. So the clip stays, because it is what cuts the children
 * (a window's title bar, a well's contents), and a bevel radius of the same
 * size puts the edge along the diagonal inside it. Where corner-shape is not
 * supported this block does nothing and the surfaces look as they did.
 *
 * The radii are the clip's offsets, 3px and 5px, and test/contract.test.mjs
 * holds them to the polygons, so the two cannot drift. The list is the
 * surfaces the clip reaches: everything bevel.css paints, less its
 * focus-ring opt-out.
 */
@layer pw.skin {
  @supports (corner-shape: bevel) {
    [data-skin="cyber"] :where(.pw-badge, .pw-slider-track, .pw-lcd, .pw-tab-panel,
                               .pw-progress, .pw-visualiser, .pw-menu,
                               .pw-select-list, .pw-select-button,
                               .pw-switch-thumb, .pw-tooltip) {
      border-radius: 3px 0;
      corner-shape: bevel;
    }
    [data-skin="cyber"] :where(.pw-window, .pw-eq) {
      border-radius: 5px 0;
      corner-shape: bevel;
    }
  }
}

/**
 * SKIN: paper's face, and the one thing wrong with it.
 *
 * The skin sets --pw-font-ui to a slab, because its own icon sheet is
 * documented as the heaviest of the three and as "a catalogue's vocabulary
 * rather than a HUD's", and a slab serif is the one face that answers both.
 *
 * ── Rockwell reports metrics that are not true ───────────────────────────
 * Rockwell is the only real slab installed on a stock Mac, so a stack that
 * wants a slab there has to name it. It also ships a broken vertical metric:
 * at 15px it reports a font ascent of 10 and a descent of 5, and its cap
 * height is 11.04. A reported ascent SHORTER than the cap height is not a
 * style choice, it is wrong.
 *
 * Everything in this kit that centres a label centres the line box, because
 * that is what `align-items: center` does. Centre a box whose top sits below
 * the caps and the caps end up high. Measured on .pw-button at 15px, against
 * the cap-height centre a reader judges:
 *
 *     system-ui      -0.72px   chrome, fine
 *     ui-monospace   -0.20px   cyber, fine
 *     Georgia        -0.30px   this stack's fallback, fine
 *     Rockwell       +3.02px   four times the next worst, and visible
 *
 * ── Why this and not a nudge on the skin ─────────────────────────────────
 * A per-skin padding or transform would correct Rockwell and then break every
 * machine that does not have Rockwell, because CSS cannot see which family in
 * a stack won. A consumer on Linux would get Georgia plus a three
 * pixel shove.
 *
 * ascent-override is scoped exactly right instead. It repairs the metrics of
 * ONE family, and `src: local('Rockwell')` means that if Rockwell is not
 * installed this @font-face resolves to nothing and the stack falls through
 * to the next name on its own. The correction and the font it corrects cannot
 * be separated.
 *
 * 97/23 rather than the 66.7/33.3 Rockwell claims: it puts the line box
 * centre within half a pixel of the cap-height centre, which lands Rockwell
 * in the same band as the other three faces above. Re-derive it the same way
 * if the stack ever changes: ascent minus descent should equal the cap
 * height, and their sum should stay near 1.2em so a descender still fits.
 */
/* Inside the layer with every other skin file, which changes nothing about
   how @font-face resolves: layers order rules that cascade, and this one does
   not. It is here so the rule that every file under css/skins/ declares its
   layer stays a rule with no exceptions, and so a rule that DOES cascade can
   be added below without anyone having to notice the file was unlayered. */
@layer pw.skin {
  @font-face {
    font-family: 'pw-Rockwell';
    src: local('Rockwell');
    ascent-override: 97%;
    descent-override: 23%;
    line-gap-override: 0%;
  }
}

/**
 * What the paper skin's own sprite sheet does to the optical gaps.
 *
 * GENERATED from assets/icon-font.paper.mjs by scripts/build-icons-index.mjs.
 * Do not edit by hand: edit the font data and run `npm run generate`.
 *
 * Cells are not here and never will be. Every sheet shares ICON_ORDER, so the
 * cell a name resolves to is the same under every skin and the base index
 * already carries it. Only the ink bearings move, and only for the 28 names
 * whose drawing changed width.
 */

@layer pw.skin {
  [data-skin="paper"] .pw-icon[data-icon="play"] {
    --pw-icon-ink-l: 4;
    --pw-icon-ink-r: 2;
  }

  [data-skin="paper"] .pw-icon[data-icon="stop"] {
    --pw-icon-ink-l: 2;
    --pw-icon-ink-r: 2;
  }

  [data-skin="paper"] .pw-icon[data-icon="previous"] {
    --pw-icon-ink-l: 2;
    --pw-icon-ink-r: 3;
  }

  [data-skin="paper"] .pw-icon[data-icon="next"] {
    --pw-icon-ink-l: 3;
    --pw-icon-ink-r: 2;
  }

  [data-skin="paper"] .pw-icon[data-icon="close"] {
    --pw-icon-ink-l: 2;
    --pw-icon-ink-r: 2;
  }

  [data-skin="paper"] .pw-icon[data-icon="minimize"] {
    --pw-icon-ink-l: 2;
    --pw-icon-ink-r: 2;
  }

  [data-skin="paper"] .pw-icon[data-icon="maximize"] {
    --pw-icon-ink-l: 2;
    --pw-icon-ink-r: 2;
  }

  [data-skin="paper"] .pw-icon[data-icon="restore"] {
    --pw-icon-ink-l: 2;
    --pw-icon-ink-r: 2;
  }

  [data-skin="paper"] .pw-icon[data-icon="chevron-right"] {
    --pw-icon-ink-l: 3;
    --pw-icon-ink-r: 6;
  }

  [data-skin="paper"] .pw-icon[data-icon="chevron-left"] {
    --pw-icon-ink-l: 6;
    --pw-icon-ink-r: 3;
  }

  [data-skin="paper"] .pw-icon[data-icon="check"] {
    --pw-icon-ink-l: 2;
    --pw-icon-ink-r: 3;
  }

  [data-skin="paper"] .pw-icon[data-icon="exclamation"] {
    --pw-icon-ink-l: 5;
    --pw-icon-ink-r: 7;
  }

  [data-skin="paper"] .pw-icon[data-icon="dot"] {
    --pw-icon-ink-l: 4;
    --pw-icon-ink-r: 4;
  }

  [data-skin="paper"] .pw-icon[data-icon="minus"] {
    --pw-icon-ink-l: 2;
    --pw-icon-ink-r: 2;
  }

  [data-skin="paper"] .pw-icon[data-icon="plus"] {
    --pw-icon-ink-l: 2;
    --pw-icon-ink-r: 2;
  }

  [data-skin="paper"] .pw-icon[data-icon="caret-down"] {
    --pw-icon-ink-l: 4;
    --pw-icon-ink-r: 4;
  }

  [data-skin="paper"] .pw-icon[data-icon="search"] {
    --pw-icon-ink-l: 2;
    --pw-icon-ink-r: 2;
  }

  [data-skin="paper"] .pw-icon[data-icon="info"] {
    --pw-icon-ink-l: 7;
    --pw-icon-ink-r: 5;
  }

  [data-skin="paper"] .pw-icon[data-icon="question"] {
    --pw-icon-ink-l: 2;
    --pw-icon-ink-r: 4;
  }

  [data-skin="paper"] .pw-icon[data-icon="ellipsis"] {
    --pw-icon-ink-l: 2;
    --pw-icon-ink-r: 3;
  }

  [data-skin="paper"] .pw-icon[data-icon="spinner-1"] {
    --pw-icon-ink-l: 6;
    --pw-icon-ink-r: 6;
  }

  [data-skin="paper"] .pw-icon[data-icon="spinner-2"] {
    --pw-icon-ink-l: 10;
    --pw-icon-ink-r: 2;
  }

  [data-skin="paper"] .pw-icon[data-icon="spinner-3"] {
    --pw-icon-ink-l: 10;
    --pw-icon-ink-r: 2;
  }

  [data-skin="paper"] .pw-icon[data-icon="spinner-4"] {
    --pw-icon-ink-l: 10;
    --pw-icon-ink-r: 2;
  }

  [data-skin="paper"] .pw-icon[data-icon="spinner-5"] {
    --pw-icon-ink-l: 6;
    --pw-icon-ink-r: 6;
  }

  [data-skin="paper"] .pw-icon[data-icon="spinner-6"] {
    --pw-icon-ink-l: 2;
    --pw-icon-ink-r: 10;
  }

  [data-skin="paper"] .pw-icon[data-icon="spinner-7"] {
    --pw-icon-ink-l: 2;
    --pw-icon-ink-r: 10;
  }

  [data-skin="paper"] .pw-icon[data-icon="spinner-8"] {
    --pw-icon-ink-l: 2;
    --pw-icon-ink-r: 10;
  }

  [data-skin="paper"] .pw-spinner .pw-icon {
    --pw-icon-ink-l: 2;
    --pw-icon-ink-r: 2;
  }
}

/**
 * SKIN: the select, under paper.
 *
 * A printed form does not have a raised drop button, because print has no
 * raised anything. It has a value on a ruled line and a mark at the end of
 * it. The well goes, the rule arrives, and the caret stays a caret.
 *
 * This is presentation only. .pw-select-button is a <span> with
 * pointer-events: none, so nothing here is reachable by focus or announced.
 *
 * The reason the drop button needed this at all: until the change that landed
 * with this layer it was outside the shared slot and painted its own
 * elevation, so a skin whose depth is dither density rather than a shadow
 * could not reach it and it stayed a chrome object on a printed sheet. It is
 * in the slot now, which is what makes --pw-elev: none below do anything.
 */
@layer pw.skin {
  [data-skin="paper"] .pw-select-button {
    --pw-elev: none;
    --pw-elev-fill: none;
    background-color: transparent;
    margin: 0;
    /* The field below puts its rule in the ornament slot, and custom
       properties inherit, so without this the button draws the same rule
       across its own box and the field ends with two hairlines meeting at a
       corner. Measured, not guessed: the button's computed background-image
       was the field's gradient. */
    --pw-ornament: none;
  }

  /* The field stops being a well and becomes a ruled value. The rule is the
     boundary ink rather than the accent, because newsprint has no spot colour
     at all and a form rule is not an accent anywhere. */
  [data-skin="paper"] .pw-select {
    --pw-elev: none;
    --pw-elev-fill: none;
    background-color: transparent;
    border-radius: 0;
    /* The rule goes in the ornament slot rather than in a border, because the
       ornament is a background layer the slot already composites and a border
       is not. --pw-ornament is what structural.css calls "a mark a skin draws
       that no component does", and until now no skin drew one. */
    --pw-ornament: linear-gradient(
      to top,
      var(--pw-color-base-content) var(--pw-border),
      transparent var(--pw-border)
    );
  }

  /* Pressed and open, without a shadow to move. Ink gets heavier or it does
     nothing, so the rule thickens rather than the surface sinking. */
  [data-skin="paper"] .pw-select[data-state="open"],
  [data-skin="paper"] .pw-select:active {
    --pw-ornament: linear-gradient(
      to top,
      var(--pw-color-base-content) var(--pw-bracket-weight),
      transparent var(--pw-bracket-weight)
    );
  }
}
