@font-face {
  font-family: "Geist Mono Variable";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("/assets/fonts/geist-mono.684ad5b531f81d43.woff2") format("woff2");
}
@font-face {
  font-family: "Inter Variable";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("/assets/fonts/inter.3100e775e8616cd2.woff2") format("woff2");
}
@font-face {
  font-family: "Space Grotesk Variable";
  font-style: normal;
  font-weight: 300 700;
  font-display: swap;
  src: url("/assets/fonts/space-grotesk.0640890476fc1198.woff2") format("woff2");
}
/* The reset.
 *
 * Small on purpose -- this is not a normalising stylesheet, it is the two
 * or three browser defaults that make a component library's own rules mean
 * what they say.
 *
 * **`border-box` is the load-bearing one, and its absence is why this file
 * exists.** Under the browser default, `content-box`, a `width: 100%` plus
 * a `padding` is wider than its parent by the padding. Every component here
 * that sets both was affected: the scene's header and body, `.field`,
 * `.card__body`, a padded `.panel`. On a desktop it hides -- the scene's
 * header overflowed its viewport by 15px and `overflow: clip` swallowed it
 * -- and on a phone the same bug eats the entire side gutter, which is what
 * "it does not handle smaller viewports" looked like.
 *
 * The console this replaces never had the problem because Tailwind's
 * preflight sets `border-box` globally. Writing plain CSS means writing the
 * preflight's load-bearing lines too, and this one was simply missed.
 *
 * `stylesheet()` puts this first, before the tokens and the components, and
 * `the_reset_comes_first` keeps it there: a reset that lands after the rules
 * it is meant to underpin is a reset that loses every specificity tie.
 */

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

/* Nothing in this library wants a browser's default margins, and the
   components that need space between them get it from the caller's layout
   -- `--gap-stack` and its siblings. A stray `h1` margin inside a card is a
   gap nobody asked for and nobody can see the source of. */
body,
h1,
h2,
h3,
h4,
h5,
h6,
p,
figure,
blockquote,
ul,
ol {
  margin: 0;
}

/* A long token, a charge point id or a reference digest has no spaces to
   break at, so it will otherwise push a container wider than its parent
   rather than wrapping -- the same failure as the missing `border-box`, and
   it shows up in exactly the places this product puts identifiers. */
body {
  overflow-wrap: break-word;
}

/* iOS paints a grey box over whatever is tapped; every interactive thing
   here has its own pressed and focus states. */
* {
  -webkit-tap-highlight-color: transparent;
}

/* The token layer.
 *
 * One of the four standing design rules is that colour, radius and font go
 * through tokens and never through a raw palette value. This file is that
 * layer for the Dioxus console. It began as a copy of
 * `services/web/src/app/globals.css`, so the two consoles could not drift
 * while both existed; ADR-0126 ended that on purpose. The console being
 * replaced keeps its values and this layer is retuned to the brand's Night
 * sky and the wire, so a value here that differs from `globals.css` is the
 * design, not drift.
 *
 * Tokens only. A rule that styles an element belongs in a sheet under assets/components/ --
 * if this file ever contains a selector that is not a token block, the
 * layer has stopped being a layer, and `the_token_layer_is_only_tokens`
 * fails.
 *
 * THREE THEME STATES, and the selectors are not interchangeable:
 *
 *   1. No `data-theme` anywhere: follow the operating system, via the media
 *      query. This is what `clients/console` does today.
 *   2. `data-theme="dark"` / `"light"` on ANY ancestor: that subtree is
 *      themed, whatever the operating system says.
 *
 * Case 2 deliberately does not say `:root`. The storybook themes a `div`
 * rather than the document, because the useful thing to be able to build
 * there is the same specimen on both grounds at once, and a `:root`-only
 * selector makes that impossible. Getting this wrong is silent: the
 * attribute lands on an element no rule matches, the media query keeps
 * winning, and the toggle appears to do nothing.
 *
 * The dark palette therefore appears twice, which is the cost of CSS having
 * no way to name a block of declarations and apply it from both a media
 * query and a selector. Keep the two copies identical.
 *
 * Not carried across: the ~200 tokens the existing console defines for
 * resources, charger swatches, scene grounds and sidebar chrome. They come
 * over with the components that need them, so that this file stays a list
 * of things actually in use rather than a copy of another codebase's
 * surface area.
 */

:root {
  /* A control's own ground: fields, pickers and outline buttons. On light
     it is the page's white with the ordinary border, so nothing moves; on
     dark it sits one step *above* the card it is on, in the panel's petrol
     hue -- the brand's Night (`--background`) under a field made every
     control read as a hole in its card. */
  --field: oklch(1 0 0);
  --field-line: oklch(0.925 0.005 214.3);
  --field-hover: oklch(0.963 0.002 197.1);
  /* Somebody else's logo stands on its own stage (`brand_logo.rs`). Logos
     are drawn for a light page, most in dark ink, so the stage is a
     softened off-white in both themes and every logo keeps its own
     colours; `--logo-ink` is a name or letters written on it. On dark it
     is a shade greyer, so a grid of stages does not glare. */
  --logo-stage: #f5f8f7;
  --logo-ink: #101a1c;
  color-scheme: light dark;

  /* Ground and ink. oklch, as in the original -- perceptually uniform
     lightness is what lets the dark palette change L and leave hue
     alone. */
  --background: oklch(1 0 0);
  --foreground: oklch(0.148 0.004 228.8);
  --muted-foreground: oklch(0.5 0.02 213.5);
  --hairline: oklch(0.148 0.004 228.8 / 12%);
  --raised: oklch(0.148 0.004 228.8 / 4%);

  /* The status vocabulary. Five tones, closed -- see libs/ui/src/status.rs
     for why it does not grow per caller. */
  --status-ok: oklch(0.51 0.16 152);
  --status-busy: oklch(0.54 0.19 258);
  --status-warn: oklch(0.54 0.16 70);
  --status-fault: oklch(0.552 0.245 27.325);
  --status-unknown: oklch(0.5 0.02 213.5);

  /* Action colours, for `button.rs`. Same names the existing console uses,
     because the components that read them are ports. */
  --primary: oklch(0.508 0.118 165.612);
  --primary-foreground: oklch(0.979 0.021 166.113);
  --secondary: oklch(0.967 0.001 286.375);
  --secondary-foreground: oklch(0.21 0.006 285.885);
  --destructive: oklch(0.577 0.245 27.325);
  --muted: oklch(0.963 0.002 197.1);
  --border: oklch(0.925 0.005 214.3);
  --ring: oklch(0.723 0.014 214.4);

  /* Brand. The gradient runs head to tail; on light grounds that is deep
     emerald to deep cyan (docs/architecture/brand.md).

     `--brand-star` and `--brand-ion` are the mark's two lights: the fixed
     stars of the constellation, and the one that moves. They are a separate
     pair from the gradient because they invert differently -- on Night they
     are near-white (emerald-100 and -50, the brightest thing in the mark),
     and on white they are a deep emerald that the gradient's own stops are
     too light to stand in for. The committed asset ships two files for
     exactly this reason; these two tokens are what let one component do
     the job of both. */
  --brand-from: #059669;
  --brand-to: #0891b2;
  --brand-star: #047857;
  --brand-ion: #047857;

  /* The scene's two accents. Unthemed, like everything else about the night
     sky: it has no light mode, so these are the same on both grounds. Used
     by the error scene's eyebrow -- amber for a fault, cyan for a page where
     nothing is wrong. */
  --scene-accent-warm: #fcd34d;
  --scene-accent-cool: #67e8f9;

  /* The panel: the ground every authenticated page is (spec 097
     design.md §1.1). A cool near-white with a faint green bias, and the
     same eight roles on Night as a petrol-teal. `pn-bg` is the page,
     `pn-raise` a card on it, `pn-raise-2` a band inside a card, `pn-line`
     every hairline, `pn-track` an unlit conductor or an empty bar,
     `pn-bar` a bar that is present but not the subject. Read by the
     console shell and the instrument primitives; `--background` and
     `--foreground` above stay what the sign-in screens and the storybook
     sit on. */
  --pn-bg: #eff3f1;
  --pn-raise: #ffffff;
  --pn-raise-2: #f4f7f6;
  --pn-line: #dde5e2;
  --pn-track: #e6ecea;
  --pn-bar: #a9bcb8;
  --pn-text: #101a1c;
  --pn-dim: #5a6b68;

  /* The wash (ADR-0212): the brand's two ends, faint, over the panel's
     ground -- green from the top left, cyan from the bottom right. The
     geometry is one rule on `.shell`; these are only the two colours, with
     their alpha, so one drawing serves both grounds. The alpha is a
     ceiling, not a taste: it is the most the panel's dim text can bear at
     the wash's peak and still read, which `contrast.rs` holds. Stronger
     means retuning `--pn-dim` first. */
  --pn-wash-a: #05966917;
  --pn-wash-b: #0891b217;
  /* Porcelain's edge (ADR-0212): the light along a card's top edge. By
     day a white card on the near-white panel needs none, so this is
     clear; on Night a shadow has nothing to darken and the card is only a
     slightly lighter rectangle, so a hairline of light stands in for the
     border that is gone. Drawn as an inset shadow, never a border. */
  --pn-edge: #ffffff00;

  /* Glass (ADR-0212): the surface of what floats over content and carries
     only controls and short labels -- the tab strip, the phone's tab bar,
     menus. Translucent and blurred, so the wash and whatever is beneath
     show through; the line is its edge and the highlight the light along
     its top. Never under prose or a figure: those sit on `pn-raise`. */
  --glass: #ffffffa3;
  --glass-line: #ffffffcc;
  --glass-highlight: #ffffffe6;

  /* The anchor surface: one card per page, carrying the thing the page is
     actually for -- the ceiling that binds, the money, what you can send,
     what you cannot edit. A page has at most one, and being the only
     inverted surface is the whole mechanism: it says "this is the point"
     without spending a word on it. On the panel it is the brand's Night
     (ADR-0126): the same petrol ground the sidebar is drawn on, so the
     page's point and the chrome read as one sky.

     Seven roles, mirroring the panel's: `ink-bg` is the card, `ink-lift`
     the top of its gradient, `ink-raise` a chip inside it, `ink-line` a
     hairline on it, `ink-track` an empty bar, and the two inks.

     THE DIRECTION FLIPS WITH THE THEME, and that is the point rather than
     an oversight. On the panel's near-white ground the anchor is dark. On
     Night it cannot also be dark -- it would disappear into a page that is
     already darker than it -- so it becomes the *lightest* surface on the
     page, above `pn-raise-2`. What is constant is that the anchor is the
     surface furthest from the ground in lightness; which way that points
     is the ground's business. A card that reads as the subject on one
     theme and as an ordinary tile on the other is the failure this
     avoids.

     If this turns out to be the wrong call, it is one block of values per
     theme and nothing else. */
  --ink-bg: #0d1a1c;
  --ink-lift: #132427;
  --ink-raise: #1b3034;
  --ink-line: #263d41;
  --ink-track: #1b3034;
  --ink-text: #e6efec;
  --ink-dim: #9bb1ac;
  /* A status on the ink surface, which is dark in both schemes: the dark
     palette's fault and warn, so a dot on a message log reads the same
     in either. */
  --ink-fault: oklch(0.704 0.191 22.216);
  --ink-warn: oklch(0.8 0.16 80);

  /* Night: the chrome's own ground (ADR-0126). The sidebar and the phone's
     bottom bar are drawn on the brand's sky in both themes, so these are
     defined once, here, and never themed: a theme flip moves the page and
     leaves the chrome where it is. `night-dim` is the quietest thing that
     is ever text on it (7:1); an icon may go dimmer, a word may not. */
  --night-bg: #0d1a1c;
  --night-raise: #172a2d;
  --night-line: #263d41;
  --night-text: #e6efec;
  --night-dim: #9bb1ac;
  --night-accent: #6ee7b7;
  --night-accent-2: #67e8f9;
  --night-star: #ecfdf5;

  /* The mascot's inks, on the same night: the body's shadow side, the
     tail's shaded edge, and the one highlight. Unthemed like the rest of
     the night, because the sprite never leaves its plate. */
  --night-accent-shade: #2fb48b;
  --night-accent-2-shade: #1fa6c4;
  --night-highlight: #d1fae5;

  /* The sandbox band's hazard tape, and the ink written on it (ADR-0125).
     Unthemed: tape is tape on either ground, and the ink clears 10:1 on
     both stripes. */
  --sandbox-tape: #fbbf24;
  --sandbox-tape-2: #f59e0b;
  --sandbox-ink: #1f1300;

  /* The mode switch (spec 105): live and sandbox, worn by `mode.rs`'s
     `ModeSwitch` and `ModeTag`. Sandbox is the marked mode and live the
     unmarked normal (ADR-0125): live is the panel's own neutral, because
     the mode an operator is in all day should not be drawn in the colour
     this product keeps for faults, and sandbox is hazard-tape amber.
     Colour is never the only channel either carries: the icon and the
     words repeat the same distinction (FR-020). */
  --mode-live: oklch(0.5 0.02 213.5);
  --mode-sandbox: #d97706;
  /* The same two, as ink: for the mode's name written on its own tint
     (the selected mode switch, a mode tag). A mid-tone cannot clear 4.5:1
     on a tint of itself, so the text takes a deeper step of the same hue
     and the border and tint keep the mode colour. */
  --mode-live-ink: #2e3b39;
  --mode-sandbox-ink: #7c3a06;
  /* The supply's two answers and three lines, worn by the plate and the
     ladder. Served and constrained are numerically status-ok and
     status-busy and deliberately their own tokens: a constrained wire is
     the scheduler at capacity, not a busy dot, and the two must be able to
     move apart. The lines are IEC 60445's conductor colours -- brown,
     black, grey -- so an installer reads them without a key; black lifts
     to near-white on the dark ground. */
  --line-l1: #8a5a2b;
  --line-l2: #24292e;
  --line-l3: #8b949b;
  /* Text on a line's own colour, when a chip wears it as a ground. */
  --line-l1-ink: #ffffff;
  --line-l2-ink: #ffffff;
  --line-l3-ink: #101a1c;
  --price-cheap: oklch(0.51 0.16 152);
  --price-dear: oklch(0.55 0.19 30);
  /* The two kinds of current a tariff can price apart. Blue for AC and a
     warm orange for DC, their own tokens rather than the location or fault
     hue: a DC price is not a warning, and an AC price is not a place. */
  --kind-ac: oklch(0.5 0.13 240);
  --kind-dc: oklch(0.47 0.15 45);
  /* As ink, for the kind's name on its own tint laid over a tinted row --
     where AC measured 4.37:1. 4.91 each on the darkest row tint. */
  --kind-ac-ink: #00558f;
  --kind-dc-ink: #942e00;

  /* One hue per resource kind, worn by `ResourceChip` as a foreground over
     a low-alpha tint of itself -- never as body text. Kept apart from the
     status tones: status means health, resource means kind, and a violet
     circuit chip must never read as a busy dot. The charging station wears
     the brand's own emerald; it is the core object of the product. */
  --resource-environment: oklch(0.5 0.15 278);
  --resource-location: oklch(0.52 0.11 225);
  --resource-circuit: oklch(0.51 0.17 295);
  --resource-charging-station: oklch(0.51 0.13 163);
  --resource-outlet: oklch(0.51 0.12 185);
  --resource-transaction: oklch(0.47 0.13 75);
  --resource-analytics: oklch(0.51 0.16 255);
  --resource-simulator: oklch(0.54 0.17 330);

  /* Radius. Sharper than the existing console, deliberately: its root was
     0.625rem and its buttons carried 1.4x that, so a 2rem-tall button was
     rounded 14px -- most of the way to a pill, which reads as soft where
     this product is mostly dense tables and instrument readouts. The four
     steps below halve that at the top end.

     Explicit values rather than `calc()` off one root. The derived form
     scales the whole system from one number, which sounds better than it
     is: nobody rescales a radius system, and the price is that no test can
     tell you whether the steps are in order (CSS `calc` is not something
     a Rust test is going to evaluate) and the Radius page cannot print a
     true number beside a name. The ratios are still 1:2:3:4 if anyone
     wants to move them together. */
  --radius-sm: 0.125rem;
  --radius-md: 0.25rem;
  --radius-lg: 0.375rem;
  --radius-xl: 0.5rem;
  /* Above the control steps, and for surfaces only. The four steps above
     were chosen for a console of dense tables and instrument readouts,
     and they are still right for a row, an input or a badge. A card on
     the panel is a different object: it is large, it floats on the ground
     rather than sitting in a rule, and at 8px it reads as a clipped
     rectangle rather than as a surface. 1rem is the smallest radius that
     holds at that size.

     Surfaces only, deliberately. A 16px corner on a 20px-tall badge is a
     pill with extra steps, and if this step reaches controls the scale
     above it stops meaning anything. */
  --radius-2xl: 1rem;
  /* Porcelain (ADR-0212): a card, a table, a tile group on the washed
     ground, drawn with no edge and a soft shadow. Borderless, a surface
     needs more corner than a ruled one to read as an object, and this is
     the step the native apps' cards take. A group *inside* a card stays
     at 1rem, so the corners are concentric. */
  --radius-3xl: 1.5rem;
  --radius-pill: 999px;

  /* How far a glass surface blurs what is behind it. One value, unthemed:
     the frost is the same by day and by night, only its colour changes. */
  --glass-blur: 1.25rem;

  /* The curve of a corner, where the browser can draw one (`corner-shape`,
     Chromium 139+; Safari and Firefox fall back to the plain radius). The
     continuous corner SwiftUI draws: `squircle` is the named step, and a
     `superellipse(K)` between 1 (round) and 2 (squircle) tunes it. Pills
     stay capsules and do not take it. */
  --corner-continuous: squircle;

  /* Elevation. Composite shadows -- a tight contact shadow plus a wider
     ambient one -- because a single blur reads as a glow rather than as
     height.

     These are themed, unlike radius and spacing, and the dark values are
     not the light ones with more alpha. On the Night ground a shadow has
     almost nothing to darken, so elevation there is carried mostly by
     `--raised` and `--hairline`; the shadow only stops a surface looking
     pasted on. Raising the alpha instead produces a dark smudge that
     reads as dirt. See the Elevation page.

     `--shadow-md` was retuned for the panel's floating cards: wider and
     fainter than it was (2px/6px at 8%). A card that carries no border
     needs the shadow to describe its whole edge, and a tight dark one
     reads as a drawn outline rather than as height, while a wide faint
     one reads as the card lifting off the ground -- which is what it is
     doing. Retuned rather than joined by a fourth step: `--shadow-md` is
     already defined as "a panel that sits above the page and stays
     there", which is exactly a card, so a `--shadow-card` beside it
     would be the near-duplicate the Shape page's test exists to
     prevent. */
  --shadow-sm: 0 1px 2px oklch(0.148 0.004 228.8 / 8%);
  --shadow-md:
    0 4px 16px oklch(0.148 0.004 228.8 / 6%),
    0 1px 3px oklch(0.148 0.004 228.8 / 4%);
  --shadow-lg:
    0 8px 24px oklch(0.148 0.004 228.8 / 12%),
    0 2px 6px oklch(0.148 0.004 228.8 / 6%);

  /* Three families, named as `@fontsource-variable/*` declares them.
     `services/web` self-hosts these rather than using `next/font/google`,
     because that downloads from fonts.gstatic.com inside the build action;
     the same reasoning applies here and the same three families are named,
     so the two consoles render the same words the same way.

     The files are the renderer's to serve, not this crate's to embed:
     clients/console declares the faces in its index.html from three
     pinned http_files (MODULE.bazel). Where they are not served -- the
     showcase, today -- every stack falls through to a system font, which
     is close and not deterministic across machines; see libs/ui/AGENTS.md
     before writing a pixel test against any of this. */
  --font-sans:
    "Inter Variable", ui-sans-serif, system-ui, -apple-system, "Segoe UI",
    Roboto, sans-serif;
  --font-heading:
    "Space Grotesk Variable", "Inter Variable", ui-sans-serif, system-ui,
    sans-serif;
  --font-mono:
    "Geist Mono Variable", ui-monospace, SFMono-Regular, "SF Mono", Menlo,
    monospace;

  /* The type scale, and its line heights, carried across from the existing
     console rather than re-chosen. Each size is one token so that a
     component names a step and never a length -- the same rule colour
     follows. `sm` is the console's body size, not `base`: measured across
     `services/web/src/app` and `src/components`, `text-sm` appears 536
     times against 25 for `text-base`. A component library whose default
     body size is not the product's body size is one every call site has to
     correct. */
  /* Below the carried-across scale, and CSS-only: no `TextSize` offers it,
     so body copy can never reach for it. It names the 10px micro label an
     instrument draws -- a unit beside a figure, an axis tick, a step
     number -- which the component layer had written as a raw `0.625rem` in
     eleven places. Whether 10px is legible enough is a design question;
     one token is what makes answering it a one-line change. */
  --text-2xs: 0.625rem;
  --text-xs: 0.75rem;
  --text-xs-leading: 1rem;
  --text-sm: 0.875rem;
  --text-sm-leading: 1.25rem;
  --text-base: 1rem;
  --text-base-leading: 1.5rem;
  --text-lg: 1.125rem;
  --text-lg-leading: 1.75rem;
  --text-xl: 1.25rem;
  --text-xl-leading: 1.75rem;
  --text-2xl: 1.5rem;
  --text-2xl-leading: 2rem;
  --text-3xl: 1.875rem;
  --text-3xl-leading: 2.25rem;
  --text-4xl: 2.25rem;
  --text-4xl-leading: 2.5rem;
  --text-5xl: 3rem;
  --text-5xl-leading: 3.25rem;

  --weight-regular: 400;
  --weight-medium: 500;
  --weight-semibold: 600;

  /* The spacing scale. Eight steps, and every one of them is a value the
     existing console already leans on -- derived by counting Tailwind
     spacing utilities across `services/web/src/app` and `src/components`
     rather than invented:

       0.5 -> 2xs (84 uses)      3 -> lg  (414)
       1   -> xs  (190)          4 -> xl  (368)
       1.5 -> sm  (176)          6 -> 2xl (149)
       2   -> md  (468)          8 -> 3xl (29)

     The two in-between values it drops (2.5 and 5, 101 uses together) snap
     to a neighbour. That is the point of a scale: the steps it refuses are
     what stop a layout drifting into fourteen slightly different gaps. */
  --space-2xs: 0.125rem;
  --space-xs: 0.25rem;
  --space-sm: 0.375rem;
  --space-md: 0.5rem;
  --space-lg: 0.75rem;
  --space-xl: 1rem;
  --space-2xl: 1.5rem;
  --space-3xl: 2rem;

  /* Three steps above the console's range, for the public site.
     A dense tool tops out at 2rem: nothing in a table or a form wants more.
     A marketing page's section break does, and without these the site
     reached for arithmetic instead -- `calc(var(--space-3xl) * 2)` in
     prose.css, hard-coded paddings in the header and footer -- which is how
     one page came to start 2rem under the header, another 1rem and a third
     1.5rem. Ratio 1.5x, 2x, 3x on --space-3xl, so they stay on the scale
     rather than beside it. */
  --space-4xl: 3rem;
  --space-5xl: 4rem;
  --space-6xl: 6rem;

  /* The standard gaps: four roles, each pointing at a step. Reach for these
     before reaching for the scale, because a role survives a change to the
     scale and a raw step does not -- if `--gap-stack` should become 1rem,
     it changes here once rather than at every call site that happened to
     pick `--space-lg` for that reason.

     The roles are nested, smallest to largest, and the rhythm only reads if
     they stay ordered: things inside one control are closer than controls
     are to each other, which are closer than blocks, which are closer than
     sections. `gaps_are_ordered_smallest_to_largest` holds that. */
  --gap-inline: var(--space-sm);
  --gap-stack: var(--space-lg);
  --gap-block: var(--space-2xl);
  --gap-section: var(--space-3xl);

  /* The smallest thing a finger can hit: 44px, WCAG 2.5.5's target and
     what `touch.spec.ts` measures every control against on a phone. The
     floor of every button, field, menu item and row, and the hit area a
     text link grows to on a coarse pointer. It was a raw `2.75rem` in
     twenty places; as one token, a change to it is a change to all of
     them rather than to whichever ones a search found. */
  --touch-target: 2.75rem;

  /* The page frame. Three widths and a gutter, read by `container.rs` and
     by the scene -- so a page that wants to be as wide as the console's
     standard page is that width by name, not by repeating a number.

     Counted across the console this replaces rather than chosen:
     `max-w-6xl` (72rem) at 37 call sites, `max-w-2xl` (42rem) at 24,
     `max-w-3xl` (48rem) at 21. */
  --width-page: 72rem;
  --width-content: 48rem;
  --width-prose: 42rem;

  /* The side gutter. One token because it is the thing that actually goes
     wrong on a phone -- a max-width is easy to repeat correctly, a
     responsive inset is not, and when it is missing the text sits against
     the glass. It opens up below. */
  --gutter: var(--space-xl);

  /* Motion. Quiet on purpose: this is a tool read all day, so movement
     says where something came from and then gets out of the way. Three
     durations and two curves, and nothing picks its own.
     - `fast` for feedback on the thing touched (press, hover, a tab);
     - `base` for something appearing (a skeleton, a fade);
     - `slow` for a page moving (the push and pop between screens).
     `--ease-out` settles quickly; `--ease-move` is the long-tailed curve
     a native navigation stack uses, so a pushed page decelerates in.
     Reduced motion zeroes the durations below, so every rule that reads
     them stops moving without a query of its own. */
  --motion-fast: 120ms;
  --motion-base: 200ms;
  --motion-slow: 320ms;

  /* The mascot: four frames a cycle, each shown for 180ms, and the two
     sizes at which a 16-cell sprite stays sharp (two and four device
     pixels a cell). */
  --mascot-period: 720ms;
  --mascot-size: 2rem;
  --mascot-size-large: 4rem;
  --ease-out: cubic-bezier(0.2, 0, 0, 1);
  --ease-move: cubic-bezier(0.32, 0.72, 0, 1);
  /* How far a pressed control gives, and how long a load may take before
     a placeholder is worth showing: under that, the flash of a skeleton
     is the only thing the reader would notice. */
  --press-scale: 0.97;
  --skeleton-delay: 150ms;

  /* The sky's pointer offset, in pixels. `Scene` writes these inline as the
     pointer moves and the layers inside read them, each scaled by its own
     depth. Defined here so the rest pose is a real value rather than an
     unresolved var() -- an undefined custom property makes the whole
     `translate` declaration invalid, which would silently disable the
     parallax rather than centre it. */
  --parallax-x: 0px;
  --parallax-y: 0px;

  /* The shell's chrome, as lengths. Read by `shell.rs` and the
     components that sit in it. `--topbar-height` is the public site's
     header now; the console's page header is `--header-height`. */
  --sidebar-width: 15.5rem;
  /* The standing column (ADR-0213) and the rail it folds to. */
  --aside-width: 22.5rem;
  --rail-width: 3.5rem;
  --header-height: 3.5rem;
  /* The sandbox band across the top of every page in sandbox, and how
     much of it is showing: none, until `.shell` holds a band. */
  --band-height: 2.125rem;
  --chrome-band: 0px;
  /* A Table's column names, for its phone cards: each table sets its own
     on itself (libs/ui/src/table.rs); unset, a cell has no label. Slot 1
     is the card's heading and is never named. */
  --table-col-2: none;
  --table-col-3: none;
  --table-col-4: none;
  --table-col-5: none;
  --table-col-6: none;
  --table-col-7: none;
  --table-col-8: none;
  --list-width: 20rem;
  --topbar-height: 3.25rem;

  --bottombar-height: 4.75rem;

  /* A bottom sheet's ceiling and where its handle has been pulled to.
     `--sheet-peek` is how much of the box the sheet sits in stays showing
     above the tallest sheet, so the reader can see where they are (in the
     console that box is the page under the header: with the header, about
     what the mockup leaves of an 844px phone). `--sheet-drag` is written
     inline by `Sheet` while the handle is dragged, and this is its rest
     pose, so the panel's `translateY` is never an unresolved var(). */
  --sheet-peek: 5rem;
  --sheet-drag: 0px;

  /* A conductor's flow period. `ConductorTree` writes it inline per edge
     from the current flowing there; defined here so an edge that never
     writes one still animates rather than silently not. */
  --flow-period: 1.6s;

  /* A status tone's colour, set by base.css's tone map on anything that
     says `data-tone="status-*"` and read by its parts. Declared here so
     the token guard knows it; nothing reads this default. */
  --tone: var(--status-unknown);

  /* The sky's comet, as a dash length along its wake -- in the wake's own
     `pathLength` units, so unitless. This is the glow's tail; the core
     sets its own shorter one. Defined here for the same reason as the
     parallax above: `scene.css`'s keyframes are written as
     `calc(var(--comet-tail) - n)`, and an unresolved var() would make the
     whole offset invalid and leave the comet parked, not moving. */
  --comet-tail: 22;

  /* Where along the wake the comet's head is, 0 to 111. `scene.css`
     registers it with `@property` so it interpolates, and animates it;
     this is its rest value, for the same reason as the two above. */
  --comet-head: 0;

  /* The fuse demo's state (`FuseDemo`, site.css): set per state on
     `.fuse-demo` by `:has()` rules on its own inputs and read by its
     drawing -- the live colour (fault when the fuse trips), each car's
     share of its ring and the fuse's load (of 100), and how long current
     takes to run a wire. Declared here so the token guard knows them;
     these rest values are an empty garage with Flowion on. */
  --fuse-demo-live: var(--night-accent);
  --fuse-demo-reading: var(--ink-text);
  --fuse-demo-share: 0;
  --fuse-demo-load-on: 0;
  --fuse-demo-load-off: 0;
  --fuse-demo-period: 2s;

  /* The sky's artwork, by day. `Scene` follows the theme like every other
     component; what it draws -- the bolt, the comet, the ion, the nebulae,
     the horizon -- is the same on both grounds, in these colours. The
     stars are the one thing with nowhere to go on a pale ground, so a
     token carries their `display`. The star layers' own gradients stay in
     `scene.css`, where the literal-colour exemption for artwork lives. */
  --sky-ground: linear-gradient(to bottom, #f0fdfa, #ffffff 45%, #f8fafc);
  --sky-nebula-one: #10b98126;
  --sky-nebula-two: #22d3ee1f;
  --sky-nebula-three: #6366f114;
  --sky-stars: none;
  --sky-line: #04785733;
  --sky-star: #047857;
  --sky-star-glow: #6ee7b7;
  --sky-crown: #065f46;
  --sky-wake-glow: #34d399;
  --sky-wake-core: #047857;
  --sky-ion: #047857;
  --sky-horizon: rgba(52, 211, 153, 0.14);
  --sky-hairline: #04785766;
}

/* The gutter opens up once there is room for it. A token that varies by
   viewport rather than a media query per component: every page frame moves
   together, and `.container` never learns a breakpoint. */
@media (min-width: 40rem) {
  :root {
    --gutter: var(--space-2xl);
  }
}

@media (prefers-reduced-motion: reduce) {
  :root {
    --motion-fast: 0ms;
    --motion-base: 0ms;
    --motion-slow: 0ms;
    --press-scale: 1;
  }
}

/* Case 1: follow the operating system, unless something has explicitly
   asked for light. The dark ground is the brand's Night (#060a14) and the
   tones lift rather than invert: same hue, higher lightness, so a fault
   stays the same red idea at a legible contrast.

   The panel's steps on Night are spaced on purpose (2026-10-08): the
   sidebar's `--night-bg` is the floor, `--pn-bg` sits a step above it so
   the page reads as a surface on the chrome rather than a hole in it, and
   `--pn-raise`, `--pn-raise-2` and the anchor's `--ink-*` each sit three
   or four L* above the one below. Before this they were within a step or
   two of each other and the shell read as one slab. The contrast pairs
   in `contrast.rs` are the ceiling on how far any of them may rise. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --field: #1a282b;
    --field-line: #2c3d41;
    --field-hover: #213235;
    --logo-stage: #e8eeec;
    --logo-ink: #101a1c;
    --background: #060a14;
    --foreground: #f8fafc;
    --muted-foreground: #94a3b8;
    --hairline: #ffffff1a;
    --raised: #ffffff14;
    --status-ok: oklch(0.75 0.16 152);
    --status-busy: oklch(0.75 0.14 258);
    --status-warn: oklch(0.8 0.16 80);
    --status-fault: oklch(0.704 0.191 22.216);
    --status-unknown: oklch(0.723 0.014 214.4);
    --primary: oklch(0.72 0.13 166.913);
    --primary-foreground: oklch(0.18 0.045 166.913);
    --secondary: #213235;
    --secondary-foreground: oklch(0.985 0 0);
    --destructive: oklch(0.704 0.191 22.216);
    --muted: oklch(0.275 0.011 216.9);
    --border: oklch(1 0 0 / 10%);
    --ring: oklch(0.56 0.021 213.5);
    --brand-from: #6ee7b7;
    --brand-to: #67e8f9;
    --brand-star: #d1fae5;
    --brand-ion: #ecfdf5;
    --pn-bg: #111e21;
    --pn-raise: #182a2e;
    --pn-raise-2: #203439;
    --pn-line: #2a4044;
    --pn-track: #243a3f;
    --pn-bar: #46606a;
    --pn-text: #e4ecea;
    --pn-dim: #9aaeaa;
    --pn-wash-a: #6ee7b721;
    --pn-wash-b: #67e8f91c;
    --pn-edge: #ffffff0a;
    --glass: #1a2d3199;
    --glass-line: #ffffff1f;
    --glass-highlight: #ffffff1a;
    /* The anchor, inverted: on Night it is the lightest surface on the
       page rather than the darkest. See the note in :root. */
    --ink-bg: #213b41;
    --ink-lift: #284650;
    --ink-raise: #30525a;
    --ink-line: #3a5e66;
    --ink-track: #172a2e;
    --ink-text: #eef5f3;
    --ink-dim: #b0c4c0;
    --ink-fault: oklch(0.704 0.191 22.216);
    --ink-warn: oklch(0.8 0.16 80);
    --mode-live: oklch(0.723 0.014 214.4);
    --mode-sandbox: #fbbf24;
    --mode-live-ink: #cfdad8;
    --mode-sandbox-ink: #fcd34d;
    --line-l1: #d09a66;
    --line-l2: #eef1f3;
    --line-l3: #8a949b;
    --line-l1-ink: #1b1206;
    --line-l2-ink: #111827;
    --line-l3-ink: #101a1c;
    --price-cheap: oklch(0.75 0.16 152);
    --price-dear: oklch(0.74 0.17 30);
    --kind-ac: oklch(0.78 0.11 240);
    --kind-dc: oklch(0.78 0.13 45);
    --kind-ac-ink: #71c1f7;
    --kind-dc-ink: #fc9b6f;
    --resource-environment: oklch(0.78 0.13 278);
    --resource-location: oklch(0.78 0.11 225);
    --resource-circuit: oklch(0.78 0.15 295);
    --resource-charging-station: oklch(0.78 0.13 163);
    --resource-outlet: oklch(0.78 0.12 185);
    --resource-transaction: oklch(0.8 0.13 75);
    --resource-analytics: oklch(0.78 0.14 255);
    --resource-simulator: oklch(0.78 0.15 330);

    /* The sky's artwork, by night: the ground the brand is drawn on. See the
       `:root` block for what each is. */
    --sky-ground: #060a14;
    --sky-nebula-one: #10b98133;
    --sky-nebula-two: #22d3ee1f;
    --sky-nebula-three: #6366f11f;
    --sky-stars: block;
    --sky-line: #a7f3d029;
    --sky-star: #d1fae5;
    --sky-star-glow: #a7f3d0;
    --sky-crown: #ecfdf5;
    --sky-wake-glow: #a7f3d0;
    --sky-wake-core: #ecfdf5;
    --sky-ion: #d1fae5;
    --sky-horizon: rgba(52, 211, 153, 0.22);
    --sky-hairline: #6ee7b780;
    --shadow-sm: 0 1px 2px oklch(0 0 0 / 45%);
    --shadow-md: 0 2px 6px oklch(0 0 0 / 45%), 0 1px 2px oklch(0 0 0 / 35%);
    --shadow-lg: 0 8px 24px oklch(0 0 0 / 50%), 0 2px 6px oklch(0 0 0 / 35%);
  }
}

/* Case 2, dark. Any element, not just :root -- see the header. */
[data-theme="dark"] {
  --field: #1a282b;
  --field-line: #2c3d41;
  --field-hover: #213235;
  --logo-stage: #e8eeec;
  --logo-ink: #101a1c;
  color-scheme: dark;
  --background: #060a14;
  --foreground: #f8fafc;
  --muted-foreground: #94a3b8;
  --hairline: #ffffff1a;
  --raised: #ffffff14;
  --status-ok: oklch(0.75 0.16 152);
  --status-busy: oklch(0.75 0.14 258);
  --status-warn: oklch(0.8 0.16 80);
  --status-fault: oklch(0.704 0.191 22.216);
  --status-unknown: oklch(0.723 0.014 214.4);
  --primary: oklch(0.72 0.13 166.913);
  --primary-foreground: oklch(0.18 0.045 166.913);
  --secondary: #213235;
  --secondary-foreground: oklch(0.985 0 0);
  --destructive: oklch(0.704 0.191 22.216);
  --muted: oklch(0.275 0.011 216.9);
  --border: oklch(1 0 0 / 10%);
  --ring: oklch(0.56 0.021 213.5);
  --brand-from: #6ee7b7;
  --brand-to: #67e8f9;
  --brand-star: #d1fae5;
  --brand-ion: #ecfdf5;
  --pn-bg: #111e21;
  --pn-raise: #182a2e;
  --pn-raise-2: #203439;
  --pn-line: #2a4044;
  --pn-track: #243a3f;
  --pn-bar: #46606a;
  --pn-text: #e4ecea;
  --pn-dim: #9aaeaa;
  --pn-wash-a: #6ee7b721;
  --pn-wash-b: #67e8f91c;
  --pn-edge: #ffffff0a;
  --glass: #1a2d3199;
  --glass-line: #ffffff1f;
  --glass-highlight: #ffffff1a;
  /* The anchor, inverted: on Night it is the lightest surface on the
     page rather than the darkest. See the note in :root. */
  --ink-bg: #213b41;
  --ink-lift: #284650;
  --ink-raise: #30525a;
  --ink-line: #3a5e66;
  --ink-track: #172a2e;
  --ink-text: #eef5f3;
  --ink-dim: #b0c4c0;
  --ink-fault: oklch(0.704 0.191 22.216);
  --ink-warn: oklch(0.8 0.16 80);
  --mode-live: oklch(0.723 0.014 214.4);
  --mode-sandbox: #fbbf24;
  --mode-live-ink: #cfdad8;
  --mode-sandbox-ink: #fcd34d;
  --line-l1: #d09a66;
  --line-l2: #eef1f3;
  --line-l3: #8a949b;
  --line-l1-ink: #1b1206;
  --line-l2-ink: #111827;
  --line-l3-ink: #101a1c;
  --price-cheap: oklch(0.75 0.16 152);
  --price-dear: oklch(0.74 0.17 30);
  --kind-ac: oklch(0.78 0.11 240);
  --kind-dc: oklch(0.78 0.13 45);
  --kind-ac-ink: #71c1f7;
  --kind-dc-ink: #fc9b6f;
  --resource-environment: oklch(0.78 0.13 278);
  --resource-location: oklch(0.78 0.11 225);
  --resource-circuit: oklch(0.78 0.15 295);
  --resource-charging-station: oklch(0.78 0.13 163);
  --resource-outlet: oklch(0.78 0.12 185);
  --resource-transaction: oklch(0.8 0.13 75);
  --resource-analytics: oklch(0.78 0.14 255);
  --resource-simulator: oklch(0.78 0.15 330);

  /* The sky's artwork, by night: the ground the brand is drawn on. See the
     `:root` block for what each is. */
  --sky-ground: #060a14;
  --sky-nebula-one: #10b98133;
  --sky-nebula-two: #22d3ee1f;
  --sky-nebula-three: #6366f11f;
  --sky-stars: block;
  --sky-line: #a7f3d029;
  --sky-star: #d1fae5;
  --sky-star-glow: #a7f3d0;
  --sky-crown: #ecfdf5;
  --sky-wake-glow: #a7f3d0;
  --sky-wake-core: #ecfdf5;
  --sky-ion: #d1fae5;
  --sky-horizon: rgba(52, 211, 153, 0.22);
  --sky-hairline: #6ee7b780;
  --shadow-sm: 0 1px 2px oklch(0 0 0 / 45%);
  --shadow-md: 0 2px 6px oklch(0 0 0 / 45%), 0 1px 2px oklch(0 0 0 / 35%);
  --shadow-lg: 0 8px 24px oklch(0 0 0 / 50%), 0 2px 6px oklch(0 0 0 / 35%);
}

/* Case 2, light. This block is what makes the toggle work in both
   directions: without it, asking for light inside a dark-mode operating
   system re-states nothing and the media query's values stay in force. */
[data-theme="light"] {
  --field: oklch(1 0 0);
  --field-line: oklch(0.925 0.005 214.3);
  --field-hover: oklch(0.963 0.002 197.1);
  --logo-stage: #f5f8f7;
  --logo-ink: #101a1c;
  color-scheme: light;
  --background: oklch(1 0 0);
  --foreground: oklch(0.148 0.004 228.8);
  --muted-foreground: oklch(0.5 0.02 213.5);
  --hairline: oklch(0.148 0.004 228.8 / 12%);
  --raised: oklch(0.148 0.004 228.8 / 4%);
  --status-ok: oklch(0.51 0.16 152);
  --status-busy: oklch(0.54 0.19 258);
  --status-warn: oklch(0.54 0.16 70);
  --status-fault: oklch(0.552 0.245 27.325);
  --status-unknown: oklch(0.5 0.02 213.5);
  --primary: oklch(0.508 0.118 165.612);
  --primary-foreground: oklch(0.979 0.021 166.113);
  --secondary: oklch(0.967 0.001 286.375);
  --secondary-foreground: oklch(0.21 0.006 285.885);
  --destructive: oklch(0.577 0.245 27.325);
  --muted: oklch(0.963 0.002 197.1);
  --border: oklch(0.925 0.005 214.3);
  --ring: oklch(0.723 0.014 214.4);
  --brand-from: #059669;
  --brand-to: #0891b2;
  --brand-star: #047857;
  --brand-ion: #047857;
  --pn-bg: #eff3f1;
  --pn-raise: #ffffff;
  --pn-raise-2: #f4f7f6;
  --pn-line: #dde5e2;
  --pn-track: #e6ecea;
  --pn-bar: #a9bcb8;
  --pn-text: #101a1c;
  --pn-dim: #5a6b68;
  --pn-wash-a: #05966917;
  --pn-wash-b: #0891b217;
  --pn-edge: #ffffff00;
  --glass: #ffffffa3;
  --glass-line: #ffffffcc;
  --glass-highlight: #ffffffe6;
  --ink-bg: #0d1a1c;
  --ink-lift: #132427;
  --ink-raise: #1b3034;
  --ink-line: #263d41;
  --ink-track: #1b3034;
  --ink-text: #e6efec;
  --ink-dim: #9bb1ac;
  --ink-fault: oklch(0.704 0.191 22.216);
  --ink-warn: oklch(0.8 0.16 80);
  --mode-live: oklch(0.5 0.02 213.5);
  --mode-sandbox: #d97706;
  /* The same two, as ink: for the mode's name written on its own tint
     (the selected mode switch, a mode tag). A mid-tone cannot clear 4.5:1
     on a tint of itself, so the text takes a deeper step of the same hue
     and the border and tint keep the mode colour. */
  --mode-live-ink: #2e3b39;
  --mode-sandbox-ink: #7c3a06;
  --line-l1: #8a5a2b;
  --line-l2: #24292e;
  --line-l3: #8b949b;
  /* Text on a line's own colour, when a chip wears it as a ground. */
  --line-l1-ink: #ffffff;
  --line-l2-ink: #ffffff;
  --line-l3-ink: #101a1c;
  --price-cheap: oklch(0.51 0.16 152);
  --price-dear: oklch(0.55 0.19 30);
  /* The two kinds of current a tariff can price apart. Blue for AC and a
     warm orange for DC, their own tokens rather than the location or fault
     hue: a DC price is not a warning, and an AC price is not a place. */
  --kind-ac: oklch(0.5 0.13 240);
  --kind-dc: oklch(0.47 0.15 45);
  /* As ink, for the kind's name on its own tint laid over a tinted row --
     where AC measured 4.37:1. 4.91 each on the darkest row tint. */
  --kind-ac-ink: #00558f;
  --kind-dc-ink: #942e00;
  --resource-environment: oklch(0.5 0.15 278);
  --resource-location: oklch(0.52 0.11 225);
  --resource-circuit: oklch(0.51 0.17 295);
  --resource-charging-station: oklch(0.51 0.13 163);
  --resource-outlet: oklch(0.51 0.12 185);
  --resource-transaction: oklch(0.47 0.13 75);
  --resource-analytics: oklch(0.51 0.16 255);
  --resource-simulator: oklch(0.54 0.17 330);

  /* The sky's artwork, by day. `Scene` follows the theme like every other
     component; what it draws -- the bolt, the comet, the ion, the nebulae,
     the horizon -- is the same on both grounds, in these colours. The
     stars are the one thing with nowhere to go on a pale ground, so a
     token carries their `display`. The star layers' own gradients stay in
     `scene.css`, where the literal-colour exemption for artwork lives. */
  --sky-ground: linear-gradient(to bottom, #f0fdfa, #ffffff 45%, #f8fafc);
  --sky-nebula-one: #10b98126;
  --sky-nebula-two: #22d3ee1f;
  --sky-nebula-three: #6366f114;
  --sky-stars: none;
  --sky-line: #04785733;
  --sky-star: #047857;
  --sky-star-glow: #6ee7b7;
  --sky-crown: #065f46;
  --sky-wake-glow: #34d399;
  --sky-wake-core: #047857;
  --sky-ion: #047857;
  --sky-horizon: rgba(52, 211, 153, 0.14);
  --sky-hairline: #04785766;
  --shadow-sm: 0 1px 2px oklch(0.148 0.004 228.8 / 8%);
  --shadow-md:
    0 4px 16px oklch(0.148 0.004 228.8 / 6%),
    0 1px 3px oklch(0.148 0.004 228.8 / 4%);
  --shadow-lg:
    0 8px 24px oklch(0.148 0.004 228.8 / 12%),
    0 2px 6px oklch(0.148 0.004 228.8 / 6%);
}

/* base.css -- Type, icons, and the plain surfaces and layouts everything else stands on. */

/* --- status tones ------------------------------------------------------- */

/* Each status tone, mapped once: an element that says `data-tone` with a
   status carries its colour as --tone, and its parts read var(--tone)
   rather than every component writing out five rules of its own. A
   component that needs more than one colour from a tone (a tint, a
   border) still says so per tone. */
[data-tone="status-ok"] {
  --tone: var(--status-ok);
}

[data-tone="status-busy"] {
  --tone: var(--status-busy);
}

[data-tone="status-warn"] {
  --tone: var(--status-warn);
}

[data-tone="status-fault"] {
  --tone: var(--status-fault);
}

[data-tone="status-unknown"] {
  --tone: var(--status-unknown);
}
/* Component styles.
 *
 * Every value here resolves through a token from tokens.css. A literal
 * colour in this file is a bug, and `//libs/ui:token_layer_test` fails on
 * one rather than leaving it to review.
 *
 * Components are addressed by class and varied by data attribute
 * (`[data-tone]`, `[data-size]`) rather than by a class per variant. That
 * is what lets `status.rs` emit a tone as a token name and nothing else --
 * the Rust side never assembles a class string, so it cannot assemble a
 * wrong one.
 */

/* --- typography -------------------------------------------------------- */

.heading {
  margin: 0;
  font-family: var(--font-heading);
  font-weight: var(--weight-semibold);
  color: var(--foreground);
  letter-spacing: -0.01em;
  text-wrap: balance;
}

.text {
  margin: 0;
  font-family: var(--font-sans);
  color: var(--foreground);
}

.text[data-tone="muted"] {
  color: var(--muted-foreground);
}

/* For text inside something that has already chosen a colour -- a Button in
   any variant, a badge. Without it `.text`'s own colour wins on specificity
   and the container's variant silently does nothing to its label. */
.text[data-tone="warn"] {
  color: color-mix(in oklab, var(--status-warn) 55%, var(--pn-text));
}

.text[data-tone="inherit"] {
  color: inherit;
}

/* Inline text inside a heading is part of the heading: it takes the
   heading's face, size and weight rather than `.text`'s own, so a toned
   word in a title -- the brand-gradient "easy." on the sign-in screen --
   is a word in the title and not a caption beside it. */
.heading > .text[data-size] {
  font: inherit;
  letter-spacing: inherit;
}

/* The brand gradient as ink. The same device as `.error-scene__accent`
   and the wordmark's "ion"; a word, never a sentence. */
.text[data-tone="brand"] {
  background-image: linear-gradient(
    to right,
    var(--brand-from),
    var(--brand-to)
  );
  background-clip: text;
  color: transparent;
}

.text[data-weight="regular"] {
  font-weight: var(--weight-regular);
}

.text[data-weight="medium"] {
  font-weight: var(--weight-medium);
}

.text[data-weight="semibold"] {
  font-weight: var(--weight-semibold);
}

/* The scale applies to both, which is what makes level and size
   independent: a heading at `sm` is the same size as body text at `sm`. */
.heading[data-size="xs"],
.text[data-mono="true"] {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}

.text[data-breaks="true"] {
  overflow-wrap: anywhere;
}

.text[data-size="xs"] {
  font-size: var(--text-xs);
  line-height: var(--text-xs-leading);
}

.heading[data-size="sm"],
.text[data-size="sm"] {
  font-size: var(--text-sm);
  line-height: var(--text-sm-leading);
}

.heading[data-size="base"],
.text[data-size="base"] {
  font-size: var(--text-base);
  line-height: var(--text-base-leading);
}

.heading[data-size="lg"],
.text[data-size="lg"] {
  font-size: var(--text-lg);
  line-height: var(--text-lg-leading);
}

.heading[data-size="xl"],
.text[data-size="xl"] {
  font-size: var(--text-xl);
  line-height: var(--text-xl-leading);
}

.heading[data-size="2xl"],
.text[data-size="2xl"] {
  font-size: var(--text-2xl);
  line-height: var(--text-2xl-leading);
}

.heading[data-size="3xl"],
.text[data-size="3xl"] {
  font-size: var(--text-3xl);
  line-height: var(--text-3xl-leading);
}

.heading[data-size="4xl"],
.text[data-size="4xl"] {
  font-size: var(--text-4xl);
  line-height: var(--text-4xl-leading);
}

/* --- divider ------------------------------------------------------------ */

/* Plain, it is an `<hr>`: already a separator to assistive technology, so
   there is no ARIA here to get wrong. The browser's own border and margins
   are not wanted. */
.divider {
  width: 100%;
  height: 1px;
  margin: 0;
  border: 0;
  background: var(--border);
}

/* Labelled, it is a row -- rule, word, rule -- so the height above has to
   go back to auto or the word is clipped to a hairline. */
.divider--labelled {
  display: flex;
  align-items: center;
  gap: var(--gap-inline);
  height: auto;
  background: none;
}

.divider__rule {
  flex: 1;
  height: 1px;
  background: var(--border);
}

/* --- skeleton ----------------------------------------------------------- */

/* Heights are the type scale's own line heights rather than round numbers:
   a placeholder that is not the height of the text replacing it makes the
   page jump on load, which is the one thing a skeleton exists to prevent. */
.skeleton {
  border-radius: var(--radius-md);
  background: var(--muted);
}

.skeleton[data-shape="text"] {
  height: var(--text-sm-leading);
}

.skeleton[data-shape="heading"] {
  height: var(--text-xl-leading);
}

/* A control is the default `Button` height, which is also `.field__input`'s
   -- so a form waiting does not resize when it arrives. */
.skeleton[data-shape="control"] {
  height: 2rem;
}

.skeleton[data-shape="block"] {
  height: 8rem;
}

/* Fractions rather than the console's fixed `w-56`/`w-32`: a fixed width is
   wrong in any container narrower than itself. Varying them is the point --
   a stack of identical bars reads as a table, and text does not end in the
   same place twice. */
.skeleton[data-width="full"] {
  width: 100%;
}

.skeleton[data-width="long"] {
  width: 75%;
}

.skeleton[data-width="medium"] {
  width: 55%;
}

.skeleton[data-width="short"] {
  width: 30%;
}

/* Held back, then faded in: an answer that arrives inside
   `--skeleton-delay` never shows a placeholder at all, which is most of
   them. The box is laid out from the first frame either way -- only its
   paint waits -- so nothing moves when it appears. The pulse starts once
   the fade has finished, so the two never fight over `opacity`. */
.skeleton {
  animation: skeleton-appear var(--motion-base) var(--ease-out) var(--skeleton-delay) both;
}

@media (prefers-reduced-motion: no-preference) {
  .skeleton {
    animation:
      skeleton-appear var(--motion-base) var(--ease-out) var(--skeleton-delay) both,
      skeleton-pulse 1.8s ease-in-out calc(var(--skeleton-delay) + var(--motion-base)) infinite;
  }
}

@keyframes skeleton-appear {
  from {
    opacity: 0;
  }
}

@keyframes skeleton-pulse {
  50% {
    opacity: 0.5;
  }
}

/* --- form --------------------------------------------------------------- */

/* The `<form>` element carries no class and no rule. The fieldset below is
   the layout box, because it is the one that can be disabled, and a form
   that laid out its own single child would only be something for that child
   to fight. `display: contents` on either of them would do it too and is
   not worth the risk: it has a history of dropping elements out of the
   accessibility tree, and these two are the ones carrying the semantics.

   One stacking rhythm. The console this replaces uses gap-2, gap-3, gap-4
   and gap-6 across its 24 forms depending on which one you open; all four
   snap to the `stack` role, which is what a scale is for.

   `min-width: 0` is the load-bearing line. A `<fieldset>` ships
   `min-inline-size: min-content` in the UA stylesheet -- it is the one
   element that refuses to shrink below its content -- so without this it
   silently breaks overflow in any flex or grid parent, and shows up later
   as "the dialog will not scroll" a long way from its cause. The border,
   margin and padding are the rest of that stylesheet's inheritance from
   1996. */
/* A form that follows prose in a panel -- "Confirm your email" and its
   sentence, then the field -- keeps a stack's gap before it. The Panel
   stacks nothing itself, and without this the label sat on the sentence. */
.panel > .text + form {
  margin-top: var(--space-xl);
}

/* --- icon --------------------------------------------------------------- */

/* Only the size. Stroke, width, caps and joins are Lucide's own default
   attributes on the `<svg>` itself, where the package puts them, so an icon
   is still an icon with no stylesheet -- and so a drawing's own
   `fill="currentColor"` detail can override the inherited `none`, which a
   descendant rule here would take away from it.

   The three steps are the console's own counts: `size-4` (1rem) at 99 call
   sites, `size-3` at 35, `size-5` at 20. */
.icon {
  display: block;
  flex-shrink: 0;
}

/* Inside a line of words an icon travels with them: it wraps with the
   first word instead of taking a line of its own above the sentence. */
.text .icon {
  display: inline-block;
  vertical-align: -0.125em;
  margin-inline-end: 0.3em;
}

.icon[data-size="sm"] {
  width: 0.75rem;
  height: 0.75rem;
}

.icon[data-size="md"] {
  width: 1rem;
  height: 1rem;
}

.icon[data-size="lg"] {
  width: 1.25rem;
  height: 1.25rem;
}

/* --- panel ------------------------------------------------------------- */

/* Porcelain (ADR-0212), like `Card`: no edge, the deep corner, the soft
   shadow and Night's hairline of light. The glass variant below keeps an
   edge of its own, because it sits on the sky. */
.panel {
  background: var(--background);
  border: 0;
  border-radius: var(--radius-3xl);
  box-shadow:
    inset 0 1px 0 var(--pn-edge),
    var(--shadow-md);
}

.panel[data-elevation="raised"] {
  box-shadow: var(--shadow-sm);
}

.panel[data-elevation="floating"] {
  box-shadow: var(--shadow-md);
}

.panel[data-elevation="overlay"] {
  box-shadow: var(--shadow-lg);
}

.panel[data-padding="sm"] {
  padding: var(--space-lg);
}

.panel[data-padding="md"] {
  padding: var(--space-xl);
}

/* Frosted. The blur is the point: this exists for a card floating on the
   night sky, where the starfield has to stay legible *behind* the form
   rather than through it. `backdrop-filter` is the only way to get that --
   an opaque panel cuts a hole in the sky and a transparent one puts stars
   behind the text.

   `color-mix` against `--foreground` rather than a literal white, so the
   wash follows whatever ground it is on; on the scene that resolves to the
   near-white the console's own `bg-white/[0.04]` names. */
.panel[data-surface="glass"] {
  border: 1px solid color-mix(in oklch, var(--foreground), transparent 88%);
  background: color-mix(in oklch, var(--foreground), transparent 96%);
  /* Safari still wants the prefix, and Safari is where this is most likely
     to be seen -- the sign-in card on a phone. Unprefixed alone renders the
     wash without the blur there, which is legible but flat. */
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
}

/* The blur is expensive, and a device that cannot composite it renders the
   wash alone -- which is legible but flat. Nothing to do about that beyond
   keeping the wash readable on its own, which the value above is chosen
   for. */

.panel[data-padding="lg"] {
  padding: var(--space-2xl);
}

/* --- columns ------------------------------------------------------------ */

/* Peers in a row. `minmax(0, 1fr)` rather than `1fr`, so a long label in
   one column cannot push the others narrower than their share; and one
   column on a phone, where two half-width buttons are two small targets. */
/* --- address (address.rs) ------------------------------------------------ */

.address {
  display: flex;
  flex-direction: column;
  font-size: var(--text-sm);
  font-style: normal;
  line-height: var(--text-sm-leading);
}

/* --- layout: stack, cluster, split (layout.rs) ---------------------------- */

.stack {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.cluster {
  display: flex;
  flex-wrap: wrap;
  min-width: 0;
}

.cluster[data-align="center"] {
  align-items: center;
}

.cluster[data-align="baseline"] {
  align-items: baseline;
}

.cluster[data-align="end"] {
  align-items: flex-end;
}

.cluster[data-spread="true"] {
  justify-content: space-between;
}

.cluster[data-end="true"] {
  justify-content: flex-end;
}

:is(.stack, .cluster)[data-gap="2xs"] {
  gap: var(--space-2xs);
}

:is(.stack, .cluster)[data-gap="xs"] {
  gap: var(--space-xs);
}

:is(.stack, .cluster)[data-gap="sm"] {
  gap: var(--space-sm);
}

:is(.stack, .cluster)[data-gap="md"] {
  gap: var(--space-md);
}

:is(.stack, .cluster)[data-gap="lg"] {
  gap: var(--space-lg);
}

:is(.stack, .cluster)[data-gap="xl"] {
  gap: var(--space-xl);
}

/* A cluster's gap is the same step both ways, except a wrapping one keeps
   its rows a step closer than its columns: a wrapped line of tags reads
   as one group, not as two. */
.cluster[data-gap="lg"] {
  gap: var(--space-sm) var(--space-lg);
}

.cluster[data-gap="xl"] {
  gap: var(--space-md) var(--space-xl);
}

.split {
  display: grid;
  gap: var(--space-xl);
  align-items: start;
  min-width: 0;
}

.split[data-weight="main"] {
  grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr);
}

.split[data-weight="balanced"] {
  grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr);
}

.split[data-weight="even"] {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

/* The side is a column of cards, not half a page: it takes a card's width
   and the index beside it takes the rest. */
.split[data-weight="aside"] {
  grid-template-columns: minmax(0, 1fr) minmax(16rem, 20rem);
}

.columns {
  display: grid;
  gap: var(--gap-stack);
  width: 100%;
}

.columns[data-count="2"] {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

.columns[data-count="3"] {
  grid-template-columns: repeat(3, minmax(0, 1fr));
}

@media (max-width: 47.9375rem) {
  .columns[data-count="3"] {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 30rem) {
  .columns[data-count="2"],
  .columns[data-count="3"] {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* --- card -------------------------------------------------------------- */

/* Each section owns its inset, because the dividers have to reach both
   edges -- which they cannot do inside a padded container. That is why Card
   asks its Panel for `padding: none`. */
/* --- container ---------------------------------------------------------- */

/* The page frame. One rule decides both halves of it -- the maximum width
   and the gutter beside it -- so a page names a shape rather than repeating
   a number and a padding that drift apart. */
.container {
  width: 100%;
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.container[data-width="page"] {
  max-width: var(--width-page);
}

.container[data-width="content"] {
  max-width: var(--width-content);
}

.container[data-width="prose"] {
  max-width: var(--width-prose);
}

.container[data-width="full"] {
  max-width: none;
}

/* Nested inside a container that already has the gutter. Two gutters is a
   page that looks indented for no reason. */
.container[data-flush="true"] {
  padding-inline: 0;
}

/* The brand at hairline weight, closing the bar -- the same edge the
   marketing chrome closes its surfaces with. */
.hairline {
  height: 1px;
  background: linear-gradient(
    90deg,
    transparent,
    color-mix(in oklab, var(--brand-from) 60%, transparent) 30%,
    color-mix(in oklab, var(--brand-to) 60%, transparent) 70%,
    transparent
  );
}

/* A row inside a panel keeps its padding for its hover ground but gives
   it back as margin, so its text sits on the panel's own content edge
   like every heading and field beside it. */
.panel > .row {
  margin-inline: calc(-1 * var(--space-md));
}

.empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: var(--space-md);
  padding: var(--space-3xl) var(--space-xl);
  border: 1px dashed var(--pn-line);
  border-radius: var(--radius-xl);
  max-width: 32rem;
  margin: 0 auto;
}

.empty-state__action {
  margin-top: var(--space-md);
}

/* The failed state: what failed, what to do, and what support would ask
   for, in one left-aligned column in the middle of the page -- read top
   to bottom like a short note, not centred like a poster. The fault
   colour stays on the mark, so the page reads as paused rather than
   broken. */
.failed-state {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-xl);
  width: 100%;
  max-width: 28rem;
  margin: 0 auto;
  padding-block: var(--space-4xl) var(--space-3xl);
}

.failed-state__mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--touch-target);
  height: var(--touch-target);
  border-radius: var(--radius-pill);
  border: 1px solid color-mix(in oklch, var(--destructive), transparent 75%);
  background: color-mix(in oklch, var(--destructive), transparent 92%);
  color: color-mix(in oklab, var(--destructive) 78%, var(--foreground));
}

.failed-state__words {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

.failed-state__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-md);
}

.failed-state__details {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  width: 100%;
  padding-top: var(--space-xl);
  border-top: 1px solid var(--hairline);
}

.failed-state__details-label {
  font-size: var(--text-xs);
  line-height: var(--text-xs-leading);
  color: var(--muted-foreground);
}

.failed-state__well {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-lg);
  padding: var(--space-md) var(--space-lg);
  border-radius: var(--radius-lg);
  border: 1px solid var(--hairline);
  background: var(--raised);
}

/* Preformatted: the lines are what support matches against a log, so
   they break where they were written and nowhere else -- except a long
   path, which wraps rather than pushing the Copy button off the page. */
.failed-state__detail {
  min-width: 0;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  line-height: var(--text-xs-leading);
  color: var(--muted-foreground);
  white-space: pre-line;
  overflow-wrap: anywhere;
}

/* Continuous corners (ADR-0212): the curve SwiftUI draws, where the browser
   can draw it. A guarded enhancement -- a browser without `corner-shape`
   keeps the plain radius above, and nothing else changes. */
@supports (corner-shape: squircle) {
  .panel {
    corner-shape: var(--corner-continuous);
  }
}

@container shell (max-width: 68.6875rem) {
  /* Under the tablet width a split is one column: the side reads after the
     main, as it would on paper. The shell's own container, not the
     window, so the showcase's frames show the real thing. */
  .split[data-weight] {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* brand.css -- The logo, the mark and the wordmark, and the sign-in providers' own logos. */

/* --- provider logo ------------------------------------------------------ */

/* Three marks on three different grids -- GitHub draws on 16, the other two
   on 24 -- so the box is fixed here and each viewBox scales into it.
   Normalising the drawings themselves would mean redrawing somebody else's
   trademark. */
.provider-logo {
  display: block;
  flex-shrink: 0;
  width: 1rem;
  height: 1rem;
}

/* --- logo --------------------------------------------------------------- */

/* The lockup: the mark, a gap, and the name. The gap lives HERE and not on
   the wordmark, which is the whole reason the name has its own element --
   with both halves of "Flowion" as direct children of this row, the gap
   fell inside the word and the brand read "Flow ion". */
.logo {
  display: inline-flex;
  align-items: center;
  gap: var(--space-md);
  color: var(--foreground);
  text-decoration: none;
}

.logo[data-size="lg"],
.logo[data-size="xl"] {
  gap: var(--space-lg);
}

/* The lockup is usually the only interactive thing in a scene's header, so
   its focus ring has to be visible on the night ground as well as on the
   page's. `--ring` is themed; the scene rebinds it. */
.logo:focus-visible {
  outline: 2px solid var(--ring);
  outline-offset: var(--space-xs);
  border-radius: var(--radius-md);
}

/* --- mark --------------------------------------------------------------- */

/* A port of `services/web/public/brand/flowion-mark.svg`, with every colour
   in it resolved to a token -- which is what lets one component serve both
   grounds where the asset needs a `-light` twin. The widths and opacities
   below are the asset's own; they are here rather than on the elements so
   that the markup carries shape and the stylesheet carries weight. */
.mark {
  display: block;
  flex-shrink: 0;
}

/* The comet, thin and faint going in and thicker coming out. Both legs are
   drawn through a mask that is the bolt itself, so the wake passes behind
   the constellation -- see the `mask` in `logo.rs`. */
.mark__wake {
  stroke-linecap: round;
}

.mark__wake--far {
  stroke-width: 1.4;
}

.mark__wake--near {
  stroke-width: 2.1;
}

/* The bolt as a hairline. Held just under full strength: at 0.75 the
   constellation sits behind its own stars rather than in front of them,
   which is the difference between a constellation and a wireframe. */
.mark__bolt {
  stroke-width: 1.7;
  stroke-linejoin: round;
  opacity: 0.8;
}

/* Below 64px, the same six points filled. */
.mark__silhouette {
  stroke: none;
}

/* The rest of the sky: five stars that are not the constellation. Muted,
   because they are what the bolt is picked out *of*. */
.mark__sky {
  fill: var(--muted-foreground);
  opacity: 0.5;
}

.mark__star {
  fill: var(--brand-star);
}

/* The glow around each fixed star, and around the strike point. Low enough
   that it reads as air rather than as a second circle. */
.mark__halo {
  fill: var(--brand-star);
  opacity: 0.13;
}

/* The strike point, drawn as light rather than as a body. */
.mark__strike {
  fill: var(--brand-ion);
}

/* The one that moves. */
.mark__ion {
  fill: var(--brand-ion);
}

.mark__ion-glow {
  stroke: none;
}

/* The animated build ships its still beside it, and this is the switch:
   SMIL cannot be paused from CSS, so the reduced-motion fallback is a
   second drawing rather than a stopped one. `brand.md` requires the
   fallback; this is what makes it structural. */
.mark-motion {
  display: inline-flex;
  flex-shrink: 0;
}

.mark-motion .mark[data-motion="still"] {
  display: none;
}

@media (prefers-reduced-motion: reduce) {
  .mark-motion .mark[data-motion="animated"] {
    display: none;
  }

  .mark-motion .mark[data-motion="still"] {
    display: block;
  }
}

/* The comet drawing itself: one stroke the length of the whole arc. */
.mark__wake--drawn {
  stroke-width: 1.8;
}

.mark__comet-head {
  fill: var(--brand-ion);
}

.mark__comet-glow {
  stroke: none;
}

/* --- wordmark ----------------------------------------------------------- */

/* Live type, not outlines: Space Grotesk 600, tracking tight, per
   docs/architecture/brand.md. Both halves of the name share this one
   element and it has no gap of its own -- see `.logo`. */
.wordmark {
  font-family: var(--font-heading);
  font-weight: var(--weight-semibold);
  letter-spacing: -0.01em;
  line-height: 1;
  white-space: nowrap;
  color: var(--foreground);
}

.wordmark[data-size="xs"] {
  font-size: var(--text-xs);
}

.wordmark[data-size="sm"] {
  font-size: var(--text-sm);
}

.wordmark[data-size="base"] {
  font-size: var(--text-base);
}

.wordmark[data-size="lg"] {
  font-size: var(--text-lg);
}

.wordmark[data-size="xl"] {
  font-size: var(--text-xl);
}

.wordmark[data-size="2xl"] {
  font-size: var(--text-2xl);
}

.wordmark[data-size="3xl"] {
  font-size: var(--text-3xl);
}

.wordmark[data-size="4xl"] {
  font-size: var(--text-4xl);
}

/* The gradient device, spent once. `brand.md`'s deep emerald to deep cyan
   on light grounds, emerald to cyan on Night -- both live in the token
   layer, so this rule names no colour of its own. */
.wordmark__accent {
  background-image: linear-gradient(
    to bottom,
    var(--brand-from),
    var(--brand-to)
  );
  background-clip: text;
  color: transparent;
}

/* --- brand logo (brand_logo.rs) ------------------------------------------ */

/* Somebody else's logo, fitted to one box: a height, and a width it may not
   pass. Its colours are its owner's -- the file's -- and it stands on the
   logo stage in both themes, so a dark-ink logo never vanishes. A name
   written without a stage takes the text colour around it; on a stage
   (a plate, a tile), the stage's ink. */
.brand-logo {
  display: inline-flex;
  align-items: center;
  flex-shrink: 0;
  height: 1.375rem;
  max-width: 7.5rem;
}

.brand-logo[data-size="sm"] {
  height: 1rem;
  max-width: 5.5rem;
}

.brand-logo[data-size="lg"] {
  height: 1.75rem;
  max-width: 10rem;
}

/* A brand tile's stage: the logo is the tile. */
.brand-logo[data-size="xl"] {
  height: 2.5rem;
  max-width: 75%;
  justify-content: center;
}

/* A brand whose only logo is its symbol (Tesla's T) stands taller, or it
   reads as an icon beside the wordmarks. */
.brand-logo[data-size="xl"][data-mark="symbol"] {
  height: 3.75rem;
}

.brand-logo__img {
  display: block;
  height: 100%;
  width: auto;
  max-width: 100%;
  object-fit: contain;
  object-position: left center;
}

/* The same mark written into the page instead of fetched, for an app that
   serves no files. The span carries the sizing; the svg inside fills it. */
span.brand-logo__img > svg {
  display: block;
  height: 100%;
  width: auto;
  max-width: 100%;
}

.brand-logo[data-size="xl"] .brand-logo__img {
  object-position: center;
}

/* No logo here yet: the name, set in the display face at the logo's
   height, so a grid of logos and names still reads as one grid. */
.brand-logo__name {
  font-family: var(--font-heading);
  font-size: var(--text-lg);
  font-weight: var(--weight-semibold);
  line-height: 1;
  letter-spacing: -0.01em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.brand-logo[data-size="sm"] .brand-logo__name {
  font-size: var(--text-sm);
}

.brand-logo[data-size="xl"] .brand-logo__name {
  font-size: var(--text-2xl);
}

/* The square plate: the stage, holding a symbol, a wordmark fitted inside,
   or letters. */
.brand-logo[data-shape="symbol"] {
  justify-content: center;
  width: 2.5rem;
  height: 2.5rem;
  max-width: none;
  padding: var(--space-xs);
  box-sizing: border-box;
  border-radius: var(--radius-xl);
  border: 1px solid var(--pn-line);
  background: var(--logo-stage);
  color: var(--logo-ink);
}

.brand-logo[data-shape="symbol"][data-size="lg"] {
  width: 3.25rem;
  height: 3.25rem;
  padding: var(--space-sm);
  border-radius: var(--radius-2xl);
}

.brand-logo[data-shape="symbol"] .brand-logo__img {
  object-position: center;
}

.brand-logo[data-shape="symbol"][data-fit="wordmark"] {
  width: 4.5rem;
}

.brand-logo[data-shape="symbol"][data-fit="wordmark"][data-size="lg"] {
  width: 6rem;
}

.brand-logo__letters {
  font-family: var(--font-heading);
  font-size: var(--text-base);
  font-weight: var(--weight-semibold);
  letter-spacing: -0.02em;
}

.brand-logo[data-size="lg"] .brand-logo__letters {
  font-size: var(--text-xl);
}

/* Not built yet: grey, and quieter than every brand that works. */
/* On the image only: a name faded with `opacity` would be measured at its
   blended colour, and fail contrast. */
.brand-logo[data-quiet="true"]:has(.brand-logo__img) {
  filter: grayscale(1);
  opacity: 0.55;
}

/* --- mascot ------------------------------------------------------------- */

/* The assistant's face: a sixteen-pixel sprite drawn as one `rect` per run
   of a row, every pixel's ink a `data-ink` resolved here. It stands bare on
   the panel (the owner's call, 2026-10-07: no square of night behind it).
   Every ink is a Night token, the same on both themes: the body's emerald
   and cyan, and Night itself for the eyes, mouth and feet -- dark eyes on
   the emerald body read on either ground, where eyes that followed the
   panel's text colour went white on the dark theme (the owner, 2026-10-08:
   "creepy"). The feet vanish on the dark panel, which is fine.
   The grid is a 16-unit viewBox scaled crisp, so the sizes below are the
   only integer multiples that stay sharp: two rem is two device pixels a
   cell. */
.mascot {
  display: block;
  flex-shrink: 0;
  width: var(--mascot-size);
  height: var(--mascot-size);
  shape-rendering: crispEdges;
}

.mascot[data-large="true"] {
  width: var(--mascot-size-large);
  height: var(--mascot-size-large);
}

.mascot__px[data-ink="body"] {
  fill: var(--night-accent);
}

.mascot__px[data-ink="shade"] {
  fill: var(--night-accent-shade);
}

.mascot__px[data-ink="highlight"] {
  fill: var(--night-highlight);
}

.mascot__px[data-ink="spark"] {
  fill: var(--night-accent-2);
}

.mascot__px[data-ink="spark-shade"] {
  fill: var(--night-accent-2-shade);
}

.mascot__px[data-ink="night"] {
  fill: var(--night-bg);
}

.mascot__px[data-ink="star"] {
  fill: var(--night-star);
}

.mascot__px[data-ink="dim"] {
  fill: var(--night-dim);
}

/* At rest, and under reduced motion, the first frame and only it. Thinking
   renders four; with motion allowed they take turns, each visible for a
   quarter of `--mascot-period`, by a negative delay that places frame k in
   the k-th quarter. A stepped animation, so a frame is shown or not and
   never half-faded, which is how a sprite is meant to move. */
.mascot__frame {
  display: none;
}

.mascot__frame[data-frame="0"] {
  display: initial;
}

@media (prefers-reduced-motion: no-preference) {
  .mascot[data-mood="thinking"] .mascot__frame {
    display: initial;
    animation: mascot-cycle var(--mascot-period) steps(1) infinite;
  }

  .mascot[data-mood="thinking"] .mascot__frame[data-frame="1"] {
    animation-delay: calc(var(--mascot-period) * -0.75);
  }

  .mascot[data-mood="thinking"] .mascot__frame[data-frame="2"] {
    animation-delay: calc(var(--mascot-period) * -0.5);
  }

  .mascot[data-mood="thinking"] .mascot__frame[data-frame="3"] {
    animation-delay: calc(var(--mascot-period) * -0.25);
  }
}

@keyframes mascot-cycle {
  0%,
  24.99% {
    visibility: visible;
  }

  25%,
  100% {
    visibility: hidden;
  }
}

/* actions.css -- Buttons, and a row of them. */

/* --- button ------------------------------------------------------------ */

.button {
  display: inline-flex;
  flex-shrink: 0;
  align-items: center;
  justify-content: center;
  gap: var(--gap-inline);
  white-space: nowrap;
  border: 1px solid transparent;
  /* A pill (ADR-0212): the controls layer's shape, as in the apps. */
  border-radius: var(--radius-pill);
  cursor: pointer;
  /* An `<a class="button">` is a real case -- the error scene's way home has
     to be a document request, not a click handler -- and an anchor brings
     the browser's underline and visited colour with it. */
  text-decoration: none;
  user-select: none;
}

.button:focus-visible {
  outline: none;
  border-color: var(--ring);
  box-shadow: 0 0 0 3px color-mix(in oklch, var(--ring), transparent 70%);
}

.button:disabled {
  pointer-events: none;
  opacity: 0.5;
}

/* Fills its container. A layout decision the caller cannot otherwise reach,
   because the component owns its own `display` -- and every button in a
   sign-in card is this. */
.button[data-full-width="true"] {
  width: 100%;
}

/* The leading mark: a provider's logo, a status glyph. Sized here rather
   than by whatever is passed in, so that a 16-unit viewBox and a 24-unit
   one come out the same height beside the same label. */
.button__icon {
  display: inline-flex;
  flex-shrink: 0;
  align-items: center;
}

.button__icon svg {
  width: 1rem;
  height: 1rem;
}

/* The check arrives with a small settle, once: the moment an action
   worked is the one place a control is allowed to say so. */
.button[data-done="true"] .button__icon {
  animation: button-done var(--motion-base) var(--ease-move) both;
}

@keyframes button-done {
  from {
    opacity: 0;
    transform: scale(0.6);
  }
}

.button[data-size="xs"] .button__icon svg {
  width: 0.875rem;
  height: 0.875rem;
}

.button:not(:disabled):active {
  transform: translateY(1px);
}

/* Sizes. `min-height` rather than `height` below the mobile breakpoint, so
   a dense desktop toolbar renders exactly as before while a touch target
   still reaches FR-026's 44px. */
.button[data-size="xs"] {
  height: 1.5rem;
  padding: 0 var(--space-md);
  gap: var(--space-xs);
}

.button[data-size="sm"] {
  height: 2rem;
  padding: 0 var(--space-lg);
  gap: var(--space-xs);
}

/* 40px, level with a field (`.field__input`), and the row of both reads
   as one height. */
.button[data-size="default"] {
  height: 2.5rem;
  padding: 0 var(--space-xl);
}

.button[data-size="lg"] {
  height: 2.75rem;
  padding: 0 var(--space-2xl);
}

@media (pointer: coarse) {
  .button {
    min-height: var(--touch-target);
    min-width: var(--touch-target);
  }
}

/* Variants.
 *
 * Hover darkens toward the foreground rather than fading the fill. A
 * `background: var(--primary)` at 80% opacity composites against the page,
 * and the lighter result dropped the label to 3.43:1 -- below WCAG AA's
 * 4.5:1, which applies to text as presented, hover included. `color-mix`
 * also behaves on the dark ground, where `--foreground` is light. See
 * libs/ui/src/button.rs for the whole story. */
.button[data-variant="default"] {
  background: var(--primary);
  color: var(--primary-foreground);
}

@media (hover: hover) {
  .button[data-variant="default"]:not(:disabled):hover {
    background: color-mix(in oklch, var(--primary), var(--foreground) 10%);
  }
}

/* On the anchor's ground (ADR-0126): Night in the light theme, the lifted
   Night surface in the dark one, so it stays a surface there and never a
   hole. Its icon takes the accent, the way the sidebar's current one does. */
.button[data-variant="night"] {
  border-color: var(--ink-bg);
  background: var(--ink-bg);
  color: var(--ink-text);
  box-shadow: inset 0 1px 0 color-mix(in oklab, var(--ink-text) 8%, transparent);
}

.button[data-variant="night"] .button__icon {
  color: var(--night-accent);
}

@media (hover: hover) {
  .button[data-variant="night"]:not(:disabled):hover {
    background: var(--ink-raise);
  }
}

.button[data-variant="outline"] {
  border-color: var(--field-line);
  background: var(--field);
  color: var(--foreground);
}

@media (hover: hover) {
  .button[data-variant="outline"]:not(:disabled):hover {
    background: var(--field-hover);
  }
}

.button[data-variant="secondary"] {
  background: var(--secondary);
  color: var(--secondary-foreground);
}

@media (hover: hover) {
  .button[data-variant="secondary"]:not(:disabled):hover {
    background: color-mix(in oklch, var(--secondary), var(--foreground) 5%);
  }
}

.button[data-variant="ghost"] {
  background: transparent;
  color: var(--foreground);
}

@media (hover: hover) {
  .button[data-variant="ghost"]:not(:disabled):hover {
    background: var(--muted);
  }
}

.button[data-variant="destructive"] {
  background: color-mix(in oklch, var(--destructive), transparent 90%);
  /* Pulled toward the ink, as .alert[data-tone="danger"] is: the bare fill
     token measured 3.68:1 on this tint (//clients/site:a11y.check, in the
     Try it confirmation). 70% leaves room for the darker hover tint. */
  color: color-mix(in oklab, var(--destructive) 70%, var(--foreground));
}

@media (hover: hover) {
  .button[data-variant="destructive"]:not(:disabled):hover {
    background: color-mix(in oklch, var(--destructive), transparent 80%);
  }
}

.button[data-variant="link"] {
  background: transparent;
  color: var(--primary);
  text-underline-offset: 4px;
}

@media (hover: hover) {
  .button[data-variant="link"]:not(:disabled):hover {
    text-decoration: underline;
  }
}

/* --- button row ---------------------------------------------------------- */

/* A row of buttons answering one question (ButtonRow). A label that is a
   sentence wraps inside its button rather than squeezing the row; the
   button keeps a control's height as its floor, and a touch target's on a
   coarse pointer. */
.button-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-sm);
}

.button-row > .button {
  flex-shrink: 1;
  min-width: 0;
  white-space: normal;
  text-align: left;
  height: auto;
  min-height: 2.5rem;
  padding-block: var(--space-xs);
}

@media (pointer: coarse) {
  .button-row > .button {
    min-height: var(--touch-target);
  }
}

/* --- action bar (action_bar.rs) -------------------------------------------- */

.action-bar {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  flex-wrap: wrap;
}

@container shell (max-width: 47.9375rem) {
  /* A page's own verb on a phone: held above the tab bar wherever the page
     is scrolled, full width, and the page leaves room for it at its foot. */
  .shell .action-bar {
    position: fixed;
    left: 0;
    right: 0;
    bottom: calc(var(--bottombar-height) + env(safe-area-inset-bottom, 0px));
    z-index: 25;
    flex-wrap: nowrap;
    padding: var(--space-md) var(--space-xl);
    background: color-mix(in oklab, var(--pn-bg) 92%, transparent);
    border-top: 1px solid var(--pn-line);
    backdrop-filter: blur(16px);
  }

  .shell .action-bar > .button {
    flex: 1;
    min-height: 3rem;
    font-size: var(--text-base);
    border-radius: var(--radius-2xl);
  }

  .shell .page:has(.action-bar) {
    padding-bottom: 5.5rem;
  }
}

/* forms.css -- Every control that takes an answer: fields, pickers, choices, switches. */

.form__fields {
  display: flex;
  flex-direction: column;
  gap: var(--gap-stack);

  /* `flex-start`, not the default `stretch`. A column flex container
     stretches every child across, which silently makes every button in
     every form full width -- and `Button`'s own `full_width` prop then
     means nothing wherever it is most used. `.field` carries its own
     `width: 100%`, so fields still fill the column; a button is its natural
     size until it is asked not to be. */
  align-items: flex-start;
  min-width: 0;
  margin: 0;
  padding: 0;
  border: 0;
}

/* --- input ------------------------------------------------------------- */

.field {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  width: 100%;
  max-width: 22rem;
  text-align: left;
}

.field__label {
  display: flex;
  align-items: center;
  gap: var(--space-2xs);
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  line-height: var(--text-sm-leading);
  font-weight: var(--weight-medium);
  color: var(--foreground);
}

/* Not a colour-only signal: the asterisk carries an aria-label, and the
   input carries `required`. Either alone is silent to somebody. */
.field__required {
  color: var(--destructive);
}

/* 40px (ADR-0212): level with a default button, the 44px touch floor on
   a phone, and a corner one step inside the group it sits in. */
.field__input {
  width: 100%;
  height: 2.5rem;
  padding: 0 var(--space-lg);
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  line-height: var(--text-sm-leading);
  color: var(--foreground);
  background: var(--field);
  border: 1px solid var(--field-line);
  border-radius: var(--radius-xl);
}

/* Several lines: the same face and border, as tall as its rows, and only
   the height can be dragged. */
textarea.field__input {
  height: auto;
  padding: var(--space-sm) var(--space-md);
  resize: vertical;
}

/* The input and, when there is one, the fixed text it continues. */
.field__control {
  display: flex;
  min-width: 0;
}

.field__input[data-mono="true"] {
  font-family: var(--font-mono);
}

/* A prefixed field is one box: the prefix is a tinted well inside its
   left edge, and the box, not the input, draws the border and the ring. */
.field:has(> .field__control[data-prefixed="true"]) {
  max-width: 36rem;
}

.field:has(> .field__control[data-suffixed="true"]) {
  max-width: 10rem;
}

.field__control:is([data-prefixed="true"], [data-suffixed="true"]) {
  border: 1px solid var(--field-line);
  border-radius: var(--radius-md);
  background: var(--field);
  overflow: hidden;
}

.field__control:is([data-prefixed="true"], [data-suffixed="true"]) .field__input {
  flex: 1 1 8rem;
  min-width: 0;
  border: 0;
  border-radius: 0;
}

.field__control:is([data-prefixed="true"], [data-suffixed="true"]) .field__input:focus-visible {
  box-shadow: none;
}

.field__control:is([data-prefixed="true"], [data-suffixed="true"]):focus-within {
  border-color: var(--ring);
  box-shadow: 0 0 0 3px color-mix(in oklch, var(--ring), transparent 70%);
}

.field[data-invalid="true"] .field__control:is([data-prefixed="true"], [data-suffixed="true"]) {
  border-color: var(--destructive);
}

/* A unit after the value: "A", "kW". The value's own words, so read with
   the field like the prefix is. */
.field__suffix {
  display: flex;
  align-items: center;
  flex: 0 0 auto;
  padding: 0 var(--space-sm);
  font-size: var(--text-sm);
  color: var(--muted-foreground);
  background: var(--muted);
  border-left: 1px solid var(--border);
}

.field__prefix {
  display: flex;
  align-items: center;
  flex: 0 1 auto;
  min-width: 0;
  padding: 0 var(--space-sm);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--muted-foreground);
  background: var(--muted);
  border-right: 1px solid var(--border);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* On a phone the prefix is most of the width: it takes its own line in
   the box, above the value, rather than squeezing the value to nothing. */
@media (max-width: 30rem) {
  .field__control[data-prefixed="true"] {
    flex-direction: column;
  }

  .field__control[data-prefixed="true"] .field__input {
    flex: 0 0 auto;
  }

  .field__prefix {
    padding-block: var(--space-2xs);
    border-right: 0;
    border-bottom: 1px solid var(--border);
    white-space: normal;
    overflow-wrap: anywhere;
  }
}

.field__input::placeholder {
  color: var(--muted-foreground);
}

.field__input:focus-visible {
  outline: none;
  border-color: var(--ring);
  box-shadow: 0 0 0 3px color-mix(in oklch, var(--ring), transparent 70%);
}

.field__input:disabled {
  cursor: not-allowed;
  opacity: 0.5;
}

/* The fourth signal, and the least load-bearing: the message, the
   role=alert, and aria-invalid all carry this before colour does. */
.field[data-invalid="true"] .field__input {
  border-color: var(--destructive);
}

.field[data-invalid="true"] .field__input:focus-visible {
  border-color: var(--destructive);
  box-shadow: 0 0 0 3px color-mix(in oklch, var(--destructive), transparent 75%);
}

.field__error {
  margin: 0;
  font-family: var(--font-sans);
  font-size: var(--text-xs);
  line-height: var(--text-xs-leading);
  color: var(--destructive);
}

/* 1rem on touch: Safari zooms the page into any input under 16px on
   focus and does not zoom back out. */
@media (pointer: coarse) {
  .field__input {
    min-height: var(--touch-target);
    font-size: var(--text-base);
  }
}

/* --- segmented control -------------------------------------------------- */

/* A few short answers in one well; the chosen one is lifted out of it. */
.segmented {
  display: inline-flex;
  flex-wrap: wrap;
  gap: 3px;
  padding: 3px;
  border-radius: var(--radius-pill);
  background: var(--muted);
  border: 1px solid var(--border);
}

.segmented__option {
  min-height: 2.125rem;
  padding: 0 var(--space-lg);
  border: 0;
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--muted-foreground);
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  cursor: pointer;
  transition: background-color var(--motion-fast) var(--ease-out),
    color var(--motion-fast) var(--ease-out);
}

@media (hover: hover) {
  .segmented__option:hover:not([aria-pressed="true"], [aria-current="page"]):not(:disabled) {
    color: var(--foreground);
  }
}

.segmented__option[aria-pressed="true"],
.segmented__option[aria-current="page"] {
  background: var(--background);
  color: var(--foreground);
  font-weight: var(--weight-semibold);
  box-shadow: var(--shadow-sm);
}

/* A `SegmentedNav`'s answers are links: laid out like the buttons. */
a.segmented__option {
  display: inline-flex;
  align-items: center;
  text-decoration: none;
}

.segmented__option:focus-visible {
  outline: 2px solid var(--ring);
  outline-offset: 1px;
}

.segmented__option:disabled {
  cursor: not-allowed;
  opacity: 0.5;
}

/* Both ways: an answer as short as "4 h" is narrower than a finger. */
@media (pointer: coarse) {
  .segmented__option {
    min-height: var(--touch-target);
    min-width: var(--touch-target);
  }
}

/* On a phone the answers share the width in equal columns, a long one
   taking two lines, rather than one of them dropping to a row alone. */
@media (max-width: 30rem) {
  .segmented {
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: minmax(0, 1fr);
    width: 100%;
  }

  .segmented__option {
    padding: var(--space-xs) var(--space-sm);
    line-height: var(--text-sm-leading);
  }
}

/* A shown question at the start of the row, its answers at the end;
   the answers drop under the question when the row runs out. */
.segmented-field {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-md) var(--space-xl);
}

.segmented-field__text {
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
  min-width: 0;
  flex: 1 1 14rem;
}

.segmented-field__question {
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  color: var(--foreground);
}

.segmented-field__hint {
  font-size: var(--text-xs);
  line-height: var(--text-xs-leading);
  color: var(--muted-foreground);
}

/* A toggle that fires at once, not a mode and not an acknowledgement --
   see switch.rs's own doc comment for the three-way distinction. The knob
   and track are shaped exactly like `.mode-toggle`'s; the colour differs
   because this is a real on/off, not sandbox's amber. */
.switch {
  display: inline-flex;
  align-items: center;
  gap: var(--space-sm);
  border: none;
  background: none;
  padding: 0;
  color: var(--pn-text);
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  cursor: pointer;
}

.switch:focus-visible {
  outline: 2px solid var(--brand-from);
  outline-offset: 2px;
  border-radius: var(--radius-md);
}

.switch:disabled {
  pointer-events: none;
  opacity: 0.5;
}

.switch__word {
  line-height: 1;
}

.switch__track {
  position: relative;
  display: inline-block;
  width: 2.5rem;
  height: 1.375rem;
  border-radius: var(--radius-pill);
  background: var(--pn-dim);
}

.switch__knob {
  position: absolute;
  top: 0.125rem;
  left: 0.125rem;
  width: 1.125rem;
  height: 1.125rem;
  border-radius: var(--radius-pill);
  background: var(--pn-raise);
  box-shadow: var(--shadow-sm);
  transition: transform var(--motion-fast) var(--ease-out);
}

.switch[data-checked="true"] .switch__track {
  background: var(--status-ok);
}

.switch[data-checked="true"] .switch__knob {
  transform: translateX(1.125rem);
}

@media (pointer: coarse) {
  .switch {
    min-height: var(--touch-target);
  }
}

/* A secret shown once -- see libs/ui/src/secret_reveal.rs. The value is
   selectable text in a well, so it can be copied by hand. */
.secret-reveal {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

.secret-reveal__label {
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  color: var(--pn-text);
}

.secret-reveal__well {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  padding: var(--space-sm) var(--space-sm) var(--space-sm) var(--space-md);
  border: 1px solid var(--pn-line);
  border-radius: var(--radius-md);
  background: var(--pn-track);
}

.secret-reveal__value {
  flex: 1;
  min-width: 0;
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  color: var(--pn-text);
  overflow-wrap: anywhere;
  user-select: all;
}

.secret-reveal__note {
  margin: 0;
  font-size: var(--text-sm);
  color: var(--pn-dim);
}

.secret-reveal__note strong {
  color: var(--pn-text);
  font-weight: var(--weight-semibold);
}

/* A checkbox and its statement. */
.checkbox {
  display: flex;
  align-items: center;
  gap: var(--space-md);
}

.checkbox__input {
  width: 1rem;
  height: 1rem;
  margin: 0;
  flex-shrink: 0;
  accent-color: var(--primary);
}

.checkbox__input:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px color-mix(in oklch, var(--ring), transparent 70%);
  border-radius: var(--radius-sm);
}

.checkbox__input:disabled {
  cursor: not-allowed;
  opacity: 0.5;
}

.checkbox__label {
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  line-height: var(--text-sm-leading);
  color: var(--foreground);
}

/* The identifier a choice stands for, under its words. */
.checkbox__detail {
  display: block;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--muted-foreground);
}

/* --- choice card --------------------------------------------------------- */

/* A radio option as a card. The real input is inside and invisible; the
   dot and the border are both drawn from its state, so they cannot
   disagree with what a screen reader announces. */
.choice {
  display: flex;
  gap: var(--space-lg);
  padding: var(--space-lg);
  border: 0;
  border-radius: var(--radius-2xl);
  background: var(--pn-raise-2);
  cursor: pointer;
  color: var(--pn-text);
}

/* One that cannot be picked, with its reason under it. Muted by colour,
   not opacity: the reason is what the reader came to read, and a faded
   sentence is one that fails contrast. */
.choice[data-disabled="true"] {
  cursor: not-allowed;
  background: var(--pn-bg);
  color: var(--pn-dim);
}

.choice[data-disabled="true"] .choice__dot {
  border-style: dashed;
}

.choice__refusal {
  font-size: var(--text-xs);
  color: color-mix(in oklab, var(--status-warn) 70%, var(--pn-text));
}

.choice[data-selected="true"] {
  box-shadow: inset 0 0 0 1px var(--brand-from);
  background: color-mix(in oklab, var(--brand-from) 6%, var(--pn-raise));
}

.choice:focus-within {
  outline: none;
  box-shadow: 0 0 0 3px color-mix(in oklch, var(--ring), transparent 70%);
}

/* Off-screen rather than display:none, so it stays focusable and the
   arrow keys still walk the group. */
.choice__input {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

.choice__dot {
  flex-shrink: 0;
  width: 1.125rem;
  height: 1.125rem;
  margin-top: 1px;
  border-radius: var(--radius-pill);
  border: 2px solid var(--pn-bar);
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.choice[data-selected="true"] .choice__dot {
  border-color: var(--brand-from);
}

.choice[data-selected="true"] .choice__dot::after {
  content: "";
  width: 0.5rem;
  height: 0.5rem;
  border-radius: var(--radius-pill);
  background: var(--brand-from);
}

.choice__text {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  min-width: 0;
}

.choice__title {
  display: flex;
  align-items: center;
  gap: var(--space-md);
}

.choice__kicker {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--pn-dim);
}

/* A tile told apart by its mark: a brand to sign in with. The figure
   stands on an inset above the name, the dot in the corner. */
.choice[data-figure="true"] {
  position: relative;
  flex-direction: column;
  align-items: stretch;
  gap: var(--space-md);
  padding: var(--space-md);
}

.choice[data-figure="true"] .choice__dot {
  position: absolute;
  top: var(--space-md);
  right: var(--space-md);
  margin: 0;
}

.choice__figure {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 4.5rem;
  border-radius: var(--radius-md);
  background: var(--pn-bg);
}

.choice[data-figure="true"] .choice__text {
  align-items: center;
  text-align: center;
}

.choice-group {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  margin: 0;
  padding: 0;
  border: 0;
  min-width: 0;
}

.choice-group__legend {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* A few short options in one row, while they fit. The legend is a
   fieldset's own and is not a grid item. */
.choice-group[data-across="true"] {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
}

/* Tiles told apart by a mark need less room than a sentence does: two
   brands sit side by side on a phone. */
.choice-group[data-across="true"]:has(> .choice[data-figure="true"]) {
  grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
}

/* A form asking several questions in a row names each one. */
.choice-group[data-shown="true"] > .choice-group__legend {
  position: static;
  width: auto;
  height: auto;
  margin: 0;
  padding: 0 0 var(--space-md);
  overflow: visible;
  clip: auto;
  white-space: normal;
  font-size: var(--text-sm);
  line-height: var(--text-sm-leading);
  font-weight: var(--weight-medium);
  color: var(--foreground);
}

/* --- pin ---------------------------------------------------------------- */

.pin {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  width: 100%;
  max-width: 22rem;
  text-align: left;
}

.pin__label {
  display: flex;
  align-items: center;
  gap: var(--space-2xs);
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  line-height: var(--text-sm-leading);
  font-weight: var(--weight-medium);
  color: var(--foreground);
}

.pin__required {
  color: var(--destructive);
}

/* The real input lies over the cells, invisible but focusable; the cells
   below it draw what it holds. */
.pin__cells {
  position: relative;
  display: grid;
  grid-template-columns: repeat(4, var(--touch-target));
  gap: var(--space-md);
}

.pin__cells[data-length="6"] {
  grid-template-columns: repeat(6, 2.25rem);
}

.pin__input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  cursor: text;
  font-size: var(--text-lg);
}

.pin__cell {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 3rem;
  font-family: var(--font-mono);
  font-size: var(--text-xl);
  font-variant-numeric: tabular-nums;
  color: var(--foreground);
  background: var(--field);
  border: 1px solid var(--field-line);
  border-radius: var(--radius-md);
  pointer-events: none;
}

.pin__input:focus-visible ~ .pin__cell[data-active="true"] {
  border-color: var(--ring);
  box-shadow: 0 0 0 3px color-mix(in oklch, var(--ring), transparent 70%);
}

.pin__input:disabled ~ .pin__cell {
  opacity: 0.5;
}

.pin[data-invalid="true"] .pin__cell {
  border-color: var(--destructive);
}

.pin__error {
  margin: 0;
  font-family: var(--font-sans);
  font-size: var(--text-xs);
  line-height: var(--text-xs-leading);
  color: var(--destructive);
}

/* --- pick list -------------------------------------------------------- */

.pick-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  width: 100%;
}

.pick-list__search {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  height: 2.5rem;
  padding: 0 var(--space-lg);
  background: var(--field);
  border: 1px solid var(--field-line);
  border-radius: var(--radius-xl);
  color: var(--muted-foreground);
}

.pick-list__search:focus-within {
  border-color: var(--ring);
  box-shadow: 0 0 0 3px color-mix(in oklch, var(--ring), transparent 70%);
}

.pick-list__search-icon {
  display: inline-flex;
  flex-shrink: 0;
}

.pick-list__input {
  flex: 1;
  min-width: 0;
  border: 0;
  background: none;
  outline: none;
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  color: var(--foreground);
}

.pick-list__rows {
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
}

.pick-list__empty {
  padding: var(--space-lg) var(--space-md);
}

.pick {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto auto;
  align-items: center;
  gap: var(--space-md);
  width: 100%;
  min-height: var(--touch-target);
  padding: var(--space-sm) var(--space-md);
  border: 1px solid transparent;
  border-radius: var(--radius-xl);
  background: none;
  color: var(--pn-text);
  text-align: left;
  cursor: pointer;
}

/* No mark: the first column collapses so the text does not indent. */
.pick > .pick__text:first-child {
  grid-column: 1 / 3;
}

@media (hover: hover) {
  .pick:hover {
    background: color-mix(in oklab, var(--pn-raise-2) 60%, transparent);
  }
}

.pick:focus-visible {
  outline: none;
  border-color: var(--ring);
  box-shadow: 0 0 0 3px color-mix(in oklch, var(--ring), transparent 70%);
}

.pick[aria-pressed="true"] {
  background: var(--pn-raise-2);
  box-shadow: inset 0 0 0 1px var(--pn-line);
}

.pick:disabled {
  cursor: not-allowed;
  opacity: 0.5;
}

.pick__mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.75rem;
  height: 1.75rem;
  flex-shrink: 0;
  border-radius: var(--radius-md);
  background: var(--pn-raise-2);
  color: var(--pn-dim);
}

.pick__text {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.pick__trailing {
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--pn-dim);
}

.pick__go {
  display: inline-flex;
  color: var(--pn-dim);
}

.pick[aria-pressed="true"] .pick__go {
  color: var(--primary);
}

/* Docs search. The field is always there; the results appear once the
 * runtime has loaded, which happens on the first focus. */

.search {
  display: grid;
  gap: var(--space-xs);
}

.search__form {
  display: grid;
  gap: var(--space-2xs);
}

.search__label {
  font-size: var(--text-xs);
  line-height: var(--text-xs-leading);
  font-weight: var(--weight-semibold);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted-foreground);
}

/* Below the width where the sidebar becomes a column, it is stacked above
   the article instead -- and the label, its gap and the field together took
   the whole first screen of a phone, so the page's own title started below
   the fold. The field keeps its "Search" placeholder and the label stays
   for a screen reader; what goes is the line of shouting above it. */
@media (max-width: 63.99rem) {
  .search__label {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
    border: 0;
  }
}

.search__input {
  width: 100%;
  padding: var(--space-xs) var(--space-sm);
  border: 1px solid var(--field-line);
  border-radius: var(--radius-sm);
  background: var(--field);
  color: var(--foreground);
  font: inherit;
}

.search__input:focus-visible {
  outline: 2px solid var(--ring);
  outline-offset: 1px;
}

.search__results:empty {
  display: none;
}

/* --- period stepper ------------------------------------------------------ */

.period-stepper {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2xs);
}

.period-stepper__step {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.25rem;
  height: 2.5rem;
  padding: 0;
  border: 1px solid var(--field-line);
  border-radius: var(--radius-lg);
  background: var(--field);
  color: var(--pn-text);
  cursor: pointer;
  transition: background var(--motion-fast) var(--ease-out);
}

.period-stepper__step[aria-disabled="true"] {
  color: var(--pn-dim);
  opacity: 0.45;
  cursor: default;
}

.period-stepper__step:focus-visible {
  outline: 2px solid var(--ring);
  outline-offset: 2px;
}

@media (hover: hover) {
  .period-stepper__step[aria-disabled="false"]:hover {
    background: var(--pn-raise-2);
  }
}

/* Wide enough for the longest month's name, so the arrows stay where the
   finger is while it changes. */
.period-stepper__words {
  min-width: 9.5rem;
  text-align: center;
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  font-variant-numeric: tabular-nums;
}

/* --- menu picker --------------------------------------------------------- */

.menu-picker__trigger {
  display: inline-flex;
  align-items: center;
  gap: var(--space-sm);
  min-height: 2.5rem;
  max-width: 100%;
  padding: 0 var(--space-md);
  border: 1px solid var(--field-line);
  border-radius: var(--radius-lg);
  background: var(--field);
  color: var(--pn-text);
  font: inherit;
  font-size: var(--text-sm);
  cursor: pointer;
  transition: background var(--motion-fast) var(--ease-out);
}

.menu-picker__trigger:focus-visible {
  outline: 2px solid var(--ring);
  outline-offset: 2px;
}

@media (hover: hover) {
  .menu-picker__trigger:hover {
    background: var(--pn-raise-2);
  }
}

.menu-picker__label {
  color: var(--pn-dim);
}

.menu-picker__value {
  font-weight: var(--weight-medium);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* A long list scrolls inside the menu rather than off the page. */
.menu-picker .menu {
  max-height: min(20rem, 60vh);
  overflow-y: auto;
}

/* On a phone a toolbar wraps and its controls start at the left edge, so
   the menu hangs from the trigger's left rather than its right, which
   would put it off the screen. */
@media (max-width: 47.9375rem) {
  .menu-picker .menu {
    left: 0;
    right: auto;
    transform-origin: top left;
  }
}

/* A finger gets the full target; a pointer gets the height of the
   controls beside it. After the rules above, so it wins over them. */
@media (pointer: coarse) {
  .period-stepper__step {
    width: var(--touch-target);
    height: var(--touch-target);
  }

  .menu-picker__trigger {
    min-height: var(--touch-target);
  }
}

/* --- combobox ------------------------------------------------------------ */

/* A field (the .field rules) whose list opens under it, over what
   follows. The input leaves room at its end for the clear button and the
   mark. */
.combobox {
  position: relative;
}

.combobox__control {
  position: relative;
  display: flex;
  align-items: center;
  min-width: 0;
}

.combobox__input {
  padding-right: calc(var(--space-md) + 2.5rem);
  text-overflow: ellipsis;
}

.combobox__mark {
  position: absolute;
  right: var(--space-sm);
  display: flex;
  color: var(--muted-foreground);
  pointer-events: none;
}

.combobox__clear {
  position: absolute;
  right: calc(var(--space-sm) + 1.25rem);
  display: flex;
  align-items: center;
  justify-content: center;
  width: 1.5rem;
  height: 1.5rem;
  padding: 0;
  border: 0;
  border-radius: var(--radius-md);
  background: transparent;
  color: var(--muted-foreground);
  cursor: pointer;
}

/* A finger's worth of target around a small mark, without growing the
   field it sits in. */
.combobox__clear::after {
  content: "";
  position: absolute;
  inset: -0.625rem;
}

.combobox__clear:focus-visible {
  outline: 2px solid var(--ring);
  outline-offset: 1px;
}

@media (hover: hover) {
  .combobox__clear:hover {
    color: var(--pn-text);
    background: var(--pn-raise-2);
  }
}

.combobox__list {
  position: absolute;
  top: calc(100% + var(--space-xs));
  left: 0;
  right: 0;
  z-index: 20;
  max-height: min(18rem, 50vh);
  overflow-y: auto;
  margin: 0;
  padding: var(--space-xs);
  list-style: none;
  border: 1px solid var(--pn-line);
  border-radius: var(--radius-xl);
  background: var(--pn-raise);
  box-shadow: var(--shadow-lg);
  animation: pop-down var(--motion-fast) var(--ease-out) both;
  transform-origin: top center;
}

.combobox__option {
  display: flex;
  align-items: baseline;
  gap: var(--space-sm);
  min-height: 2.5rem;
  padding: var(--space-xs) var(--space-md);
  border-radius: var(--radius-lg);
  font-size: var(--text-sm);
  color: var(--pn-text);
  cursor: pointer;
  align-content: center;
  flex-wrap: wrap;
}

/* The row the arrow keys are on, and the one under the pointer: the same
   row, since pointing moves the arrows' place too. */
.combobox__option[data-active="true"] {
  background: var(--pn-raise-2);
}

.combobox__option[aria-selected="true"] {
  font-weight: var(--weight-medium);
}

.combobox__words {
  flex: 1 1 auto;
  min-width: 0;
}

.combobox__detail {
  font-size: var(--text-xs);
  color: var(--pn-dim);
}

.combobox__check {
  display: flex;
  align-self: center;
  color: var(--status-ok);
}

.combobox__none {
  padding: var(--space-sm) var(--space-md);
  font-size: var(--text-sm);
  color: var(--pn-dim);
}

@media (pointer: coarse) {
  .combobox__option {
    min-height: var(--touch-target);
  }
}

/* --- field row (field_row.rs) ---------------------------------------------- */

.field-row {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--space-md);
  padding-block: var(--space-sm);
  border-top: 1px solid var(--pn-line);
}

/* --- choice chips (choice_chips.rs) ---------------------------------------- */

/* A question in a conversation, its answers as chips to tap. Every chip is
   a 44px target at every width, not only under a coarse pointer: a chat is
   read on a phone as often as not, and a mouse loses nothing by it. */
.choice-chips {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

.choice-chips__question {
  margin: 0;
  font-size: var(--text-sm);
  line-height: var(--text-sm-leading);
  font-weight: var(--weight-semibold);
  color: var(--foreground);
}

.choice-chips__why {
  margin: 0;
  font-size: var(--text-xs);
  line-height: var(--text-xs-leading);
  color: var(--muted-foreground);
}

.choice-chips__options {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-sm);
}

.choice-chips__option {
  display: inline-flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: center;
  gap: var(--space-2xs);
  min-height: var(--touch-target);
  min-width: var(--touch-target);
  max-width: 100%;
  padding: var(--space-xs) var(--space-lg);
  border: 1px solid var(--field-line);
  border-radius: var(--radius-xl);
  background: transparent;
  color: var(--foreground);
  font-family: var(--font-sans);
  text-align: left;
  cursor: pointer;
  transition: background-color var(--motion-fast) var(--ease-out),
    border-color var(--motion-fast) var(--ease-out);
}

@media (hover: hover) {
  .choice-chips__option:hover:not(:disabled) {
    border-color: var(--foreground);
  }
}

.choice-chips__option:focus-visible {
  outline: 2px solid var(--ring);
  outline-offset: 2px;
}

/* The chosen answer: a muted fill and a firm edge, and the question is
   closed -- the other chips fade with it. */
.choice-chips__option[aria-pressed="true"] {
  border-color: var(--foreground);
  background: var(--muted);
}

.choice-chips__option:disabled {
  cursor: default;
}

.choice-chips__option:disabled:not([aria-pressed="true"]) {
  opacity: 0.55;
}

.choice-chips__label {
  font-size: var(--text-sm);
  line-height: var(--text-sm-leading);
  font-weight: var(--weight-medium);
}

.choice-chips__consequence {
  font-size: var(--text-xs);
  line-height: var(--text-xs-leading);
  color: var(--muted-foreground);
}

.choice-chips__free {
  margin: 0;
  font-size: var(--text-xs);
  line-height: var(--text-xs-leading);
  color: var(--muted-foreground);
}

/* --- assistant composer (assistant_composer.rs) ------------------------------ */

/* Where a reader writes to the assistant: the text area grows into the row,
   the send button sits at its end, both a 44px target. */
.assistant-composer {
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
}

/* Named for a screen reader; the placeholder and the conversation above it
   say what it is to everyone else. */
.assistant-composer__label {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.assistant-composer__row {
  display: flex;
  align-items: flex-end;
  gap: var(--space-sm);
  padding: var(--space-xs);
  border: 1px solid var(--field-line);
  border-radius: var(--radius-xl);
  background: var(--field);
}

.assistant-composer__row:focus-within {
  border-color: var(--ring);
  box-shadow: 0 0 0 3px color-mix(in oklch, var(--ring), transparent 70%);
}

.assistant-composer__input {
  flex: 1 1 auto;
  min-width: 0;
  min-height: var(--touch-target);
  max-height: 12rem;
  padding: var(--space-sm);
  border: 0;
  background: transparent;
  color: var(--foreground);
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  line-height: var(--text-sm-leading);
  resize: none;
}

.assistant-composer__input:focus-visible {
  outline: none;
}

/* 1rem on touch, as every field: Safari zooms into anything smaller. */
@media (pointer: coarse) {
  .assistant-composer__input {
    font-size: var(--text-base);
  }
}

.assistant-composer__input::placeholder {
  color: var(--muted-foreground);
}

.assistant-composer__send {
  display: inline-flex;
  flex: none;
  align-items: center;
  justify-content: center;
  width: var(--touch-target);
  height: var(--touch-target);
  border: 0;
  border-radius: var(--radius-lg);
  background: var(--primary);
  color: var(--primary-foreground);
  cursor: pointer;
  transition: background-color var(--motion-fast) var(--ease-out);
}

.assistant-composer__send:focus-visible {
  outline: 2px solid var(--ring);
  outline-offset: 2px;
}

.assistant-composer__send:disabled {
  cursor: not-allowed;
  opacity: 0.5;
}

.assistant-composer__count {
  margin: 0;
  font-size: var(--text-xs);
  line-height: var(--text-xs-leading);
  color: var(--muted-foreground);
  text-align: right;
}

/* What becomes of a request it cannot meet (FR-019), said where the
   reader writes. */
.assistant-composer__note {
  margin: 0;
  font-size: var(--text-xs);
  line-height: var(--text-xs-leading);
  color: var(--muted-foreground);
}

/* Continuous corners (ADR-0212): the curve SwiftUI draws, where the browser
   can draw it. A guarded enhancement -- a browser without `corner-shape`
   keeps the plain radius above, and nothing else changes. */
@supports (corner-shape: squircle) {
  .field__input {
    corner-shape: var(--corner-continuous);
  }

  .choice {
    corner-shape: var(--corner-continuous);
  }
}

@container shell (max-width: 47.9375rem) {

  /* A choice of views -- All, Needs you, Charging -- is a row of chips
     that scrolls sideways under the thumb rather than wrapping a word onto
     a second line. */
.shell .segmented {
    display: flex;
    flex-wrap: nowrap;
    gap: var(--space-sm);
    overflow-x: auto;
    max-width: 100%;
    padding: var(--space-xs) 0;
    border: 0;
    border-radius: 0;
    background: none;
    scrollbar-width: none;
  }

.shell .segmented__option {
    position: relative;
    flex: none;
    min-height: 2.25rem;
    padding: 0 var(--space-xl);
    border: 1px solid var(--pn-line);
    border-radius: var(--radius-pill);
    background: var(--pn-raise);
    color: var(--pn-text);
    font-size: var(--text-sm);
    white-space: nowrap;
  }

.shell .segmented__option:is([aria-pressed="true"], [aria-current="page"]) {
    border-color: var(--night-bg);
    background: var(--night-bg);
    color: var(--night-text);
    box-shadow: none;
  }

  /* A chip is drawn 36px tall and taken at 44: the hit area reaches past
     it above and below, where the row's padding is. It is laid out from
     the padding box, inside the 1px border, hence the extra pixel. */
.shell :is(.segmented__option, .menu-picker__trigger)::after {
    content: "";
    position: absolute;
    inset: calc((var(--touch-target) - 2.25rem) / -2 - 1px) 0;
  }

.shell .menu-picker__trigger {
    position: relative;
    min-height: 2.25rem;
    padding: 0 var(--space-lg);
    border-color: var(--pn-line);
    border-radius: var(--radius-pill);
  }
}

/* status.css -- Words that carry a state or a provenance: status, tags, badges, chips, notes, alerts, monograms. */

/* --- alert -------------------------------------------------------------- */

/* About the page rather than about a field -- `Input` owns the message under
   a box, and `ErrorScene` owns the whole document. This is the one in
   between, and the tone it carries decides whether a screen reader is
   interrupted (see `alert.rs`), not just what colour it is. */
.alert {
  display: flex;
  align-items: flex-start;
  gap: var(--gap-inline);
  width: 100%;
  padding: var(--space-lg) var(--space-xl);
  border: 0;
  border-radius: var(--radius-2xl);
  text-align: left;
}

.alert__icon {
  display: inline-flex;
  flex-shrink: 0;
  align-items: center;
  /* Optically on the first line of the title rather than the top of the
     box, which is half a line higher than it looks. */
  padding-block-start: 0.125rem;
}

.alert__body {
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
  min-width: 0;
}

.alert__description {
  text-wrap: pretty;
}

/* The tinted grounds. `color-mix` against the token rather than a second
   pair of tokens, so the two tones follow the palette on both grounds
   without anything else needing to know they exist. */
.alert[data-tone="notice"] {
  background: var(--raised);
  color: var(--foreground);
}

.alert[data-tone="danger"] {
  border-color: color-mix(in oklch, var(--destructive), transparent 70%);
  background: color-mix(in oklch, var(--destructive), transparent 92%);
  /* Not `--destructive` on its own: that is a fill token, and as text on
     its own 8% tint it measured 4.14:1 in the light theme, under AA's
     4.5. Pulled toward the ink until it clears -- the console's browser
     suite (//clients/console:a11y.check) is what found it, on the first
     run. */
  color: color-mix(in oklab, var(--destructive) 78%, var(--foreground));
}

/* The ink stays the foreground: amber text on its own tint is the pair
   that fails contrast. The border, the ground and the icon carry it. */
.alert[data-tone="warn"] {
  border-color: color-mix(in oklch, var(--status-warn), transparent 55%);
  background: color-mix(in oklch, var(--status-warn), transparent 90%);
  color: var(--foreground);
}

.alert[data-tone="warn"] .alert__icon {
  color: var(--status-warn);
}

/* --- status ------------------------------------------------------------ */

.status {
  display: inline-flex;
  flex-direction: row;
  align-items: center;
  gap: var(--space-md);
  font-family: var(--font-sans);
}

.status__dot {
  position: relative;
  display: inline-flex;
  flex-shrink: 0;
  border-radius: var(--radius-pill);
}

.status[data-size="sm"] .status__dot {
  width: 0.5rem;
  height: 0.5rem;
}

.status[data-size="md"] .status__dot {
  width: 0.75rem;
  height: 0.75rem;
}

.status[data-size="sm"] .status__label {
  font-size: var(--text-xs);
  line-height: var(--text-xs-leading);
}

.status[data-size="md"] .status__label {
  font-size: var(--text-sm);
  line-height: var(--text-sm-leading);
}

.status[data-tone^="status-"] .status__dot {
  background: var(--tone);
}

.status[data-tone^="status-"] .status__label {
  color: var(--tone);
}

/* It breathes, and it does not ping. design.md §1.3 fixes both the gesture
   and the 2.6s duration: the dot marks *live*, not urgency. A one-second
   radar ring scaling out of the dot is the gesture that rule exists to rule
   out, and the component it was ported from carried exactly that for a long
   time. Under motion-safe only. */
@media (prefers-reduced-motion: no-preference) {
  .status__dot[data-pulse="true"] {
    animation: breathe 2.6s ease-in-out infinite;
  }
}

@keyframes breathe {
  0%,
  100% {
    opacity: 1;
  }

  50% {
    opacity: 0.35;
  }
}

/* --- note ---------------------------------------------------------------- */

/* One quiet, tinted line framing the page below it. */
.note {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-xs) var(--space-md);
  padding: var(--space-md) var(--space-lg);
  border-radius: var(--radius-lg);
  background: var(--pn-raise-2);
  color: var(--pn-text);
  font-size: var(--text-sm);
  line-height: var(--text-sm-leading);
}

.note__mark {
  display: inline-flex;
  flex-shrink: 0;
  color: var(--brand-from);
}

.note__body {
  flex: 1 1 16rem;
  min-width: 0;
}

.note__action {
  flex-shrink: 0;
  margin-left: auto;
}

/* A warning note: something below needs attention. */
.note[data-tone="warn"] {
  background: color-mix(in oklab, var(--status-warn) 12%, var(--pn-raise));
}

.note[data-tone="warn"] > .note__mark {
  color: var(--status-warn);
}

/* --- excerpt ------------------------------------------------------------- */

.excerpt {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  margin: 0;
  min-width: 0;
}

.excerpt__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-sm);
}

.excerpt__label {
  font-size: var(--text-xs);
  color: var(--pn-dim);
}

.excerpt__body {
  margin: 0;
  max-height: 9rem;
  overflow: auto;
  padding: var(--space-sm) var(--space-md);
  border-radius: var(--radius-md);
  border: 1px solid var(--pn-line);
  background: var(--pn-raise-2);
  color: var(--pn-text);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  line-height: 1.5;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.excerpt__body:focus-visible {
  outline: 2px solid var(--ring);
  outline-offset: 2px;
}

/* --- badge ------------------------------------------------------------- */

/* No `role` and no dot -- see libs/ui/src/badge.rs. A badge is a fact about
   a record; a live state is a StatusIndicator. */
.badge {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2xs);
  padding: 0 var(--space-md);
  height: 1.25rem;
  white-space: nowrap;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
}

.badge[data-variant="default"] {
  background: var(--primary);
  color: var(--primary-foreground);
}

.badge[data-variant="secondary"] {
  background: var(--secondary);
  color: var(--secondary-foreground);
}

.badge[data-variant="destructive"] {
  background: color-mix(in oklch, var(--destructive), transparent 88%);
  /* The same mix the danger alert uses, for the same reason: the fill
     token on its own tint reads at 3.8:1 in light, under the 4.5 a 12px
     label needs. Pulling it toward the foreground clears it in both
     themes. */
  color: color-mix(in oklab, var(--destructive) 78%, var(--foreground));
}

.badge[data-variant="outline"] {
  border-color: var(--border);
  color: var(--foreground);
}

.live {
  display: inline-flex;
  align-items: center;
  gap: var(--space-sm);
  font-size: var(--text-xs);
  color: var(--pn-dim);
}

.live[data-live="true"] {
  color: var(--status-ok);
}

.live[data-state="reconnecting"] {
  color: var(--status-warn);
}

.live__dot {
  width: 0.4375rem;
  height: 0.4375rem;
  border-radius: var(--radius-pill);
  background: currentColor;
  flex-shrink: 0;
}

.live__label {
  white-space: nowrap;
}

.avatar {
  width: 1.75rem;
  height: 1.75rem;
  padding: 0;
  line-height: 1;
  white-space: nowrap;
  border-radius: var(--radius-pill);
  border: 0;
  background: linear-gradient(135deg, var(--brand-from), var(--brand-to));
  color: var(--pn-bg);
  font-family: var(--font-sans);
  font-weight: var(--weight-semibold);
  font-size: var(--text-xs);
  cursor: pointer;
  box-shadow:
    0 0 0 2px var(--pn-bg),
    0 0 0 3px var(--pn-line);
  overflow: hidden;
}

.avatar__image {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.avatar[aria-expanded="true"] {
  box-shadow:
    0 0 0 2px var(--pn-bg),
    0 0 0 4px var(--brand-from);
}

/* The account as a row, the face and who it is: one button. The caption
   is shown only in the sidebar's foot, which has the room; everywhere
   else the row draws as the face alone. */
.avatar-row {
  display: flex;
  align-items: center;
  gap: var(--space-lg);
  min-width: 0;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.avatar-row .avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}

.avatar-row[aria-expanded="true"] .avatar {
  box-shadow:
    0 0 0 2px var(--pn-bg),
    0 0 0 4px var(--brand-from);
}

.avatar-row:focus-visible {
  outline: 2px solid var(--brand-from);
  outline-offset: 2px;
  border-radius: var(--radius-lg);
}

.avatar-row__caption {
  display: none;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The inline mark a destructive confirmation carries (FR-021), and what
   an API key's own row wears -- a key outlives the session that minted
   it, so it states its mode rather than inheriting the page's. */
.mode-tag {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2xs);
  padding: 1px var(--space-sm);
  border-radius: var(--radius-md);
  border: 1px solid var(--pn-line);
}

.mode-tag[data-mode="live"] {
  border-color: var(--mode-live);
  color: var(--mode-live-ink);
}

.mode-tag[data-mode="sandbox"] {
  border-style: dashed;
  border-color: var(--mode-sandbox);
  color: var(--mode-sandbox-ink);
}

/* A virtual charger's own mark (spec 114): dashed, the same boundary a
   sandbox mode draws, because a virtual charger only ever exists there.
   Its hue is the simulator's kind token, never a status tone: it sits
   beside the charger's status tag, and a green "Virtual" next to a green
   "Charging" would read as a second health signal. */
.virtual-tag {
  display: inline-flex;
  align-items: center;
  gap: var(--space-sm);
  height: 1.375rem;
  padding: 0 var(--space-md);
  border-radius: var(--radius-lg);
  border: 1px dashed var(--resource-simulator);
  background: color-mix(in oklab, var(--resource-simulator) 10%, transparent);
  color: color-mix(in oklab, var(--resource-simulator) 60%, var(--pn-text));
  white-space: nowrap;
}

/* --- status tag (ADR-0126, point 4) ------------------------------------- */

/* The tone's tint behind, its ink for the words, and the glyph in the
   tone itself: the shape carries the state as well as the colour. */
.status-tag {
  display: inline-flex;
  align-items: center;
  gap: var(--space-sm);
  height: 1.375rem;
  padding: 0 var(--space-md) 0 var(--space-sm);
  border-radius: var(--radius-lg);
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  white-space: nowrap;
}

.status-tag__glyph {
  flex-shrink: 0;
  width: 0.6875rem;
  height: 0.6875rem;
}

.status-tag[data-tone="status-ok"] {
  background: color-mix(in oklab, var(--status-ok) 14%, transparent);
  color: color-mix(in oklab, var(--status-ok) 55%, var(--pn-text));
}

.status-tag[data-tone^="status-"] .status-tag__glyph {
  color: var(--tone);
}

.status-tag[data-tone="status-busy"] {
  background: color-mix(in oklab, var(--status-busy) 14%, transparent);
  color: color-mix(in oklab, var(--status-busy) 55%, var(--pn-text));
}

.status-tag[data-tone="status-warn"] {
  background: color-mix(in oklab, var(--status-warn) 14%, transparent);
  color: color-mix(in oklab, var(--status-warn) 55%, var(--pn-text));
}

.status-tag[data-tone="status-fault"] {
  background: color-mix(in oklab, var(--status-fault) 14%, transparent);
  color: color-mix(in oklab, var(--status-fault) 55%, var(--pn-text));
}

.status-tag[data-tone="status-unknown"] {
  background: var(--pn-raise-2);
  color: var(--pn-dim);
}

/* --- chip --------------------------------------------------------------- */

.chip {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-lg);
  flex-shrink: 0;
}

.chip[data-size="sm"] {
  width: 1.625rem;
  height: 1.625rem;
}

.chip[data-size="md"] {
  width: 2.25rem;
  height: 2.25rem;
  border-radius: var(--radius-xl);
}

.chip[data-size="lg"] {
  width: 2.75rem;
  height: 2.75rem;
  border-radius: var(--radius-xl);
  box-shadow:
    0 0 0 1px color-mix(in oklab, currentColor 30%, transparent),
    0 8px 24px -8px color-mix(in oklab, currentColor 45%, transparent);
}

.chip[data-kind="environment"] {
  color: var(--resource-environment);
  background: color-mix(in oklab, var(--resource-environment) 14%, transparent);
}

.chip[data-kind="location"] {
  color: var(--resource-location);
  background: color-mix(in oklab, var(--resource-location) 14%, transparent);
}

.chip[data-kind="circuit"] {
  color: var(--resource-circuit);
  background: color-mix(in oklab, var(--resource-circuit) 14%, transparent);
}

.chip[data-kind="charging-station"] {
  color: var(--resource-charging-station);
  background: color-mix(
    in oklab,
    var(--resource-charging-station) 14%,
    transparent
  );
}

.chip[data-kind="outlet"] {
  color: var(--resource-outlet);
  background: color-mix(in oklab, var(--resource-outlet) 14%, transparent);
}

.chip[data-kind="transaction"] {
  color: var(--resource-transaction);
  background: color-mix(in oklab, var(--resource-transaction) 14%, transparent);
}

.chip[data-kind="analytics"] {
  color: var(--resource-analytics);
  background: color-mix(in oklab, var(--resource-analytics) 14%, transparent);
}

.chip[data-kind="simulator"] {
  color: var(--resource-simulator);
  background: color-mix(in oklab, var(--resource-simulator) 14%, transparent);
}

/* --- assignment mark ---------------------------------------------------- */

/* Pinned here, or inherited from above. Pinned wears the transaction
   amber -- the console's money hue, never the sandbox tape's -- as
   a chip, because a pin is a decision made at this node. Inherited is a
   quiet sentence: nothing was decided here, so nothing should look
   decided. */
.assignment {
  display: inline-flex;
  align-items: center;
  gap: var(--space-sm);
  min-width: 0;
  white-space: nowrap;
  font-size: var(--text-xs);
  line-height: var(--text-xs-leading);
}

.assignment[data-state="pinned"] {
  height: 1.375rem;
  padding: 0 var(--space-md);
  border-radius: var(--radius-md);
  font-weight: var(--weight-medium);
  color: var(--resource-transaction);
  background: color-mix(in oklab, var(--resource-transaction) 14%, transparent);
  border: 1px solid color-mix(in oklab, var(--resource-transaction) 30%, transparent);
}

.assignment[data-state="inherited"] {
  color: var(--pn-dim);
}

.assignment[data-state="unpriced"] {
  color: var(--pn-dim);
  font-style: italic;
}

.assignment__from {
  overflow: hidden;
  text-overflow: ellipsis;
}

.assignment__tariff {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}

.assignment[data-state="inherited"] .assignment__tariff {
  color: var(--pn-text);
}

/* --- kind --------------------------------------------------------------- */

/* AC or DC beside a price. Its own two tokens, apart from status (health)
   and resource (kind of thing): a DC price is not a warning, an AC price
   is not a place. Mono and letter-spaced, because it is a code, not a
   word. */
.kind {
  display: inline-flex;
  align-items: center;
  height: 1.125rem;
  padding: 0 var(--space-sm);
  border-radius: var(--radius-sm);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  letter-spacing: 0.04em;
  line-height: 1;
  white-space: nowrap;
}

.kind[data-kind="ac"] {
  color: var(--kind-ac-ink);
  background: color-mix(in oklab, var(--kind-ac) 12%, transparent);
}

.kind[data-kind="dc"] {
  color: var(--kind-dc-ink);
  background: color-mix(in oklab, var(--kind-dc) 12%, transparent);
}

/* --- origin tag ---------------------------------------------------------- */

.origin-tag {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
  height: 1.375rem;
  padding: 0 var(--space-sm);
  border-radius: var(--radius-md);
  font-size: var(--text-xs);
  white-space: nowrap;
}

/* Inherited is the usual case, and quiet. */
.origin-tag[data-origin="inherited"],
.origin-tag[data-origin="default"] {
  border: 1px solid var(--pn-line);
  background: var(--pn-raise-2);
  color: var(--pn-dim);
}

/* Set here is the exception worth seeing. */
.origin-tag[data-origin="here"] {
  border: 1px solid color-mix(in oklch, var(--status-ok) 35%, transparent);
  background: color-mix(in oklch, var(--status-ok) 10%, var(--pn-raise));
  color: var(--pn-text);
  font-weight: var(--weight-semibold);
}

/* --- checklist (checklist.rs) ---------------------------------------------- */

.checklist {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xs) var(--space-lg);
  margin: 0;
  padding: var(--space-md) 0 0;
  list-style: none;
  border-top: 1px solid var(--pn-line);
  font-size: var(--text-xs);
  color: var(--pn-dim);
}

/* --- stale chip (stale_chip.rs) -------------------------------------------- */

.stale-chip {
  position: relative;
}

.stale-chip__summary {
  display: flex;
  align-items: center;
  list-style: none;
  cursor: pointer;
  border-radius: var(--radius-lg);
}

.stale-chip__summary::-webkit-details-marker {
  display: none;
}

.stale-chip__summary:focus-visible {
  outline: 2px solid var(--ring);
  outline-offset: 2px;
}

.stale-chip__age {
  font-size: var(--text-xs);
  color: var(--pn-dim);
  white-space: pre;
}

.stale-chip__reason {
  position: absolute;
  inset-inline-end: 0;
  top: calc(100% + var(--space-sm));
  z-index: 30;
  width: min(20rem, calc(100vw - 2 * var(--space-lg)));
  margin: 0;
  padding: var(--space-md) var(--space-lg);
  border: 1px solid var(--pn-line);
  border-radius: var(--radius-lg);
  background: var(--pn-raise);
  color: var(--pn-text);
  box-shadow: var(--shadow-md);
  font-size: var(--text-sm);
}

@media (pointer: coarse) {
  .stale-chip__summary {
    min-height: var(--touch-target);
  }
}

/* --- filled mark (filled_mark.rs) ------------------------------------------ */

/* "Filled in", inside a field's label: tinted like a set-here origin tag,
   because it is the same kind of news -- this value is the exception worth
   checking. The words are the label's own ink on a near-white tint, never
   the brand green as small text on a light ground (3.8:1). */
.filled-mark {
  display: inline-flex;
  align-items: center;
  align-self: center;
  height: 1.125rem;
  padding: 0 var(--space-xs);
  border: 1px solid color-mix(in oklch, var(--status-ok) 40%, transparent);
  border-radius: var(--radius-md);
  background: color-mix(in oklch, var(--status-ok) 10%, var(--background));
  color: var(--foreground);
  font-size: var(--text-2xs);
  font-weight: var(--weight-semibold);
  letter-spacing: 0.02em;
  white-space: nowrap;
}

/* Beside a choice's question, which is a column: it keeps its own width. */
.segmented-field__text > .filled-mark {
  align-self: flex-start;
}

/* Continuous corners (ADR-0212): the curve SwiftUI draws, where the browser
   can draw it. A guarded enhancement -- a browser without `corner-shape`
   keeps the plain radius above, and nothing else changes. */
@supports (corner-shape: squircle) {
  .alert {
    corner-shape: var(--corner-continuous);
  }
}

@container shell (max-width: 47.9375rem) {

.shell .live__label,
  .shell .page-header__actions .status__label {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }

  /* The top bar has no room for the age on a phone; the reason says it. */
.shell .stale-chip__age {
    display: none;
  }
}

/* rows.css -- Rows and lists of records: tables, rows, timelines, ledgers, detail lists. */

/* --- health checks ------------------------------------------------------- */

.health-checks {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr));
  gap: var(--space-md);
}

.health-check {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  min-width: 0;
  padding: var(--space-lg) var(--space-xl);
  border: 0;
  border-radius: var(--radius-2xl);
  background: var(--pn-raise-2);
}

.health-check__title {
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--pn-dim);
}

.health-check__answer {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  font-family: var(--font-heading);
  font-size: var(--text-lg);
  font-weight: var(--weight-semibold);
  color: var(--pn-text);
}

.health-check__dot {
  flex-shrink: 0;
  width: 0.5rem;
  height: 0.5rem;
  border-radius: var(--radius-pill);
  background: var(--status-unknown);
}

.health-check:is([data-tone="status-ok"], [data-tone="status-busy"], [data-tone="status-warn"], [data-tone="status-fault"]) .health-check__dot {
  background: var(--tone);
}

.health-check__detail {
  font-size: var(--text-xs);
  line-height: 1.45;
  color: var(--pn-dim);
}

/* --- attention row (attention_row.rs) ------------------------------------ */

/* The verdict: a glyph in the tone of the worst thing, the one-line answer
   in the heading face, and the counts under it. */
.attention-verdict {
  display: flex;
  align-items: center;
  gap: var(--space-lg);
}

.attention-verdict__glyph {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 2.75rem;
  height: 2.75rem;
  border-radius: var(--radius-2xl);
  background: var(--pn-raise-2);
  color: var(--pn-dim);
}

.attention-verdict[data-tone="status-fault"] > .attention-verdict__glyph {
  background: color-mix(in oklab, var(--status-fault) 14%, var(--pn-raise));
  color: var(--status-fault);
}

.attention-verdict[data-tone="status-warn"] > .attention-verdict__glyph {
  background: color-mix(in oklab, var(--status-warn) 14%, var(--pn-raise));
  color: var(--status-warn);
}

.attention-verdict[data-tone="status-busy"] > .attention-verdict__glyph {
  background: color-mix(in oklab, var(--status-busy) 14%, var(--pn-raise));
  color: var(--status-busy);
}

.attention-verdict[data-tone="status-ok"] > .attention-verdict__glyph {
  background: color-mix(in oklab, var(--status-ok) 14%, var(--pn-raise));
  color: var(--status-ok);
}

.attention-verdict__text {
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
  min-width: 0;
}

.attention-verdict__title {
  margin: 0;
  font-family: var(--font-heading);
  font-size: var(--text-xl);
  font-weight: var(--weight-semibold);
  letter-spacing: -0.02em;
  line-height: 1.1;
  color: var(--pn-text);
}

.attention-verdict__summary {
  margin: 0;
  font-size: var(--text-sm);
  color: var(--pn-dim);
}

/* A group: a small label, its caption at the end, then its rows. */
.attention-group {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

.attention-group__label {
  display: flex;
  justify-content: space-between;
  gap: var(--space-md);
  margin: var(--space-xs) var(--space-2xs) 0;
  font-size: var(--text-2xs);
  font-weight: var(--weight-semibold);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--pn-dim);
}

.attention-group__caption {
  font-weight: var(--weight-medium);
  letter-spacing: 0;
  text-transform: none;
}

/* A row that needs a hand sits on the inset surface with a tile in its
   tone; a quiet one is flat and a size smaller. */
.attention-row {
  display: flex;
  align-items: center;
  gap: var(--space-lg);
  padding: var(--space-md) var(--space-lg) var(--space-md) var(--space-md);
  border-radius: var(--radius-2xl);
  background: var(--pn-raise-2);
}

.attention-row__mark {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 2.25rem;
  height: 2.25rem;
  border-radius: var(--radius-xl);
  background: var(--pn-raise);
  color: var(--pn-dim);
}

.attention-row[data-tone="status-fault"] > .attention-row__mark {
  background: color-mix(in oklab, var(--status-fault) 14%, var(--pn-raise));
  color: var(--status-fault);
}

.attention-row[data-tone="status-warn"] > .attention-row__mark {
  background: color-mix(in oklab, var(--status-warn) 14%, var(--pn-raise));
  color: var(--status-warn);
}

.attention-row[data-tone="status-busy"] > .attention-row__mark {
  background: color-mix(in oklab, var(--status-busy) 14%, var(--pn-raise));
  color: var(--status-busy);
}

.attention-row__text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  flex: 1 1 auto;
  min-width: 0;
}

.attention-row__title {
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  letter-spacing: -0.01em;
  color: var(--pn-text);
}

.attention-row__detail {
  font-size: var(--text-xs);
  line-height: var(--text-xs-leading);
  color: var(--pn-dim);
}

.attention-row__action {
  flex-shrink: 0;
}

.attention-row[data-quiet="true"] {
  padding-block: var(--space-sm);
  background: transparent;
}

.attention-row[data-quiet="true"] > .attention-row__mark {
  width: 1.75rem;
  height: 1.75rem;
  margin-left: var(--space-xs);
  border-radius: var(--radius-lg);
}

.attention-row[data-quiet="true"] .attention-row__title {
  font-weight: var(--weight-medium);
}

@media (max-width: 30rem) {
  .attention-row {
    flex-wrap: wrap;
  }

  .attention-row__action {
    margin-left: calc(2.25rem + var(--space-lg));
  }
}

/* --- list column -------------------------------------------------------- */

.list {
  width: var(--list-width);
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  border-right: 1px solid var(--pn-line);
  background: color-mix(in oklab, var(--pn-bg) 60%, transparent);
  min-height: 0;
}

.list__head {
  padding: var(--space-lg) var(--space-lg) var(--space-md);
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  border-bottom: 1px solid var(--pn-line);
}

.list__title-row {
  display: flex;
  align-items: center;
  gap: var(--space-md);
}

.list__title {
  font-family: var(--font-heading);
  font-weight: var(--weight-semibold);
  font-size: var(--text-sm);
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.list__count {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--pn-dim);
}

.list__filter {
  display: flex;
  gap: var(--space-sm);
}

.list__filter-input {
  flex: 1;
  min-width: 0;
  height: 1.875rem;
  padding: 0 var(--space-lg);
  border-radius: var(--radius-xl);
  border: 1px solid var(--pn-line);
  background: var(--pn-raise);
  color: var(--pn-text);
  font-family: var(--font-sans);
  font-size: var(--text-xs);
}

.list__filter-input::placeholder {
  color: var(--pn-dim);
}

.list__filter-button {
  width: 1.875rem;
  height: 1.875rem;
  border-radius: var(--radius-xl);
  border: 1px solid var(--pn-line);
  background: var(--pn-raise);
  color: var(--pn-dim);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}

.list__summary {
  display: flex;
  gap: var(--space-lg);
  flex-wrap: wrap;
}

/* `xs` beside a row's own `md` puts a row's chip on the head's `lg`
   line, so the list is one left edge from its title down. */
.list__rows {
  flex: 1;
  overflow: auto;
  padding: var(--space-sm) var(--space-xs);
  display: flex;
  flex-direction: column;
  gap: 1px;
}

.list__foot {
  padding: var(--space-md) var(--space-lg);
  border-top: 1px solid var(--pn-line);
}

.row {
  display: grid;
  grid-template-columns: 1.625rem minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--space-md);
  min-height: var(--touch-target);
  padding: 0 var(--space-md);
  border-radius: var(--radius-xl);
  color: var(--pn-text);
  text-decoration: none;
}

@media (hover: hover) {
  .row:hover {
    background: color-mix(in oklab, var(--pn-raise-2) 60%, transparent);
  }
}

.row[aria-current="page"] {
  background: var(--pn-raise-2);
  box-shadow: inset 0 0 0 1px var(--pn-line);
}

/* A row whose lead column is a held-empty slot (a person, a key: nothing
   that has a resource chip) drops the slot when no row beside it has a
   chip to line up with -- otherwise the text starts 34px in beside
   nothing, which on a phone is a tenth of the screen. */
:not(:has(> .row > :first-child:not(:empty):not(.row__text)))
  > .row:has(> :first-child:empty + .row__text) {
  grid-template-columns: minmax(0, 1fr) auto;
}

:not(:has(> .row > :first-child:not(:empty):not(.row__text)))
  > .row
  > :first-child:empty:has(+ .row__text) {
  display: none;
}

/* A row whose trailing is a form (a circuit's rating, an outlet's phase
   rotation) aligns to the top: centred, the name floated mid-form. */
.row:has(.row__trailing .field) {
  align-items: start;
}

.row__text {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
}

.row__name {
  font-weight: var(--weight-medium);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.row[aria-current="page"] .row__name {
  font-weight: var(--weight-semibold);
}

.row__trailing {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--text-xs);
  text-align: right;
}

/* A row that opens a record where there is no address to link to (a native
   app with no router): the whole row is a button. A button brings its own
   look, so it is reset to the row's; the chevron shares the trailing column
   with whatever figure sits there. */
button.row {
  width: 100%;
  background: none;
  border: 0;
  font: inherit;
  text-align: inherit;
  cursor: pointer;
}

.row[data-link="true"] .row__trailing {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--space-sm);
}

.row[data-link="true"]:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px color-mix(in oklch, var(--ring), transparent 70%);
}

.row__chevron {
  display: flex;
  color: var(--pn-dim);
}

.row__unit {
  color: var(--pn-dim);
  font-size: var(--text-2xs);
  margin-left: var(--space-2xs);
}

/* A trailing column of buttons on a phone. The grid's `auto` column takes
   whatever the buttons ask for and hands the text column the rest -- and
   with two or three buttons on a 390px screen the rest is one character
   wide, so a station's model rendered as a column of letters. The buttons
   go under the text instead, wrapping if they must. The same for a
   status with a sentence for a label ("Declared, not reported by the
   station"). Rows whose trailing is a figure ("2.5 kWh") keep their
   column: a figure fits. */
@media (max-width: 40rem) {
  .row:has(.row__trailing :is(.button, .status)) {
    grid-template-columns: 1.625rem minmax(0, 1fr);
    padding-block: var(--space-sm);
  }

  .row:has(.row__trailing :is(.button, .status)) .row__trailing {
    grid-column: 2;
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-sm);
    text-align: left;
  }

  /* The same, for a row that dropped its empty lead above. */
  :not(:has(> .row > :first-child:not(:empty):not(.row__text)))
    > .row:has(> :first-child:empty + .row__text):has(.row__trailing :is(.button, .status)) {
    grid-template-columns: minmax(0, 1fr);
  }

  :not(:has(> .row > :first-child:not(:empty):not(.row__text)))
    > .row:has(> :first-child:empty + .row__text)
    .row__trailing {
    grid-column: 1;
  }

  /* And the name wraps rather than clips: a webhook's name is its URL,
     and the badge after it was the part the ellipsis cut. */
  .row:has(.row__trailing :is(.button, .status)) .row__name,
  .row:has(.row__name .badge) .row__name {
    white-space: normal;
    overflow-wrap: anywhere;
  }
}

/* --- fact --------------------------------------------------------------- */

.fact {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  padding: var(--space-lg) var(--space-xl);
  border-radius: var(--radius-xl);
  background: var(--pn-raise-2);
  border: 1px solid var(--pn-line);
  max-width: 40rem;
}

.fact__label {
  display: inline-flex;
  align-items: center;
  gap: var(--space-sm);
  font-size: 0.72rem;
  font-weight: var(--weight-semibold);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--pn-dim);
}

.fact__lock {
  display: inline-flex;
}

/* The value, and its Copy button when it has one, on one line. */
.fact__row {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  min-width: 0;
}

.fact__row > .fact__value {
  min-width: 0;
}

.fact__row > .button {
  flex-shrink: 0;
}

.fact__value {
  font-size: var(--text-base);
  color: var(--pn-text);
  overflow-wrap: anywhere;
}

.fact__value[data-code="true"] {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  /* Breaks where the markup allows, after a path's slash; mid-name only
     when one piece is wider than the whole line. */
  overflow-wrap: break-word;
}

.fact__reason {
  margin: var(--space-xs) 0 0;
  font-size: var(--text-sm);
  line-height: var(--text-sm-leading);
  color: var(--pn-dim);
}

/* --- ledger ------------------------------------------------------------- */

.ledger {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--text-sm);
  color: var(--pn-text);
}

.ledger__head {
  padding: var(--space-sm) 0;
  border-bottom: 1px solid var(--pn-line);
  text-align: left;
  font-size: 0.72rem;
  font-weight: var(--weight-semibold);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--pn-dim);
}

.ledger__head[data-figure="true"] {
  text-align: right;
}

.ledger__row > .ledger__name,
.ledger__row > .ledger__figure {
  padding: var(--space-md) 0;
  border-bottom: 1px solid var(--pn-line);
  vertical-align: baseline;
}

.ledger__name {
  display: table-cell;
}

.ledger__note {
  display: block;
  font-size: var(--text-xs);
  line-height: var(--text-xs-leading);
  color: var(--pn-dim);
}

.ledger__figure {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  text-align: right;
  white-space: nowrap;
  padding-left: var(--space-lg);
}

.ledger__total > .ledger__name,
.ledger__total > .ledger__figure {
  padding-top: var(--space-lg);
  font-weight: var(--weight-semibold);
  vertical-align: baseline;
}

.ledger__figure[data-total="true"] {
  font-size: var(--text-2xl);
  /* Its own leading: the table's is a length sized for its small text,
     and a 1.5rem figure in a 1.25rem line box pokes above the row's rule
     and into the line before it. */
  line-height: 1.15;
  letter-spacing: -0.02em;
}

/* --- timeline ----------------------------------------------------------- */

.timeline {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--space-sm);
  font-size: var(--text-sm);
  color: var(--pn-text);
}

.timeline__moment {
  display: grid;
  grid-template-columns: max-content max-content minmax(0, 1fr);
  gap: var(--space-md);
  align-items: baseline;
}

.timeline__at {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  font-variant-numeric: tabular-nums;
  color: var(--pn-dim);
  white-space: nowrap;
}

.timeline__dot {
  width: 0.5rem;
  height: 0.5rem;
  border-radius: var(--radius-pill);
  background: var(--pn-bar);
  align-self: center;
}

.timeline__dot[data-tone^="status-"] {
  background: var(--tone);
}

.timeline__text {
  min-width: 0;
  overflow-wrap: anywhere;
}

.timeline__note {
  margin-left: var(--space-sm);
  font-size: var(--text-xs);
  color: var(--pn-dim);
}

/* The ruled form: a feed of rows, each a hairline under it, the words over
   a quieter line and what it came to at the end. */
.timeline[data-ruled="true"] {
  gap: 0;
}

.timeline[data-ruled="true"] .timeline__moment {
  grid-template-columns: minmax(3.5rem, max-content) 0.5rem minmax(0, 1fr) auto;
  align-items: start;
  padding-block: var(--space-sm);
  border-bottom: 1px solid var(--pn-line);
}

.timeline[data-ruled="true"] .timeline__at {
  padding-top: 0.1rem;
}

.timeline[data-ruled="true"] .timeline__dot {
  align-self: start;
  margin-top: 0.4rem;
}

.timeline[data-ruled="true"] .timeline__text {
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
  font-weight: var(--weight-medium);
}

.timeline[data-ruled="true"] .timeline__note {
  margin-left: 0;
  font-weight: var(--weight-regular);
}

.timeline__end {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--space-sm);
  flex-wrap: wrap;
}

.timeline__figure {
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.timeline__link {
  display: inline-flex;
  align-items: center;
  min-height: var(--touch-target);
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  color: var(--pn-text);
  text-decoration: underline;
  text-underline-offset: 0.2em;
}

.timeline__by {
  display: inline-flex;
  align-items: center;
  padding: 0 var(--space-xs);
  border: 1px solid var(--pn-line);
  border-radius: var(--radius-sm);
  font-size: var(--text-xs);
  white-space: nowrap;
}

/* A narrow feed puts what a moment came to under its words. */
@media (max-width: 40rem) {
  .timeline[data-ruled="true"] .timeline__moment {
    grid-template-columns: minmax(3rem, max-content) 0.5rem minmax(0, 1fr);
  }

  .timeline[data-ruled="true"] .timeline__end {
    grid-column: 3;
    justify-content: flex-start;
  }
}

.timeline-group {
  display: flex;
  flex-direction: column;
}

.timeline-group__heading {
  margin: 0;
  padding-block: var(--space-md) var(--space-2xs);
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--pn-dim);
}

/* --- figure list (figure_list.rs) ------------------------------------------ */

.figure-list {
  margin: 0;
  display: flex;
  flex-direction: column;
}

.figure-list__line {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-lg);
  padding-block: var(--space-md);
  border-top: 1px solid var(--pn-line);
}

.figure-list__name {
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
  min-width: 0;
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  color: var(--pn-text);
}

.figure-list__about {
  font-size: var(--text-xs);
  font-weight: var(--weight-regular);
  color: var(--pn-dim);
}

.figure-list__figure {
  margin: 0;
  font-size: var(--text-sm);
  text-align: end;
}

.figure-list__figure[data-mono="true"] {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}

/* --- message log (message_log.rs) ---------------------------------------- */

.message-log {
  container-type: inline-size;
  margin: 0;
  padding: 0;
  list-style: none;
  border-radius: var(--radius-lg);
  overflow: hidden;
  background: var(--ink-bg);
  color: var(--ink-text);
}

.message-log__row {
  border-bottom: 1px solid var(--ink-line);
}

.message-log__row[data-open="true"] {
  background: var(--ink-lift);
}

.message-log__head {
  display: grid;
  grid-template-columns: 7.5rem 1rem minmax(8rem, 13rem) minmax(0, 1fr) auto;
  gap: var(--space-sm);
  align-items: center;
  width: 100%;
  min-height: var(--touch-target);
  padding: var(--space-xs) var(--space-md);
  border: 0;
  background: transparent;
  color: inherit;
  font: inherit;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  text-align: left;
  cursor: pointer;
}

.message-log__head:focus-visible {
  outline: 2px solid var(--ring);
  outline-offset: -2px;
}

.message-log__when,
.message-log__reply,
.message-log__payload {
  color: var(--ink-dim);
}

.message-log__way {
  font-weight: var(--weight-semibold);
  color: var(--ink-dim);
}

.message-log__way[data-up="true"] {
  color: var(--ink-text);
}

.message-log__action {
  font-weight: var(--weight-semibold);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.message-log__payload {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}

.message-log__flag {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2xs);
  padding: 0 var(--space-xs);
  border: 1px solid var(--ink-line);
  border-radius: var(--radius-sm);
  font-family: var(--font-sans);
  font-size: var(--text-xs);
  color: var(--ink-text);
  white-space: nowrap;
}

.message-log__flag::before {
  content: "";
  width: 0.45rem;
  height: 0.45rem;
  border-radius: var(--radius-pill);
  background: var(--ink-dim);
}

.message-log__flag[data-tone="status-fault"]::before {
  background: var(--ink-fault);
}

.message-log__flag[data-tone="status-warn"]::before {
  background: var(--ink-warn);
}

.message-log__body {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  padding: var(--space-xs) var(--space-md) var(--space-md);
}

.message-log__meta {
  font-size: var(--text-xs);
  color: var(--ink-dim);
}

.message-log__json {
  margin: 0;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  line-height: 1.55;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  color: var(--ink-text);
}

/* Narrow -- docked beside At the charger, or on a phone -- there is no
   room for one wide row: the way, the action, the time and the mark on
   the first line, the payload under them. The log's own width decides. */
@container (max-width: 40rem) {
  .message-log__head {
    grid-template-columns: 1rem minmax(0, 1fr) auto auto;
    grid-template-areas:
      "way action time flag"
      "payload payload payload payload";
    row-gap: 2px;
    min-height: calc(var(--touch-target) + 1rem);
  }

  .message-log__way {
    grid-area: way;
  }

  .message-log__action {
    grid-area: action;
  }

  .message-log__when {
    grid-area: time;
  }

  .message-log__flag {
    grid-area: flag;
  }

  .message-log__payload {
    grid-area: payload;
  }
}

/* --- detail list -------------------------------------------------------- */

.details {
  margin: 0;
  display: grid;
  gap: var(--space-sm);
  font-size: var(--text-sm);
  color: var(--pn-text);
}

.details__row {
  display: grid;
  grid-template-columns: minmax(6rem, max-content) minmax(0, 1fr);
  gap: var(--space-lg);
  align-items: baseline;
}

.details__label {
  font-size: var(--text-xs);
  color: var(--pn-dim);
}

.details__value {
  margin: 0;
  min-width: 0;
  overflow-wrap: anywhere;
}

.details__value[data-code="true"] {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  font-variant-numeric: tabular-nums;
}

.details__link {
  color: inherit;
  text-decoration: none;
}

.details__link:focus-visible {
  text-decoration: underline;
}

@media (hover: hover) {
  .details__link:hover {
    text-decoration: underline;
  }
}

.details__note {
  margin-left: var(--space-sm);
  font-size: var(--text-xs);
  color: var(--pn-dim);
}

/* A list as a table (ADR-0125, point 4). It scrolls inside its own box
   on a phone rather than pushing the page wider. */
.table {
  overflow-x: auto;
  margin: 0 calc(var(--space-md) * -1);
  border-radius: var(--radius-2xl);
}

.table:focus-visible {
  outline: 2px solid var(--ring);
  outline-offset: 2px;
}

.table__grid {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--text-sm);
}

.table__caption {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

.table__head {
  padding: var(--space-md);
  border-bottom: 1px solid var(--pn-line);
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  color: var(--pn-dim);
  text-align: left;
  white-space: nowrap;
}

.table__cell {
  padding: var(--space-md);
  border-bottom: 1px solid var(--pn-line);
  color: var(--pn-text);
  vertical-align: middle;
}

/* A link in a row -- "Open", into Stripe -- in the page's own accent. */
/* The last row ends at the surface's edge, not at a hairline. */
.table__row:last-child > .table__cell {
  border-bottom: 0;
}

.table__cell a {
  color: var(--primary);
  font-weight: var(--weight-medium);
  text-decoration: none;
}

@media (hover: hover) {
  .table__cell a:hover {
    text-decoration: underline;
  }
}

/* A row that picks what an IndexPreview opens: the open one is tinted,
   and the whole row takes the click. */
.table__row[data-selected] {
  cursor: pointer;
}

/* Tinted only where the preview sits beside the list; narrower, opening
   a row replaces the list, and a card has no row to tint. */
@media (min-width: 64.0625rem) {
  .table__row[data-selected="true"] > .table__cell {
    background: color-mix(in oklab, var(--brand-from) 8%, transparent);
  }
}

@media (hover: hover) {
  .table__row[data-selected="false"]:hover > .table__cell {
    background: color-mix(in oklab, var(--pn-raise-2) 70%, transparent);
  }
}

/* The keyboard's way into a selecting row: the first cell's words, as a
   button that looks like them. */
.table-pick {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: var(--radius-sm);
  background: none;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.table-pick:focus-visible {
  outline: 2px solid var(--ring);
  outline-offset: 2px;
}

@media (pointer: coarse) {
  .table-pick {
    min-height: var(--touch-target);
    min-width: var(--touch-target);
    justify-content: center;
  }
}

.table-link {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
  border-radius: var(--radius-sm);
  color: inherit;
  text-decoration: none;
}

/* A picture before the name (a vehicle's thumbnail): side by side, never
   wrapping, the words beside it stacked as a plain link's are. */
.table-link[data-figure="true"] {
  flex-direction: row;
  align-items: center;
  gap: var(--space-sm);
}

.table-link__figure {
  display: flex;
  flex-shrink: 0;
}

.table-link__text {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
  min-width: 0;
}

.table-link:focus-visible {
  outline: 2px solid var(--ring);
  outline-offset: 2px;
}

@media (hover: hover) {
  .table-link:hover > :first-child:not(.table-link__figure),
  .table-link:hover > .table-link__text > :first-child {
    text-decoration: underline;
    text-underline-offset: 3px;
  }
}

@media (pointer: coarse) {
  .table-link {
    min-height: var(--touch-target);
    min-width: var(--touch-target);
    justify-content: center;
  }
}

.table__row:last-child .table__cell {
  border-bottom: 0;
}

.table__head[data-figure="true"],
.table__cell[data-figure="true"] {
  text-align: right;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* On a phone every row is a card -- see libs/ui/src/table.rs. The first
   cell is the heading, a nameless last column (the row's action) sits
   beside it, and every other cell is a line under its column's name. */
@media (max-width: 47.9375rem) {
  .table {
    overflow: visible;
    margin: 0;
  }

  .table__grid,
  .table__grid > tbody {
    display: block;
  }

  /* Out of sight, and still the table's header for a screen reader. */
  .table__heads {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
    border: 0;
  }

  .table__row {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: start;
    column-gap: var(--space-md);
    row-gap: var(--space-xs);
    padding: var(--space-md) 0;
    border-bottom: 1px solid var(--pn-line);
  }

  .table__row:first-child {
    padding-top: 0;
  }

  .table__row:last-child {
    padding-bottom: 0;
    border-bottom: 0;
  }

  .table__cell,
  .table__row:last-child .table__cell {
    position: relative;
    grid-column: 1 / -1;
    min-height: var(--text-sm-leading);
    padding: 0 0 0 7rem;
    border: 0;
  }

  .table__cell[data-figure="true"] {
    text-align: left;
    white-space: normal;
  }

  /* Each label is for the eye only: `/ ""` gives the generated text an
     empty alternative, so a screen reader hears the cell once -- it already
     has the column header -- and the cell's name stays its own text. */
  .table__cell::before {
    position: absolute;
    top: 0.125rem;
    left: 0;
    width: 6.5rem;
    font-size: var(--text-xs);
    line-height: var(--text-xs-leading);
    color: var(--pn-dim);
  }

  .table__cell:nth-child(2)::before {
    content: var(--table-col-2) / "";
  }

  .table__cell:nth-child(3)::before {
    content: var(--table-col-3) / "";
  }

  .table__cell:nth-child(4)::before {
    content: var(--table-col-4) / "";
  }

  .table__cell:nth-child(5)::before {
    content: var(--table-col-5) / "";
  }

  .table__cell:nth-child(6)::before {
    content: var(--table-col-6) / "";
  }

  .table__cell:nth-child(7)::before {
    content: var(--table-col-7) / "";
  }

  .table__cell:nth-child(8)::before {
    content: var(--table-col-8) / "";
  }

  /* The heading: the whole first line, unless an action shares it. */
  .table__cell:first-child,
  .table__row:last-child .table__cell:first-child {
    padding: 0 0 var(--space-xs);
  }

  .table__cell:first-child::before {
    content: none;
  }

  .table[data-trailing="true"] .table__cell:first-child {
    grid-column: 1;
  }

  .table[data-trailing="true"] .table__cell:last-child {
    display: flex;
    justify-content: flex-end;
    gap: var(--space-xs);
    grid-column: 2;
    grid-row: 1;
    padding: 0;
  }

  .table[data-trailing="true"] .table__cell:last-child::before {
    content: none;
  }

  /* Or a list: once any cell says where it goes on a phone (`PhoneCell`),
     the table is a group of rows -- the heading, what is beneath it in
     small type, the status at the end -- rather than a stack of cards. */
  .table:has(.table__cell[data-phone]) {
    border-radius: var(--radius-2xl);
    background: var(--pn-raise);
    overflow: hidden;
  }

  .table:has(.table__cell[data-phone]) .table__row,
  .table:has(.table__cell[data-phone]) .table__row:first-child,
  .table:has(.table__cell[data-phone]) .table__row:last-child {
    position: relative;
    align-items: center;
    row-gap: var(--space-2xs);
    min-height: 3.75rem;
    padding: var(--space-lg) var(--space-xl);
    border-bottom: 0;
  }

  /* The hairline between rows starts where the words do, the way a
     phone's own lists draw it. */
  .table:has(.table__cell[data-phone]) .table__row + .table__row::before {
    content: "";
    position: absolute;
    top: 0;
    right: 0;
    left: var(--space-xl);
    height: 1px;
    background: var(--pn-line);
  }

  .table:has(.table__cell[data-phone]) .table__cell:first-child,
  .table:has(.table__cell[data-phone]) .table__row:last-child .table__cell:first-child {
    grid-column: 1;
    padding: 0;
    font-size: var(--text-base);
  }

  .table:has(.table__cell[data-phone]) .table__cell[data-phone="beneath"] {
    grid-column: 1;
    min-height: 0;
    padding: 0;
    color: var(--pn-dim);
    font-size: var(--text-sm);
  }

  .table:has(.table__cell[data-phone]) .table__cell[data-phone="end"] {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    justify-content: center;
    grid-column: 2;
    grid-row: 1 / span 4;
    min-height: 0;
    padding: 0;
    text-align: right;
  }

  /* Two at the end -- a figure and a status -- share it, one over the
     other, rather than both taking the whole column. */
  .table:has(.table__cell[data-phone]) .table__cell[data-phone="end"]:has(~ .table__cell[data-phone="end"]) {
    grid-row: 1;
    align-self: end;
  }

  .table:has(.table__cell[data-phone]) .table__cell[data-phone="end"] ~ .table__cell[data-phone="end"] {
    grid-row: 2 / span 3;
    align-self: start;
  }

  .table:has(.table__cell[data-phone]) .table__cell:is([data-phone="beneath"], [data-phone="end"])::before {
    content: none;
  }

  .table__cell[data-phone="hidden"] {
    display: none;
  }

  /* A row that opens something is one target, end to end, with the
     chevron that says it pushes. The row already takes the click
     (`TableRow`'s `onselect`); a link's own box is stretched over it. */
  .table:has(.table__cell[data-phone]) .table__row:is([data-selected], :has(.table-link)) {
    padding-inline-end: var(--space-4xl);
  }

  .table:has(.table__cell[data-phone]) .table__row:is([data-selected], :has(.table-link))::after {
    content: "";
    position: absolute;
    top: 50%;
    right: var(--space-xl);
    width: 0.5rem;
    height: 0.5rem;
    margin-top: -0.25rem;
    border-top: 2px solid var(--pn-dim);
    border-right: 2px solid var(--pn-dim);
    opacity: 0.6;
    transform: rotate(45deg);
  }

  .table:has(.table__cell[data-phone]) .table-link::after {
    content: "";
    position: absolute;
    inset: 0;
  }

  .table:has(.table__cell[data-phone]) .table__row:active {
    background: var(--pn-raise-2);
  }
}

/* One person per row; rows share a hairline. */
.person-row {
  display: grid;
  grid-template-columns: 2.25rem minmax(0, 1fr) auto;
  gap: var(--space-lg);
  align-items: center;
  padding: var(--space-md) 0;
  border-top: 1px solid var(--pn-line);
}

.person-row:first-child {
  border-top: 0;
  padding-top: 0;
}

.person-row:last-child {
  padding-bottom: 0;
}

.person-row__disc {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.25rem;
  height: 2.25rem;
  border-radius: var(--radius-pill);
  background: var(--pn-raise-2);
  border: 1px solid var(--pn-line);
  color: var(--pn-text);
  font-family: var(--font-heading);
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  letter-spacing: 0.02em;
}

.person-row__who {
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
  min-width: 0;
}

/* In a wide table the name keeps its own width -- in a narrow cell it
   broke after every character. On a phone the row is a card, and the name
   shares its line with the row's action, so there it may shrink. */
@media (min-width: 48.0625rem) {
  .table__cell .person-row {
    grid-template-columns: 2.25rem max-content;
  }
}

/* A person to pick: the row is the label, so it takes the pointer. */
.person-row[data-pick="true"] {
  cursor: pointer;
}

.person-row__figure {
  display: flex;
  justify-content: center;
}

.person-row[data-pick="true"] .checkbox__input {
  justify-self: end;
}

/* ---- VehicleRow (canvas v19 ProfileCars) ---- */

/* One of a person's cars: picture, the words with the bar under them, the
   charge at the end. */
.vehicle-row {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--space-lg);
  padding: var(--space-md) 0;
  border-top: 1px solid var(--pn-line);
}

.vehicle-row:first-child {
  border-top: 0;
  padding-top: 0;
}

/* A row that opens the car's page: the whole row is the link, a chevron
   at its end. */
.vehicle-row[data-link="true"] {
  grid-template-columns: auto minmax(0, 1fr) auto auto;
  color: inherit;
  text-decoration: none;
  border-radius: var(--radius-md);
}

/* The same row as a button, where there is no address to link to (a native
   app with no router). A button brings its own look, so it is reset to the
   row's; everything else is shared with the link above. */
button.vehicle-row {
  width: 100%;
  background: none;
  border-left: 0;
  border-right: 0;
  border-bottom: 0;
  font: inherit;
  text-align: inherit;
  cursor: pointer;
}

.vehicle-row[data-link="true"]:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px color-mix(in oklch, var(--ring), transparent 70%);
}

@media (hover: hover) {
  .vehicle-row[data-link="true"]:hover .vehicle-row__chevron {
    color: var(--pn-text);
    transform: translateX(2px);
  }
}

.vehicle-row__chevron {
  display: flex;
  color: var(--pn-dim);
  transition:
    transform var(--motion-fast) var(--ease-out),
    color var(--motion-fast) var(--ease-out);
}

.vehicle-row__picture {
  display: flex;
}

.vehicle-row__main {
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
  min-width: 0;
}

.vehicle-row__name {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-sm);
}

.vehicle-row__charge {
  font-family: var(--font-heading);
  font-size: var(--text-2xl);
  font-weight: var(--weight-semibold);
  font-variant-numeric: tabular-nums;
}

.vehicle-row__track {
  position: relative;
  display: block;
  height: 6px;
  margin-top: var(--space-xs);
  border-radius: var(--radius-pill);
  background: var(--pn-track);
}

.vehicle-row__fill {
  display: block;
  height: 100%;
  border-radius: var(--radius-pill);
  background: linear-gradient(90deg, var(--brand-from), var(--night-accent-2));
}

.vehicle-row[data-stale="true"] .vehicle-row__fill {
  background: repeating-linear-gradient(
    135deg,
    color-mix(in oklab, var(--pn-dim) 70%, transparent) 0 2px,
    color-mix(in oklab, var(--pn-dim) 30%, transparent) 2px 4px
  );
}

.vehicle-row__mark {
  position: absolute;
  top: -3px;
  bottom: -3px;
  width: 2px;
  margin-inline-start: -1px;
  border-radius: 1px;
  background: var(--pn-text);
}

.person-row__actions {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  flex-wrap: wrap;
  justify-content: flex-end;
}

@media (max-width: 47.9375rem) {
  .person-row {
    grid-template-columns: 2.25rem minmax(0, 1fr);
  }

  .person-row__actions {
    grid-column: 2;
    justify-content: flex-start;
  }

  /* A pick row keeps its tick, or its "Already yours", at the row's end on
     a phone too. Both are short; on a line of their own the words broke
     after five letters and the tick read as the next row's. */
  .person-row[data-pick] {
    grid-template-columns: 2.25rem minmax(0, 1fr) auto;
  }
}

.person-row[data-pick="settled"] > .text:last-child {
  white-space: nowrap;
}

/* --- coverage row ------------------------------------------------------- */

/* One row of the where-it-applies tree. Four columns: the node, whose
   tariff, the effective price per kind, and the pin control. The indent
   comes from data-depth and the connectors are pseudo-elements in the
   hairline colour, so the tree is drawn with no measurement. */
.coverage-row {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr) 14rem 9.5rem 2.5rem;
  align-items: center;
  gap: var(--space-md);
  min-height: var(--touch-target);
  padding-right: var(--space-lg);
  border-radius: var(--radius-xl);
  color: var(--pn-text);
}

@media (hover: hover) {
  .coverage-row:hover {
    background: color-mix(in oklab, var(--pn-raise-2) 60%, transparent);
  }
}

.coverage-row[data-depth="0"] {
  padding-left: var(--space-xl);
}

.coverage-row[data-depth="1"] {
  padding-left: 2.75rem;
}

.coverage-row[data-depth="2"] {
  padding-left: 4.5rem;
}

.coverage-row[data-depth="3"] {
  padding-left: 6.25rem;
}

/* The connectors: a vertical from the parent's column and a stub into the
   chip. `last` stops the vertical at this row's middle. */
.coverage-row:not([data-depth="0"])::before,
.coverage-row:not([data-depth="0"])::after {
  content: "";
  position: absolute;
  background: var(--pn-line);
}

.coverage-row:not([data-depth="0"])::before {
  top: 0;
  bottom: 0;
  width: 1px;
}

.coverage-row[data-last="true"]::before {
  bottom: 50%;
}

.coverage-row:not([data-depth="0"])::after {
  top: 50%;
  width: 0.625rem;
  height: 1px;
}

.coverage-row[data-depth="1"]::before,
.coverage-row[data-depth="1"]::after {
  left: 1.75rem;
}

.coverage-row[data-depth="2"]::before,
.coverage-row[data-depth="2"]::after {
  left: 3.5rem;
}

.coverage-row[data-depth="3"]::before,
.coverage-row[data-depth="3"]::after {
  left: 5.25rem;
}

/* A card is over this row. Brand green, not amber: it is an action about
   to happen, not a decision already made. */
.coverage-row[data-drop="true"] {
  background: color-mix(in oklab, var(--brand-from) 10%, var(--pn-raise));
  box-shadow: inset 0 0 0 2px color-mix(in oklab, var(--brand-from) 55%, transparent);
}

.coverage-row__what {
  display: inline-flex;
  align-items: center;
  gap: var(--space-md);
  min-width: 0;
}

/* The name gives way last, and then to an ellipsis: kept at full width it
   ran under the price chip once a deep row's indent ate the column. The
   meta beside it shrinks first, and the row's title carries the whole name. */
.coverage-row__name {
  font-weight: var(--weight-medium);
  white-space: nowrap;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}

.coverage-row__meta {
  font-size: var(--text-xs);
  line-height: var(--text-xs-leading);
  color: var(--pn-dim);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  flex-shrink: 4;
}

.coverage-row__by {
  display: inline-flex;
  min-width: 0;
}

.coverage-row__prices {
  display: inline-flex;
  align-items: center;
  gap: var(--space-lg);
  white-space: nowrap;
}

.coverage-row__price {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
}

.coverage-row__amount {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  font-variant-numeric: tabular-nums;
  color: var(--pn-dim);
}

/* The keyboard's route to what a drag does. A ghost button: the icon is
   the whole of it, and the aria-label is the whole of its meaning. */
.coverage-row__action {
  appearance: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  margin-left: auto;
  border: 0;
  border-radius: var(--radius-lg);
  background: none;
  color: var(--pn-dim);
  cursor: pointer;
}

@media (hover: hover) {
  .coverage-row__action:hover {
    color: var(--pn-text);
    background: var(--pn-raise-2);
  }
}

.coverage-row__action:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px color-mix(in oklch, var(--ring), transparent 70%);
}

/* 26rem of fixed columns do not fit a 21rem panel: the prices went past
   the edge and the names overprinted the assignment. On a phone the row
   is two lines -- what and the mark, then the prices with the action at
   the end. */
@media (max-width: 40rem) {
  .coverage-row {
    grid-template-columns: minmax(0, 1fr) auto;
    row-gap: var(--space-xs);
    padding-block: var(--space-sm);
  }

  .coverage-row__prices {
    grid-column: 1;
    gap: var(--space-md);
  }

  .coverage-row__action {
    grid-column: 2;
    grid-row: 1 / span 2;
    align-self: center;
  }
}

@media (pointer: coarse) {
  .coverage-row__action {
    width: var(--touch-target);
    height: var(--touch-target);
  }
}

/* --- receipt ------------------------------------------------------------ */

/* Printed. A mono column with a tear line, the total ruled off, and a
   perforated bottom edge drawn in the page's own ground so it reads as
   paper on both grounds. */
.receipt {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  padding: var(--space-xl) var(--space-xl) var(--space-2xl);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  line-height: var(--text-sm-leading);
  font-variant-numeric: tabular-nums;
  color: var(--pn-text);
  background: var(--pn-raise);
  border: 1px solid var(--hairline);
  border-bottom: 0;
  border-radius: var(--radius-md) var(--radius-md) 0 0;
  box-shadow: var(--shadow-md);
}

.receipt::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: -1px;
  height: 0.5rem;
  background: radial-gradient(
      circle,
      transparent 0 3px,
      var(--pn-raise) 3px
    )
    0 0 / 0.75rem 0.5rem repeat-x;
}

.receipt__title {
  font-family: var(--font-heading);
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  letter-spacing: 0.04em;
  text-align: center;
  text-transform: uppercase;
}

.receipt__subtitle {
  text-align: center;
  color: var(--pn-dim);
}

.receipt__tear {
  width: 100%;
  margin: var(--space-xs) 0;
  border: 0;
  border-top: 1px dashed var(--pn-line);
}

.receipt__line {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-md);
}

.receipt__line[data-emphasis="dim"] {
  color: var(--pn-dim);
}

.receipt__line[data-emphasis="total"] {
  margin-top: var(--space-xs);
  padding-top: var(--space-sm);
  font-weight: var(--weight-semibold);
  border-top: 1px solid var(--pn-text);
}

.receipt__label {
  min-width: 0;
}

.receipt__amount {
  flex-shrink: 0;
  white-space: nowrap;
}

/* --- link group ---------------------------------------------------------- */

/* A titled group of rows that each go somewhere (link_group.rs): one
   surface, hairlines between rows that start where the words do. */
.link-group {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

.link-group__heading {
  margin: 0;
  padding-inline: var(--space-xl);
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--pn-dim);
}

.link-group__rows {
  display: flex;
  flex-direction: column;
  overflow: hidden;
  border-radius: var(--radius-2xl);
  background: var(--pn-raise);
}

.link-row {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--space-lg);
  min-height: 3.25rem;
  margin: 0;
  padding: var(--space-md) var(--space-lg) var(--space-md) var(--space-xl);
  border: 0;
  background: none;
  color: var(--pn-text);
  font: inherit;
  text-align: left;
  text-decoration: none;
  cursor: pointer;
}

.link-row + .link-row::before {
  content: "";
  position: absolute;
  top: 0;
  right: 0;
  left: var(--space-xl);
  height: 1px;
  background: var(--pn-line);
}

.link-row:focus-visible {
  outline: 2px solid var(--ring);
  outline-offset: -2px;
}

.link-row:active {
  background: var(--pn-raise-2);
}

@media (hover: hover) {
  .link-row:hover {
    background: var(--pn-raise-2);
  }
}

.link-row__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 2rem;
  height: 2rem;
  border-radius: var(--radius-xl);
  background: var(--pn-raise-2);
  color: var(--pn-dim);
}

.link-row__text {
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
  flex: 1;
  min-width: 0;
}

.link-row__label {
  font-size: var(--text-base);
  font-weight: var(--weight-medium);
}

.link-row__detail {
  overflow: hidden;
  font-size: var(--text-sm);
  color: var(--pn-dim);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.link-row__chevron {
  display: inline-flex;
  color: var(--pn-dim);
  opacity: 0.6;
}

.link-row[data-tone="danger"] {
  color: var(--destructive);
}

.link-row[data-tone="danger"] .link-row__icon {
  background: color-mix(in oklch, var(--destructive), transparent 88%);
  color: var(--destructive);
}

/* --- assistant message (assistant_message.rs) -------------------------------- */

/* One turn in a conversation with the assistant. The member's own words sit
   at the end of the column in a dark bubble, on the ink anchor's ground;
   the assistant's read as the page does, across it. */
.assistant-message {
  display: flex;
  min-width: 0;
}

.assistant-message[data-role="member"] {
  justify-content: flex-end;
}

.assistant-message__body {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  min-width: 0;
  font-size: var(--text-sm);
  line-height: var(--text-sm-leading);
  color: var(--foreground);
  overflow-wrap: anywhere;
  white-space: pre-line;
}

.assistant-message[data-role="member"] .assistant-message__body {
  max-width: 85%;
  padding: var(--space-sm) var(--space-md);
  border-radius: var(--radius-xl);
  background: var(--ink-bg);
  color: var(--ink-text);
}

.assistant-message[data-role="assistant"] .assistant-message__body {
  width: 100%;
}

/* A link the assistant gave inside its answer -- only ever one of the
   console's own pages, which the console resolves (spec 135, T070). The
   text's own colour, underlined, so it reads as a link without leaning on
   colour. */
.assistant-message__body a {
  color: inherit;
  font-weight: var(--weight-medium);
  text-decoration: underline;
  text-underline-offset: 0.15em;
}

/* --- assistant working (assistant_working.rs) ------------------------------- */

/* The mascot and what the assistant is doing, in the status tag's green:
   `--status-ok` darkened toward the text, the pair the contrast guard
   proves. While it works a brighter band of the same green sweeps along
   the words on the conductor's rhythm -- the sign every chat the reader
   uses gives for "still thinking" -- and only for a reader who has not
   asked for less motion; for one who has, the words stand in green. */
.assistant-working {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  min-width: 0;
}

.assistant-working__words {
  font-size: var(--text-sm);
  line-height: var(--text-sm-leading);
  color: color-mix(in oklab, var(--status-ok) 55%, var(--pn-text));
}

@media (prefers-reduced-motion: no-preference) {
  .assistant-working__words {
    background-image: linear-gradient(
      90deg,
      color-mix(in oklab, var(--status-ok) 55%, var(--pn-text)) 0 38%,
      var(--status-ok) 50%,
      color-mix(in oklab, var(--status-ok) 55%, var(--pn-text)) 62% 100%
    );
    background-size: 250% 100%;
    background-clip: text;
    color: transparent;
    animation: assistant-sweep calc(var(--flow-period) * 1.25) linear infinite;
  }
}

@keyframes assistant-sweep {
  from {
    background-position: 100% 0;
  }

  to {
    background-position: 0 0;
  }
}

.assistant-message__body a:focus-visible {
  outline: 2px solid var(--ring);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

/* --- pick list ----------------------------------------------------------
   An index as a stack of tiles (pick_list.rs): each row its own porcelain
   on the page's wash, one radius, no header. The open tile is glass -- the
   sidebar's current-item treatment (ADR-0212) -- so "where you are" reads
   the same across the shell. */

.pick-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  width: 100%;
  min-width: 0;
  container: pick-list / inline-size;
}

.pick-tile {
  display: grid;
  grid-template-columns: minmax(0, 1.4fr) auto auto auto;
  align-items: center;
  gap: var(--space-xl);
  min-width: 0;
  padding: var(--space-lg) var(--space-xl) var(--space-lg) var(--space-2xl);
  border-radius: var(--radius-2xl);
  background: var(--pn-raise);
  color: var(--pn-text);
  box-shadow:
    inset 0 1px 0 var(--pn-edge),
    var(--shadow-sm);
  cursor: pointer;
  transition:
    background-color var(--motion-fast) var(--ease-out),
    box-shadow var(--motion-fast) var(--ease-out);
}

@media (hover: hover) {
  .pick-tile:not([data-current="true"]):hover {
    box-shadow:
      inset 0 1px 0 var(--pn-edge),
      var(--shadow-md);
  }
}

.pick-tile[data-current="true"] {
  background: var(--glass);
  box-shadow:
    inset 0 0 0 1px var(--glass-line),
    inset 0 1px 0 var(--glass-highlight),
    var(--shadow-md);
  backdrop-filter: blur(var(--glass-blur));
}

/* The name is the keyboard's button, drawn as the words it holds. */
.pick-tile__pick {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-2xs);
  min-width: 0;
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: var(--radius-sm);
  background: none;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.pick-tile__pick:focus-visible {
  outline: 2px solid var(--ring);
  outline-offset: 2px;
}

/* A name over a one-line address is 38px; a finger needs the 44px floor
   (FR-026), as `.table-pick` takes it. The words stay centred in it, so
   the tile only grows by the difference. */
@media (pointer: coarse) {
  .pick-tile__pick {
    min-height: var(--touch-target);
    min-width: var(--touch-target);
    justify-content: center;
  }
}

.pick-tile__name {
  display: inline-flex;
  align-items: center;
  gap: var(--space-sm);
  font-size: var(--text-sm);
  line-height: var(--text-sm-leading);
  font-weight: var(--weight-semibold);
  letter-spacing: -0.01em;
}

.pick-tile[data-current="true"] .pick-tile__name {
  color: var(--primary);
}

/* The state, before the eye reaches the tag at the end. */
.pick-tile__dot {
  flex-shrink: 0;
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 999px;
  background: var(--tone);
}

.pick-tile[data-tone="status-ok"] { --tone: var(--status-ok); }
.pick-tile[data-tone="status-busy"] { --tone: var(--status-busy); }
.pick-tile[data-tone="status-warn"] { --tone: var(--status-warn); }
.pick-tile[data-tone="status-fault"] { --tone: var(--status-fault); }
.pick-tile[data-tone="status-unknown"] { --tone: var(--status-unknown); }

.pick-tile__detail {
  font-size: var(--text-xs);
  line-height: var(--text-xs-leading);
  color: var(--pn-dim);
}

/* The facts along the tile, each in its own column of the grid. */
.pick-tile__facts {
  display: contents;
}

.pick-fact {
  font-size: var(--text-sm);
  color: var(--pn-dim);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* A bar in a tile reads across, its words beside it, so every tile is one
   line tall on both sides. */
.pick-tile .share-bar {
  flex-direction: row;
  align-items: center;
  gap: var(--space-md);
}

.pick-tile .share-bar__track {
  width: 4.5rem;
}

.pick-tile .share-bar__words {
  white-space: nowrap;
}

.pick-tile .status-tag {
  justify-self: end;
}

/* Narrow -- a phone, or a story card -- a tile folds: the name and the tag
   on the first line, the facts in a row under them. On the list's own
   width, so it holds wherever the list is put. */
@container pick-list (max-width: 34rem) {
  .pick-tile {
    grid-template-columns: minmax(0, 1fr) auto;
    gap: var(--space-sm) var(--space-lg);
    padding: var(--space-lg) var(--space-xl);
  }

  .pick-tile__facts {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-sm) var(--space-lg);
    grid-column: 1 / -1;
  }

  .pick-tile .status-tag {
    margin-left: auto;
  }
}

/* Continuous corners (ADR-0212): the curve SwiftUI draws, where the browser
   can draw it. A guarded enhancement -- a browser without `corner-shape`
   keeps the plain radius above, and nothing else changes. */
@supports (corner-shape: squircle) {
  .health-check {
    corner-shape: var(--corner-continuous);
  }

  .attention-row,
  .attention-row__mark,
  .attention-verdict__glyph {
    corner-shape: var(--corner-continuous);
  }

  .table {
    corner-shape: var(--corner-continuous);
  }

  .pick-tile {
    corner-shape: var(--corner-continuous);
  }
}

@container shell (max-width: 68.6875rem) {

/* Not fixed: `.shell`'s `contain: layout` makes a fixed box measure
     the shell rather than the viewport. Sticky with the body's own height
     holds it in view, and the negative margin gives back the width it
     would otherwise take from `.shell__main`. */
.shell .list {
    position: sticky;
    top: 0;
    align-self: flex-start;
    height: 100%;
    width: 21.25rem;
    margin-right: -21.25rem;
    z-index: 15;
    background: var(--pn-raise);
    border-radius: 0 var(--radius-xl) var(--radius-xl) 0;
    box-shadow: var(--shadow-lg);
    transform: translateX(-100%);
    transition: transform var(--motion-slow) var(--ease-move);
  }

.shell[data-list="open"] .list {
    transform: none;
  }
}

@container shell (max-width: 47.9375rem) {

  /* The list is the screen: it takes the width, and the entity is a route
     pushed on top, so whichever the Shell's `screen` names is the one
     that shows. */
.shell .list {
    position: static;
    height: auto;
    margin-right: 0;
    width: 100%;
    transform: none;
    border-right: 0;
    border-radius: 0;
    box-shadow: none;
    background: transparent;
  }

  /* On a phone the list is its own screen, so nothing in it is "the one
     beside it": the index route's first item keeps `aria-current` for
     the wider layouts, and here it must not look chosen. */
.shell[data-screen="list"] .row[aria-current="page"] {
    background: none;
    box-shadow: none;
  }

.shell[data-screen="list"] .row[aria-current="page"] .row__name {
    font-weight: var(--weight-medium);
  }


  /* A row that needs you is one target: the row, end to end, with the
     chevron that says it opens something. Its button stays the link --
     and its name, for a screen reader -- stretched over the row and drawn
     as nothing. */
.shell .attention-row:has(.attention-row__action .button) {
    position: relative;
    flex-wrap: nowrap;
    align-items: center;
    min-height: 3.75rem;
    padding-inline-end: var(--space-3xl);
  }

.shell .attention-row:has(.attention-row__action .button)::after {
    content: "";
    position: absolute;
    top: 50%;
    right: var(--space-xs);
    width: 0.5rem;
    height: 0.5rem;
    margin-top: -0.25rem;
    border-top: 2px solid var(--pn-dim);
    border-right: 2px solid var(--pn-dim);
    opacity: 0.6;
    transform: rotate(45deg);
  }

.shell .attention-row__action {
    margin: 0;
  }

.shell .attention-row__action .button {
    position: absolute;
    inset: 0;
    z-index: 1;
    width: auto;
    height: auto;
    border: 0;
    border-radius: var(--radius-lg);
    background: transparent;
    box-shadow: none;
    color: transparent;
  }

.shell .attention-row__action .button:active {
    background: color-mix(in oklab, var(--pn-raise-2) 70%, transparent);
  }

.shell .attention-row__mark {
    width: 2rem;
    height: 2rem;
    border-radius: var(--radius-xl);
  }

.shell .attention-row__title {
    font-size: var(--text-base);
  }

.shell .attention-row__detail {
    font-size: var(--text-sm);
    line-height: var(--text-sm-leading);
  }

  /* A health check is a row, not a box: its name and its answer on one
     line, what it means under them. Six boxes stood 700px tall. */
.shell .health-checks {
    grid-template-columns: minmax(0, 1fr);
    gap: 0;
  }

.shell .health-check {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: baseline;
    gap: var(--space-2xs) var(--space-md);
    padding: var(--space-md) 0;
    border: 0;
    border-radius: 0;
    background: none;
  }

.shell .health-check + .health-check {
    border-top: 1px solid var(--pn-line);
  }

.shell .health-check__title {
    grid-column: 1;
    grid-row: 1;
    font-size: var(--text-base);
    font-weight: var(--weight-medium);
    letter-spacing: 0;
    text-transform: none;
    color: var(--pn-text);
  }

.shell .health-check__answer {
    grid-column: 2;
    grid-row: 1;
    font-family: var(--font-sans);
    font-size: var(--text-sm);
  }

.shell .health-check > :not(.health-check__title, .health-check__answer) {
    grid-column: 1 / -1;
  }
}

/* figures.css -- A number and what it means: figure tiles, bars, the Right now panel, plugs. */

/* --- share bar ----------------------------------------------------------- */

.share-bar {
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
  min-width: 0;
}

.share-bar__track {
  width: 100%;
  max-width: 10rem;
  height: 0.375rem;
  border-radius: 999px;
  overflow: hidden;
  background: color-mix(in oklab, var(--status-fault) 22%, var(--pn-raise));
}

.share-bar__track[data-empty="true"],
.share-bar[data-kind="capacity"] .share-bar__track {
  background: var(--pn-line);
}

.share-bar__fill {
  height: 100%;
  background: var(--status-ok);
}

.share-bar__words {
  font-size: var(--text-xs);
  line-height: var(--text-xs-leading);
  color: var(--pn-dim);
}

/* --- load bar ------------------------------------------------------------
   Three layers on one scale: what could be taken (hatched), what the
   scheduler allows (a quiet fill), what is drawn (the status colour). The
   rating is a mark across the bar, outside its clipped track. */

.load-bar {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
  flex: 1;
  width: 100%;
  min-width: 0;
}

.load-bar__track {
  position: relative;
  height: 0.5rem;
  border-radius: 999px;
  overflow: hidden;
  background: var(--pn-track);
}

.load-bar__could,
.load-bar__allowed,
.load-bar__drawn {
  position: absolute;
  inset-block: 0;
  inset-inline-start: 0;
  transition: width var(--motion-base) var(--ease-out);
}

.load-bar__could {
  background: repeating-linear-gradient(
    135deg,
    color-mix(in oklab, var(--pn-dim) 45%, transparent) 0 1.5px,
    transparent 1.5px 4px
  );
}

.load-bar__allowed {
  background: color-mix(in oklab, var(--status-ok) 30%, var(--pn-raise));
}

.load-bar__drawn {
  border-radius: 999px;
  background: var(--status-ok);
}

.load-bar[data-full="true"] .load-bar__drawn {
  background: var(--status-warn);
}

.load-bar__rating {
  position: absolute;
  top: -0.25rem;
  height: 1rem;
  width: 2px;
  margin-inline-start: -1px;
  border-radius: 1px;
  background: var(--status-warn);
}

.load-bar__words {
  font-size: var(--text-xs);
  line-height: var(--text-xs-leading);
  color: var(--pn-dim);
}

.load-legend {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-sm) var(--space-xl);
  font-size: var(--text-xs);
  color: var(--pn-dim);
}

.load-legend__key {
  display: inline-flex;
  align-items: center;
  gap: var(--space-sm);
}

.load-legend__swatch {
  display: inline-block;
  width: 0.875rem;
  height: 0.5rem;
  border-radius: 3px;
}

.load-legend__swatch[data-layer="drawn"] {
  background: var(--status-ok);
}

.load-legend__swatch[data-layer="allowed"] {
  background: color-mix(in oklab, var(--status-ok) 30%, var(--pn-raise));
}

.load-legend__swatch[data-layer="could"] {
  background: repeating-linear-gradient(
    135deg,
    color-mix(in oklab, var(--pn-dim) 45%, transparent) 0 1.5px,
    transparent 1.5px 4px
  );
  box-shadow: inset 0 0 0 1px var(--pn-line);
}

.load-legend__swatch[data-layer="rating"] {
  width: 2px;
  height: 0.75rem;
  border-radius: 1px;
  background: var(--status-warn);
}

/* --- now panel ----------------------------------------------------------- */

/* A page's one anchor on Night: a figure, a sentence, counts, bars.
   Glass on the sky (ADR-0212 rule 5): the Night ground lifts towards its
   top, a hairline of its own ink is its edge, the brand's accent lights
   its top edge and glows from one corner, and the constellation sits
   faint behind the figure. The one place the page's point and the chrome
   read as one sky. */
.now-panel {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--space-xl);
  padding: var(--space-2xl);
  overflow: hidden;
  border: 1px solid color-mix(in oklab, var(--ink-text) 10%, transparent);
  border-radius: var(--radius-3xl);
  background:
    radial-gradient(
      40% 36% at 92% 0%,
      color-mix(in oklab, var(--night-accent) 14%, transparent),
      transparent 70%
    ),
    linear-gradient(180deg, var(--ink-lift), var(--ink-bg) 85%);
  color: var(--ink-text);
  box-shadow:
    inset 0 1px 0 color-mix(in oklab, var(--night-accent) 12%, transparent),
    var(--shadow-md);
}

/* The constellation behind the figure, as behind the sidebar's
   destinations: the same six points, from the one drawer in logo.rs. */
.now-panel__sky {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
}

.now-panel__constellation {
  position: absolute;
  right: -1.5rem;
  top: -1rem;
  width: 11rem;
  height: 11rem;
  fill: none;
  stroke: var(--night-accent);
  stroke-width: 0.6;
  stroke-linejoin: round;
  opacity: 0.14;
}

.now-panel__constellation circle {
  fill: var(--night-accent);
  stroke: none;
}

/* What is written on the anchor sits above its sky. */
.now-panel__head,
.now-panel__bars {
  position: relative;
  z-index: 1;
}

.now-panel__head {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-xl);
}

.now-panel__lead {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  min-width: 0;
  flex: 1 1 22rem;
}

.now-panel__eyebrow {
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--night-accent);
}

.now-panel__eyebrow[data-sandbox="true"] {
  color: var(--sandbox-tape);
}

.now-panel__figure {
  margin: 0;
  font-family: var(--font-heading);
  font-size: var(--text-5xl);
  font-weight: var(--weight-semibold);
  line-height: 1;
  letter-spacing: -0.03em;
  font-variant-numeric: tabular-nums;
  color: var(--night-star);
}

.now-panel__figure-words {
  margin-left: var(--space-sm);
  font-size: var(--text-lg);
  font-weight: var(--weight-medium);
  letter-spacing: 0;
  color: var(--ink-dim);
}

.now-panel__sentence {
  margin: 0;
  font-size: var(--text-sm);
  color: var(--ink-dim);
}

.now-panel__stats {
  display: flex;
  flex-wrap: wrap;
  margin: 0;
}

.now-panel__stat {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 0 var(--space-xl);
  border-left: 1px solid var(--ink-line);
}

.now-panel__stat-label {
  font-size: var(--text-xs);
  color: var(--ink-dim);
}

.now-panel__stat-value {
  margin: 0;
  font-family: var(--font-heading);
  font-size: var(--text-2xl);
  font-weight: var(--weight-semibold);
  font-variant-numeric: tabular-nums;
  color: var(--night-star);
}

.now-panel__stat-unit {
  margin-left: var(--space-2xs);
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  color: var(--ink-dim);
}

.now-panel__bars {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

.now-panel__bars:empty {
  display: none;
}

.now-bar {
  display: grid;
  grid-template-columns: minmax(6rem, 10rem) minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--space-md);
}

.now-bar__label {
  font-size: var(--text-sm);
  color: var(--ink-text);
  overflow-wrap: anywhere;
}

/* A line's label is a chip in the line's own colour, the way the supply's
   drawings name L1, L2 and L3. */
.now-bar__label[data-line] {
  justify-self: start;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 1.75rem;
  height: 1.25rem;
  padding: 0 var(--space-xs);
  border-radius: var(--radius-sm);
  font-size: var(--text-2xs);
  font-weight: var(--weight-semibold);
  box-shadow: 0 0 0 1px var(--ink-line);
}

.now-bar__label[data-line="l1"] {
  background: var(--line-l1);
  color: var(--line-l1-ink);
}

.now-bar__label[data-line="l2"] {
  background: var(--line-l2);
  color: var(--line-l2-ink);
}

.now-bar__label[data-line="l3"] {
  background: var(--line-l3);
  color: var(--line-l3-ink);
}

.now-bar__meter {
  position: relative;
  min-width: 0;
}

.now-bar__track {
  position: relative;
  height: 0.5rem;
  border-radius: 999px;
  overflow: hidden;
  background: var(--ink-track);
}

.now-bar__fill {
  position: relative;
  height: 100%;
  border-radius: 999px;
  overflow: hidden;
  background: linear-gradient(90deg, var(--brand-from), var(--night-accent-2));
  transition: width var(--motion-base) var(--ease-out);
}

/* The fill is current, so it moves (ADR-0126 rule 6): light runs along
   it on the conductor's rhythm, and only for a reader who has not asked
   for less motion. */
@media (prefers-reduced-motion: no-preference) {
  .now-bar__fill::after {
    content: "";
    position: absolute;
    inset: 0;
    background: repeating-linear-gradient(
      90deg,
      transparent 0 0.625rem,
      color-mix(in oklab, var(--night-star) 35%, transparent) 0.625rem 0.875rem,
      transparent 0.875rem 1.5rem
    );
    background-size: 1.5rem 100%;
    animation: now-bar-flow var(--flow-period) linear infinite;
  }
}

@keyframes now-bar-flow {
  to {
    background-position: 1.5rem 0;
  }
}

.now-bar[data-full="true"] .now-bar__fill {
  background: var(--status-warn);
}

/* A LoadBar on Night: what could be taken, hatched, and what is allowed,
   under what is drawn. */
.now-bar__could,
.now-bar__allowed {
  position: absolute;
  inset-block: 0;
  inset-inline-start: 0;
  transition: width var(--motion-base) var(--ease-out);
}

.now-bar__could {
  background: repeating-linear-gradient(
    135deg,
    color-mix(in oklab, var(--ink-dim) 55%, transparent) 0 1.5px,
    transparent 1.5px 4px
  );
}

.now-bar__allowed {
  background: color-mix(in oklab, var(--night-accent-2) 35%, var(--ink-track));
}

.now-bar__mark {
  position: absolute;
  top: -0.25rem;
  height: 1rem;
  width: 2px;
  margin-inline-start: -1px;
  border-radius: 1px;
  background: var(--status-warn);
}

/* LoadLegend on Night speaks Night's colours. */
.now-panel .load-legend {
  color: var(--ink-dim);
}

.now-panel .load-legend__swatch[data-layer="drawn"] {
  background: linear-gradient(90deg, var(--brand-from), var(--night-accent-2));
}

.now-panel .load-legend__swatch[data-layer="allowed"] {
  background: color-mix(in oklab, var(--night-accent-2) 35%, var(--ink-track));
}

.now-panel .load-legend__swatch[data-layer="could"] {
  background: repeating-linear-gradient(
    135deg,
    color-mix(in oklab, var(--ink-dim) 55%, transparent) 0 1.5px,
    transparent 1.5px 4px
  );
  box-shadow: inset 0 0 0 1px var(--ink-line);
}

/* One part as a row that opens it: name, bar, figures, state, and the
   arrow that says it leads somewhere. On a phone the bar and the figures
   take their own rows under the name and state. */
.now-row {
  display: grid;
  grid-template-columns: minmax(7rem, 11rem) minmax(0, 1fr) auto auto auto;
  grid-template-areas: "name meter figures state go";
  align-items: center;
  gap: var(--space-sm) var(--space-lg);
  padding: var(--space-md) var(--space-lg);
  border: 1px solid var(--ink-line);
  border-radius: var(--radius-2xl);
  background: color-mix(in oklab, var(--night-star) 4%, transparent);
  color: var(--ink-text);
  text-decoration: none;
  transition: background var(--motion-fast) var(--ease-out);
}

.now-row:focus-visible {
  outline: 2px solid var(--night-accent);
  outline-offset: 2px;
}

@media (hover: hover) {
  .now-row:hover {
    background: color-mix(in oklab, var(--night-star) 9%, transparent);
  }
}

.now-row__name {
  grid-area: name;
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
  min-width: 0;
}

.now-row__title {
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  color: var(--night-star);
}

.now-row__place {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  font-size: var(--text-xs);
  color: var(--ink-dim);
}

.now-row__meter,
.now-row__empty {
  grid-area: meter;
}

.now-row__empty {
  font-size: var(--text-xs);
  color: var(--ink-dim);
}

.now-row__figures {
  grid-area: figures;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: var(--space-2xs);
}

.now-row__words {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--ink-text);
  white-space: nowrap;
}

.now-row__note {
  font-size: var(--text-xs);
  color: var(--ink-dim);
}

.now-row__state {
  grid-area: state;
  display: flex;
  justify-content: flex-end;
}

.now-row__go {
  grid-area: go;
  display: flex;
  color: var(--ink-dim);
}

@media (max-width: 47.9375rem) {
  .now-row {
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-areas:
      "name state"
      "meter meter"
      "figures figures";
  }

  .now-row__figures {
    align-items: flex-start;
  }

  .now-row__go {
    display: none;
  }
}

/* A state tag on Night: its tint reads on the dark ground, and its words
   mix toward Night's ink rather than the panel's. */
.now-panel .status-tag[data-tone="status-ok"] {
  color: color-mix(in oklab, var(--status-ok) 60%, var(--ink-text));
}

.now-panel .status-tag[data-tone="status-busy"] {
  color: color-mix(in oklab, var(--status-busy) 60%, var(--ink-text));
}

.now-panel .status-tag[data-tone="status-warn"] {
  color: color-mix(in oklab, var(--status-warn) 60%, var(--ink-text));
}

.now-panel .status-tag[data-tone="status-fault"] {
  color: color-mix(in oklab, var(--status-fault) 60%, var(--ink-text));
}

.now-panel .status-tag[data-tone="status-unknown"] {
  background: color-mix(in oklab, var(--ink-dim) 18%, transparent);
  color: var(--ink-text);
}

/* --- plugs --------------------------------------------------------------- */

.plugs {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
}

.plug {
  display: inline-flex;
  align-items: center;
  gap: var(--space-sm);
  min-width: 0;
}

.plug__face {
  display: inline-flex;
  flex-shrink: 0;
  align-items: center;
  justify-content: center;
  width: 2.125rem;
  height: 2.125rem;
  border: 1px solid var(--pn-line);
  border-radius: var(--radius-md);
  background: var(--pn-bg);
  color: var(--pn-text);
}

.plug__face svg {
  width: 1.375rem;
  height: 1.375rem;
}

.plug__name {
  font-size: var(--text-sm);
  color: var(--pn-text);
}

.now-bar__words {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--ink-dim);
  white-space: nowrap;
}

@media (max-width: 30rem) {
  .now-panel {
    padding: var(--space-xl);
  }

  .now-panel__figure {
    font-size: var(--text-4xl);
  }

  .now-panel__figure-words {
    display: block;
    margin: var(--space-2xs) 0 0;
  }

  .now-panel__stat:first-child {
    padding-left: 0;
    border-left: 0;
  }

  /* One column: the label, the bar across the whole panel, then its
     figures, which may wrap. The meter is the grid item, not its track --
     beside the label it has no width of its own and draws nothing. */
  .now-bar {
    grid-template-columns: minmax(0, 1fr);
    row-gap: var(--space-2xs);
  }

  .now-bar__words {
    white-space: normal;
  }
}

.meter {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  width: 100%;
}

.meter__row {
  display: flex;
  justify-content: space-between;
  font-size: var(--text-xs);
}

.meter__label {
  color: var(--pn-dim);
}

.meter__value {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  color: var(--pn-text);
}

.meter__track {
  position: relative;
  height: 6px;
  border-radius: var(--radius-pill);
  background: var(--pn-track);
  overflow: hidden;
}

.meter__fill {
  height: 100%;
  border-radius: var(--radius-pill);
  background: var(--pn-bar);
}

.meter[data-subject="true"] .meter__fill {
  background: var(--status-ok);
}

/* ---- ChargeMeter (canvas v19 VehFleet) ---- */

/* The figure first and large, so a fleet's column scans as numbers; the
   limit in words beside it; the bar under both. */
.charge-meter {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  width: 100%;
  min-width: 0;
}

.charge-meter__line {
  display: flex;
  align-items: baseline;
  gap: var(--space-xs);
}

.charge-meter__figure {
  font-family: var(--font-heading);
  font-size: var(--text-lg);
  font-weight: var(--weight-semibold);
  font-variant-numeric: tabular-nums;
  color: var(--pn-text);
}

.charge-meter[data-low="true"] .charge-meter__figure {
  color: color-mix(in oklab, var(--status-warn) 55%, var(--pn-text));
}

.charge-meter__limit {
  font-size: var(--text-xs);
  color: var(--pn-dim);
}

.charge-meter__track {
  position: relative;
  height: 6px;
  border-radius: var(--radius-pill);
  background: var(--pn-track);
}

.charge-meter__fill {
  height: 100%;
  border-radius: var(--radius-pill);
  background: linear-gradient(90deg, var(--brand-from), var(--night-accent-2));
}

/* Out of date: the same figure, hatched, so it does not read as now. */
.charge-meter[data-stale="true"] .charge-meter__fill {
  background: repeating-linear-gradient(
    135deg,
    color-mix(in oklab, var(--pn-dim) 70%, transparent) 0 2px,
    color-mix(in oklab, var(--pn-dim) 30%, transparent) 2px 4px
  );
}

.charge-meter__mark {
  position: absolute;
  top: -3px;
  bottom: -3px;
  width: 2px;
  margin-inline-start: -1px;
  border-radius: 1px;
  background: var(--pn-text);
}

.meter__mark {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 2px;
  margin-inline-start: -1px;
  background: var(--pn-text);
}

.share-bar[data-full="true"] .share-bar__fill {
  background: var(--status-warn);
}

/* --- charger image and identity ----------------------------------------- */

/* A charger's picture on its own plate. Every photo is one 3:4 frame with
   one margin (charging_station_image.rs), so the plate only has to hold
   the frame at a size: the thumbnail beside a name, the portrait leading
   the card about it. */
.charger-image {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  overflow: hidden;
  border: 0;
  border-radius: var(--radius-2xl);
  background: radial-gradient(120% 90% at 50% 30%, var(--pn-raise), var(--pn-raise-2));
}

.charger-image[data-size="thumbnail"] {
  width: 3.75rem;
  height: 3.75rem;
  padding: var(--space-2xs);
  border-radius: var(--radius-lg);
}

.charger-image[data-size="portrait"] {
  width: 10.5rem;
  aspect-ratio: 6 / 7;
  padding: var(--space-sm);
  border-radius: var(--radius-xl);
}

.charger-image__photo,
.charger-image__drawing {
  display: block;
  height: 100%;
  width: auto;
  max-width: 100%;
  object-fit: contain;
}

/* The photo stands on the plate rather than being cut out of it. The
   drawing carries its own shadow. */
.charger-image__photo {
  filter: drop-shadow(0 0.375rem 0.625rem color-mix(in oklch, var(--pn-text), transparent 82%));
}

/* ---- VehicleImage (spec 119, FR-030) ---- */

/* A brand's render of one car, 16:9 and transparent, on the card's own
   ground; or the neutral drawing in the same box. The render is wider than
   it is tall, so it is contained, never cropped. */
.vehicle-image {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  overflow: hidden;
  width: 100%;
  aspect-ratio: 16 / 9;
  border-radius: var(--radius-md);
  background: radial-gradient(120% 90% at 50% 30%, var(--pn-raise), var(--pn-raise-2));
}

/* Beside a name in a list row: the car is recognised by its shape and
   colour, so a small 16:10 plate with the line round it, as a charger's
   thumbnail has. */
.vehicle-image[data-size="thumbnail"] {
  width: 4rem;
  aspect-ratio: 16 / 10;
  border: 1px solid var(--pn-line);
  border-radius: var(--radius-md);
}

.vehicle-image[data-size="feature"] {
  width: 9.5rem;
  aspect-ratio: 16 / 10;
  border: 1px solid var(--pn-line);
  border-radius: var(--radius-lg);
}

.vehicle-image {
  position: relative;
}

/* The maker's mark, small, in the corner; on the logo stage so a dark
   mark stays legible on either theme. */
.vehicle-image__badge {
  position: absolute;
  inset-inline-start: var(--space-2xs);
  inset-block-end: var(--space-2xs);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 2px;
  border-radius: var(--radius-sm);
  background: var(--logo-stage);
  box-shadow: 0 0 0 1px var(--pn-line);
}

/* In a badge the mark drops its own plate: the badge is the stage. */
.vehicle-image__badge .brand-logo[data-shape] {
  width: 0.875rem;
  height: 0.875rem;
  padding: 0;
  border: 0;
  background: none;
  box-shadow: none;
}

.vehicle-image[data-size="feature"] .vehicle-image__badge .brand-logo[data-shape] {
  width: 1.125rem;
  height: 1.125rem;
}

.vehicle-image[data-size="thumbnail"] .vehicle-image__drawing {
  padding: var(--space-2xs);
}

.vehicle-image__photo,
.vehicle-image__drawing {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.vehicle-image__drawing {
  padding: var(--space-md);
}

.vehicle-image__body {
  fill: var(--pn-line);
}

.vehicle-image__glass {
  fill: var(--pn-raise);
}

.vehicle-image__tyre {
  fill: var(--pn-dim);
}

.vehicle-image__hub {
  fill: var(--pn-raise);
}

/* The picture, then make over model, the tags and the one line of what it
   can give. */
.charger-identity {
  display: flex;
  align-items: stretch;
  gap: var(--space-xl);
}

.charger-identity__body {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  min-width: 0;
  padding-top: var(--space-2xs);
}

.charger-identity__vendor {
  margin: 0;
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--pn-dim);
}

.charger-identity__model {
  margin: 0;
  font-family: var(--font-heading);
  font-size: var(--text-2xl);
  font-weight: var(--weight-semibold);
  letter-spacing: -0.015em;
  line-height: 1.15;
  color: var(--pn-text);
  overflow-wrap: anywhere;
}

.charger-identity__tags {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xs);
  margin-top: var(--space-xs);
}

.charger-identity__facts {
  margin: var(--space-xs) 0 0;
  font-size: var(--text-sm);
  color: var(--pn-dim);
}

/* ---- QrCode (canvas v19 ProfileCarKey) ---- */

/* An address handed to a phone. The plate is light and the modules dark in
   both themes on purpose (src/qr_code.rs says why), so the code carries its
   own white card rather than standing on the surface behind it -- which is
   also where the quiet zone lives. */
.qr {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-sm);
}

.qr__plate {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-sm);
}

/* The words beside the code, and above it once there is not room for both.
   A `@media` query and not `@container shell`, deliberately: this component
   is also drawn on the chooser's sky, which carries no `shell` container, so
   a `shell` query there never matches and the words would be squeezed into a
   column one letter wide. */
.qr[data-beside="true"] {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: start;
  gap: var(--space-xl);
}

.qr__words {
  min-width: 0;
}

@media (max-width: 47.9375rem) {
  .qr[data-beside="true"] {
    grid-template-columns: minmax(0, 1fr);
    justify-items: center;
  }

  .qr__words {
    justify-self: stretch;
  }
}

/* Big enough to read from a phone held at arm's length, and no bigger: a
   code is scanned at a glance, not studied. `crispEdges` keeps the module
   boundaries hard as the browser scales the SVG; antialiased edges are what
   makes a small code fail to read. */
.qr__code {
  width: 10rem;
  height: 10rem;
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-sm);
  /* No `background` here, and that is deliberate: the plate is a white rect
     inside the SVG itself (src/qr_code.rs), because the code has to be
     dark-on-light whatever the theme and a token cannot promise that. */
  shape-rendering: crispEdges;
}

/* --- price readout ------------------------------------------------------ */

/* A price as an instrument reads it: the figure in the heading face, the
   unit smaller and dimmer beside it, the kind badge under it. Tabular
   figures so a column of prices lines up. */
.price-readout {
  display: inline-flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-xs);
  min-width: 0;
  color: var(--pn-text);
}

.price-readout__line {
  display: inline-flex;
  align-items: baseline;
  gap: var(--space-xs);
  white-space: nowrap;
}

.price-readout__amount {
  font-family: var(--font-heading);
  font-weight: var(--weight-semibold);
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.02em;
  line-height: 1;
}

.price-readout__unit {
  font-family: var(--font-mono);
  font-weight: var(--weight-regular);
  color: var(--pn-dim);
}

.price-readout[data-size="sm"] .price-readout__amount {
  font-size: var(--text-lg);
}

.price-readout[data-size="sm"] .price-readout__unit {
  font-size: var(--text-xs);
}

.price-readout[data-size="md"] .price-readout__amount {
  font-size: var(--text-2xl);
}

.price-readout[data-size="md"] .price-readout__unit {
  font-size: var(--text-sm);
}

.price-readout[data-size="lg"] .price-readout__amount {
  font-size: var(--text-4xl);
}

.price-readout[data-size="lg"] .price-readout__unit {
  font-size: var(--text-base);
}

/* --- figure --------------------------------------------------------- */

.figure-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 12rem), 1fr));
  gap: var(--space-md);
}

.figure {
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
}

.figure[data-framed="true"] {
  gap: var(--space-xs);
  padding: var(--space-lg);
  border-radius: var(--radius-2xl);
  background: var(--pn-raise-2);
}

.figure__label {
  font-size: var(--text-xs);
  color: var(--pn-dim);
}

.figure__value {
  font-family: var(--font-heading);
  font-size: var(--text-2xl);
  font-weight: var(--weight-semibold);
  font-variant-numeric: tabular-nums;
  line-height: 1.1;
}

/* A strip: one surface, bare figures with a rule between, the number
   above its words. */
.figure-row[data-strip="true"] {
  display: flex;
  flex-wrap: wrap;
  gap: 0;
  border-radius: var(--radius-2xl);
  background: var(--pn-raise-2);
  overflow: hidden;
}

.figure-row[data-strip="true"] > .figure {
  padding: var(--space-md) var(--space-xl);
  border-inline-end: 1px solid var(--pn-line);
}

.figure-row[data-strip="true"] .figure__label {
  order: 2;
  font-size: var(--text-sm);
}

.figure__value[data-tone="status-ok"] {
  color: color-mix(in oklab, var(--status-ok) 55%, var(--pn-text));
}

.figure__value[data-tone="status-warn"] {
  color: color-mix(in oklab, var(--status-warn) 55%, var(--pn-text));
}

.figure__value[data-tone="status-fault"] {
  color: color-mix(in oklab, var(--status-fault) 55%, var(--pn-text));
}

.figure__value[data-known="false"] {
  color: var(--pn-dim);
}

.figure__unit {
  margin-left: var(--space-xs);
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  color: var(--pn-dim);
}

.figure__note {
  font-size: var(--text-xs);
  color: var(--pn-dim);
}

.figure__value[data-known="true"] {
  animation: figure-arrive var(--motion-base) var(--ease-out) both;
}

.now-panel__caption {
  margin-block-start: var(--space-xs);
  font-size: var(--text-xs);
  color: var(--ink-dim);
}

/* --- hero panel (hero_panel.rs) ----------------------------------------- */

/* A page's one question on its anchor surface. The page's ink anchor, so a
   page has at most one (tokens.css, `--ink-*`); dark in both themes, and
   it says so (`data-theme="dark"`) so the Input inside is dark too. A few
   stars, drawn from the night's own token, and a lift towards the corner
   the aside sits in. */
.hero-panel {
  display: flex;
  flex-direction: column;
  gap: var(--space-xl);
  padding: var(--space-2xl) var(--space-3xl) var(--space-xl);
  border-radius: var(--radius-3xl);
  background:
    radial-gradient(1px 1px at 12% 18%, var(--night-star), transparent),
    radial-gradient(1px 1px at 34% 82%, var(--night-star), transparent),
    radial-gradient(1.5px 1.5px at 58% 12%, var(--night-star), transparent),
    radial-gradient(1px 1px at 71% 64%, var(--night-star), transparent),
    radial-gradient(1px 1px at 88% 28%, var(--night-star), transparent),
    radial-gradient(1.5px 1.5px at 94% 86%, var(--night-star), transparent),
    radial-gradient(120% 140% at 88% 0%, var(--ink-raise) 0%, var(--ink-bg) 58%);
  color: var(--ink-text);
  box-shadow: var(--shadow-md);
}

.hero-panel__main {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 32rem);
  gap: var(--space-2xl);
  align-items: center;
}

.hero-panel__ask {
  display: flex;
  flex-direction: column;
  gap: var(--space-lg);
  min-width: 0;
}

.hero-panel__eyebrow {
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--night-accent);
}

.hero-panel__title {
  margin: 0;
  max-width: 18ch;
  font-family: var(--font-heading);
  font-size: var(--text-3xl);
  line-height: var(--text-3xl-leading);
  font-weight: var(--weight-semibold);
  letter-spacing: -0.025em;
  color: var(--ink-text);
}

.hero-panel__search {
  max-width: 34rem;
}

/* On the anchor the search is glass on the night rather than a field on
   a card: the ink shows through it, so it belongs to the question. */
.hero-panel .field__input {
  background: color-mix(in oklab, var(--ink-text) 7%, transparent);
  border-color: color-mix(in oklab, var(--ink-text) 15%, transparent);
}

.hero-panel__suggestions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-sm);
}

.hero-panel__aside {
  display: flex;
  justify-content: center;
  min-width: 0;
}

.hero-panel__foot {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--space-lg) var(--space-3xl);
  padding-top: var(--space-lg);
  border-top: 1px solid var(--ink-line);
}

.hero-panel__figures {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-lg) var(--space-3xl);
  margin: 0;
}

/* A count over its words, though the words come first in the markup: a
   screen reader hears "chargers, 2 brands: 3", a reader sees 3. */
.hero-panel__figure {
  display: flex;
  flex-direction: column-reverse;
  gap: var(--space-2xs);
}

.hero-panel__figure-value {
  margin: 0;
  font-family: var(--font-heading);
  font-size: var(--text-2xl);
  line-height: 1;
  font-weight: var(--weight-semibold);
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
  color: var(--ink-text);
}

.hero-panel__figure-label {
  font-size: var(--text-xs);
  color: var(--ink-dim);
}

/* --- reach map (reach_map.rs) ----------------------------------------- */

/* What reaches a project, as a constellation on the anchor. Every stroke
   is a token; the pulse runs on the conductor's rhythm, and only for a
   reader who has not asked for less motion. */
.reach-map {
  display: block;
  width: 100%;
  max-width: 32.5rem;
  height: auto;
  overflow: visible;
}

.reach-map__halo circle {
  fill: color-mix(in oklab, var(--night-accent) 18%, transparent);
}

.reach-map__line {
  stroke: color-mix(in oklab, var(--night-accent) 40%, transparent);
  stroke-width: 1.2;
}

.reach-map__star[data-state="ready"] .reach-map__line {
  stroke: var(--ink-dim);
  stroke-opacity: 0.4;
  stroke-dasharray: 3 5;
}

.reach-map__pulse {
  stroke: var(--night-accent);
  stroke-width: 2.4;
  stroke-linecap: round;
  stroke-dasharray: 0 100;
}

.reach-map__ring {
  fill: var(--ink-bg);
  stroke: var(--night-accent);
  stroke-width: 1.5;
}

.reach-map__core {
  fill: var(--night-accent);
}

.reach-map__star[data-state="unreported"] .reach-map__ring {
  stroke-dasharray: 2 2.5;
}

.reach-map__star[data-state="unreported"] .reach-map__core {
  fill: var(--ink-dim);
}

.reach-map__star[data-state="ready"] .reach-map__ring {
  stroke: var(--ink-dim);
  stroke-width: 1.2;
}

.reach-map__star[data-state="ready"] .reach-map__core {
  fill: none;
}

.reach-map__centre {
  fill: var(--ink-bg);
  stroke: var(--night-accent);
  stroke-width: 1.6;
}

.reach-map__mark {
  fill: var(--night-accent);
}

.reach-map__name {
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  fill: var(--ink-text);
}

.reach-map__detail {
  font-family: var(--font-sans);
  font-size: var(--text-xs);
  fill: var(--ink-dim);
}

.reach-map__star[data-state="ready"] .reach-map__name {
  fill: var(--ink-dim);
}

@media (prefers-reduced-motion: no-preference) {
  .reach-map__pulse {
    stroke-dasharray: 6 94;
    animation: reach-pulse calc(var(--flow-period) * 1.6) linear infinite;
  }

  .reach-map__pulse[data-beat="1"] {
    animation-delay: calc(var(--flow-period) * -0.4);
  }

  .reach-map__pulse[data-beat="2"] {
    animation-delay: calc(var(--flow-period) * -0.8);
  }

  .reach-map__pulse[data-beat="3"] {
    animation-delay: calc(var(--flow-period) * -1.2);
  }
}

/* The pulse travels from the star to the centre: into the project. */
@keyframes reach-pulse {
  from {
    stroke-dashoffset: 6;
  }

  to {
    stroke-dashoffset: -100;
  }
}

/* --- proposal summary (proposal_summary.rs) ------------------------------ */

/* What a tariff not saved yet will charge, on Night, above the tariff form.
   The anchor's own gradient, as the hero panel draws it, without the stars:
   it is read, not looked at. */
.proposal-summary {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  padding: var(--space-xl);
  border-radius: var(--radius-xl);
  background: radial-gradient(120% 140% at 100% 0%, var(--ink-raise) 0%, var(--ink-bg) 58%);
  color: var(--ink-text);
  box-shadow: var(--shadow-sm);
}

.proposal-summary__head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-sm);
}

.proposal-summary__eyebrow {
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--night-accent);
}

.proposal-summary__pending {
  display: inline-flex;
  align-items: center;
  height: 1.375rem;
  padding: 0 var(--space-sm);
  border: 1px solid var(--ink-line);
  border-radius: var(--radius-pill);
  background: var(--ink-raise);
  color: var(--ink-text);
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  white-space: nowrap;
}

.proposal-summary__sentence {
  margin: 0;
  font-family: var(--font-heading);
  font-size: var(--text-lg);
  line-height: var(--text-lg-leading);
  font-weight: var(--weight-medium);
  font-variant-numeric: tabular-nums;
  color: var(--ink-text);
}

.proposal-summary__strip {
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
}

/* The day as one track; the band's hours lit on it, a stretch each side of
   midnight when it runs past it. */
.proposal-summary__track {
  position: relative;
  height: 0.75rem;
  border-radius: var(--radius-pill);
  background: var(--ink-track);
  box-shadow: inset 0 0 0 1px var(--ink-line);
  overflow: hidden;
}

.proposal-summary__band {
  position: absolute;
  top: 0;
  bottom: 0;
  background: var(--night-accent);
}

.proposal-summary__axis {
  display: flex;
  justify-content: space-between;
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  color: var(--ink-dim);
}

.proposal-summary__applies {
  margin: 0;
  font-size: var(--text-sm);
  color: var(--ink-dim);
}

/* Continuous corners (ADR-0212): the curve SwiftUI draws, where the browser
   can draw it. A guarded enhancement -- a browser without `corner-shape`
   keeps the plain radius above, and nothing else changes. */
@supports (corner-shape: squircle) {
  .now-panel {
    corner-shape: var(--corner-continuous);
  }

  .now-row {
    corner-shape: var(--corner-continuous);
  }

  .hero-panel {
    corner-shape: var(--corner-continuous);
  }

  .figure[data-framed="true"] {
    corner-shape: var(--corner-continuous);
  }

  .figure-row[data-strip="true"] {
    corner-shape: var(--corner-continuous);
  }

  .charger-image {
    corner-shape: var(--corner-continuous);
  }
}

@container shell (max-width: 47.9375rem) {

  /* The hero's question first, then whatever stands beside it; the map
     steps aside on a phone, where the figures under it say the same. */
.shell .hero-panel {
    padding: var(--space-xl) var(--space-lg) var(--space-lg);
  }

.shell .hero-panel__main {
    grid-template-columns: minmax(0, 1fr);
  }

.shell .hero-panel__title {
    font-size: var(--text-2xl);
    line-height: var(--text-2xl-leading);
  }

.shell .hero-panel__aside:has(> .reach-map) {
    display: none;
  }

.shell .hero-panel__figures {
    width: 100%;
    justify-content: space-between;
    gap: var(--space-lg);
  }

  /* Figures two abreast: four tiles one under another were a screen of
     numbers before the first session. */
.shell .figure-row {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

.shell .figure[data-framed="true"] {
    padding: var(--space-md) var(--space-lg);
    border: 0;
    border-radius: var(--radius-2xl);
    background: var(--pn-raise);
  }

  /* The picture across the top of the card, the words under it. */
.shell .charger-identity {
    flex-direction: column;
    gap: var(--space-lg);
  }

.shell .charger-image[data-size="portrait"] {
    width: 100%;
    height: 12.5rem;
    aspect-ratio: auto;
  }

.shell .charger-image[data-size="thumbnail"] {
    width: 3rem;
    height: 3rem;
  }
}

/* charts.css -- Charts: traces, columns, bars, ribbons, the busy grid and the conductor tree. */

/* --- power trace --------------------------------------------------------- */

/* Drawn at its declared width and shrunk to its panel by the viewBox:
   shrunk only, never scaled up, the same rule as the conductor. */
/* --- diverging columns -----------------------------------------------------
   Raised above one baseline, lowered below it, on one scale. */

.diverging {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  margin: 0;
  min-width: 0;
}

.diverging__plot {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr);
  gap: 2px;
  height: 9rem;
  border-block: 1px dashed var(--pn-line);
}

.diverging__column {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.diverging__half {
  flex: 1;
  display: flex;
}

.diverging__half[data-side="up"] {
  align-items: flex-end;
}

.diverging__half[data-side="down"] {
  align-items: flex-start;
  border-top: 1px solid var(--pn-line);
}

.diverging__bar {
  width: 100%;
  min-height: 0;
  transition: height var(--motion-base) var(--ease-out);
}

.diverging__half[data-side="up"] .diverging__bar {
  border-radius: 3px 3px 0 0;
  background: color-mix(in oklab, var(--status-ok) 45%, var(--pn-raise));
}

.diverging__half[data-side="down"] .diverging__bar {
  border-radius: 0 0 3px 3px;
  background: color-mix(in oklab, var(--status-warn) 50%, var(--pn-raise));
}

.diverging__axis {
  display: flex;
  justify-content: space-between;
  font-size: var(--text-xs);
  color: var(--pn-dim);
}

.diverging__legend {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-sm) var(--space-xl);
  font-size: var(--text-xs);
  color: var(--pn-dim);
}

.diverging__key {
  display: inline-flex;
  align-items: center;
  gap: var(--space-sm);
}

.diverging__swatch {
  width: 0.625rem;
  height: 0.625rem;
  border-radius: 3px;
}

.diverging__swatch[data-side="up"] {
  background: color-mix(in oklab, var(--status-ok) 45%, var(--pn-raise));
}

.diverging__swatch[data-side="down"] {
  background: color-mix(in oklab, var(--status-warn) 50%, var(--pn-raise));
}

/* --- ranked bars ------------------------------------------------------------ */

.ranked-bars {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  margin: 0;
  padding: 0;
  list-style: none;
}

.ranked-bars__row {
  display: grid;
  grid-template-columns: minmax(0, 11rem) minmax(0, 1fr) 2.5rem;
  align-items: center;
  gap: var(--space-md);
  font-size: var(--text-sm);
}

.ranked-bars__words {
  color: var(--pn-dim);
}

.ranked-bars__track {
  display: block;
  height: 0.375rem;
  border-radius: 999px;
  overflow: hidden;
  background: var(--pn-track);
}

.ranked-bars__fill {
  display: block;
  height: 100%;
  border-radius: 999px;
  background: color-mix(in oklab, var(--status-warn) 50%, var(--pn-raise));
}

.ranked-bars__count {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  text-align: right;
}

.ranked-bars__empty {
  margin: 0;
  font-size: var(--text-sm);
  color: var(--pn-dim);
}

.limit-trace__empty {
  font-size: var(--text-sm);
  fill: var(--pn-dim);
  text-anchor: middle;
}

/* --- limit trace --------------------------------------------------------
   A quantity over time against a limit. The line wears its supply line's
   colour; the limit is a dashed rule in the warning colour, with its words
   at the plot's right edge. */

.limit-trace {
  display: block;
  width: 100%;
  max-width: 100%;
  height: auto;
  font-family: var(--font-sans);
}

.limit-trace__grid {
  stroke: var(--pn-line);
  stroke-width: 1;
}

.limit-trace__axis {
  stroke: var(--pn-bar);
  stroke-width: 1;
}

.limit-trace__tick {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  font-variant-numeric: tabular-nums;
  fill: var(--pn-dim);
}

.limit-trace__tick[data-axis="value"] {
  text-anchor: end;
}

.limit-trace__tick[data-axis="time"] {
  text-anchor: middle;
}

.limit-trace__area {
  fill: var(--status-ok);
  fill-opacity: 0.12;
}

.limit-trace__line {
  fill: none;
  stroke: var(--status-ok);
  stroke-width: 2;
  stroke-linejoin: round;
  stroke-linecap: round;
}

.limit-trace[data-line="l1"] .limit-trace__line {
  stroke: var(--line-l1);
}

.limit-trace[data-line="l2"] .limit-trace__line {
  stroke: var(--line-l2);
}

.limit-trace[data-line="l3"] .limit-trace__line {
  stroke: var(--line-l3);
}

.limit-trace__limit {
  stroke: var(--status-warn);
  stroke-width: 1.5;
  stroke-dasharray: 5 4;
}

.limit-trace__limit-label {
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  fill: var(--status-warn);
  text-anchor: end;
}

.trace {
  display: block;
  max-width: 100%;
  height: auto;
  font-family: var(--font-sans);
}

.trace__grid {
  stroke: var(--pn-line);
  stroke-width: 1;
}

.trace__axis {
  stroke: var(--pn-bar);
  stroke-width: 1;
}

.trace__tick {
  font-family: var(--font-mono);
  font-size: 11px;
  font-variant-numeric: tabular-nums;
  fill: var(--pn-dim);
}

.trace__tick[data-axis="power"] {
  text-anchor: end;
}

.trace__tick[data-axis="time"] {
  text-anchor: middle;
}

.trace__empty {
  font-size: var(--text-sm);
  fill: var(--pn-dim);
  text-anchor: middle;
}

.trace__energy {
  fill: var(--status-ok);
  fill-opacity: 0.14;
}

.trace[data-tone="status-busy"] .trace__energy {
  fill: var(--status-busy);
}

.trace__line {
  fill: none;
  stroke: var(--status-ok);
  stroke-width: 2;
  stroke-linejoin: round;
  stroke-linecap: round;
}

.trace[data-tone="status-busy"] .trace__line {
  stroke: var(--status-busy);
}

.trace__label {
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: var(--weight-medium);
  font-variant-numeric: tabular-nums;
  fill: var(--pn-text);
  text-anchor: end;
}

/* Font size is in the SVG's own units and shrinks with the viewBox: at
   390px the 720-wide drawing is at 0.45, and 11px reads as 5px. Twice
   the size there is the same 11px on the screen. After every base rule
   above, so it wins on specificity's tie. */
@media (max-width: 40rem) {
  .trace__tick,
  .trace__label,
  .trace__empty {
    font-size: 22px;
  }
}

.trace__end {
  fill: var(--status-ok);
  stroke: var(--pn-raise);
  stroke-width: 2;
  transform-box: fill-box;
}

.trace[data-tone="status-busy"] .trace__end {
  fill: var(--status-busy);
}

@media (prefers-reduced-motion: no-preference) {
  .trace__end[data-live="true"] {
    animation: breathe 2.6s ease-in-out infinite;
  }
}

/* --- price day ----------------------------------------------------------- */

/* Laid out in HTML, not drawn: an SVG scaled to its panel scales its words
   with it, and the hours read at 7px in a half-width panel. Here the bars
   are placed by percentage and every word is type at the page's sizes. */
.price-day {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  min-width: 0;
  font-family: var(--font-sans);
}

.price-day__unit {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--pn-dim);
}

/* The prices beside the plot, the hours under it. */
.price-day__chart {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  grid-template-rows: 10rem auto;
  column-gap: var(--space-sm);
  row-gap: var(--space-xs);
  margin-top: var(--space-sm);
}

.price-day__prices {
  position: relative;
  min-width: 2rem;
}

.price-day__tick {
  position: absolute;
  right: 0;
  transform: translateY(50%);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  font-variant-numeric: tabular-nums;
  line-height: 1;
  color: var(--pn-dim);
}

.price-day__plot {
  position: relative;
  display: flex;
  gap: 2px;
}

.price-day__grid,
.price-day__axis,
.price-day__average {
  position: absolute;
  left: 0;
  right: 0;
  height: 0;
  pointer-events: none;
}

.price-day__grid {
  border-top: 1px solid var(--pn-line);
}

.price-day__axis {
  border-top: 1px solid var(--pn-bar);
}

.price-day__average {
  z-index: 1;
  border-top: 1px dashed var(--pn-dim);
}

.price-day__average-label {
  position: absolute;
  right: 0;
  bottom: 0.2rem;
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  color: var(--pn-dim);
}

.price-day__empty {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--text-sm);
  color: var(--pn-dim);
}

/* An hour, its bar and its tip. The whole height is the target, so a
   pointer finds a short bar as easily as a tall one. */
.price-day__slot {
  position: relative;
  flex: 1;
  min-width: 0;
}

.price-day__bar {
  position: absolute;
  left: 0;
  right: 0;
  border-radius: 2px;
  background: var(--pn-bar);
  opacity: 0.55;
  transition: opacity var(--motion-fast) var(--ease-out);
}

.price-day__bar[data-lit="true"] {
  background: var(--status-ok);
  opacity: 1;
}

.price-day__label {
  position: absolute;
  left: 50%;
  transform: translate(-50%, -0.25rem);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  font-variant-numeric: tabular-nums;
  line-height: 1;
  white-space: nowrap;
  pointer-events: none;
}

.price-day__label[data-edge="low"] {
  color: var(--price-cheap);
}

.price-day__label[data-edge="high"] {
  color: var(--price-dear);
}

.price-day__tip {
  position: absolute;
  bottom: calc(100% + var(--space-xs));
  left: 50%;
  z-index: 2;
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: var(--space-xs) var(--space-sm);
  border: 1px solid var(--pn-line);
  border-radius: var(--radius-md);
  background: var(--pn-raise);
  box-shadow: var(--shadow-md);
  font-size: var(--text-xs);
  white-space: nowrap;
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transform: translateX(-50%);
  transition:
    opacity var(--motion-fast) var(--ease-out),
    visibility var(--motion-fast) var(--ease-out);
}

.price-day__tip[data-side="start"] {
  left: 0;
  transform: none;
}

.price-day__tip[data-side="end"] {
  left: auto;
  right: 0;
  transform: none;
}

.price-day__tip-hour {
  font-family: var(--font-mono);
  color: var(--pn-dim);
}

.price-day__tip-price {
  font-weight: var(--weight-semibold);
  font-variant-numeric: tabular-nums;
  color: var(--pn-text);
}

/* Pointing at an hour lifts it out of the day and names it. */
@media (hover: hover) {
  .price-day__plot:hover .price-day__bar {
    opacity: 0.3;
  }

  .price-day__plot:hover .price-day__bar[data-lit="true"] {
    opacity: 0.55;
  }

  .price-day__slot:hover .price-day__bar,
  .price-day__slot:hover .price-day__bar[data-lit="true"] {
    opacity: 1;
  }

  .price-day__slot:hover .price-day__tip {
    opacity: 1;
    visibility: visible;
  }
}

.price-day__hours {
  grid-column: 2;
  display: flex;
  gap: 2px;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--pn-dim);
}

.price-day__hours > span {
  flex: 1;
  min-width: 0;
  text-align: center;
  white-space: nowrap;
}

/* --- meter -------------------------------------------------------------- */

/* --- column chart -------------------------------------------------------
   Two levels of percentage, on purpose: the column's height is its total
   against the tallest bucket, and each segment's height is its share of
   that column. The alternative -- every segment as a percentage of the
   chart -- needs the call site to know the ceiling before it can lay out
   a single bar, and gets it wrong the moment the data changes. */
.column-chart {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  min-width: 0;
  flex: 1;
}

.column-chart__legend {
  display: flex;
  align-items: center;
  gap: var(--space-xl);
}

.column-chart__key {
  display: inline-flex;
  align-items: center;
  gap: var(--space-sm);
  font-size: var(--text-xs);
  color: var(--pn-dim);
}

.column-chart__swatch {
  width: 0.5rem;
  height: 0.5rem;
  border-radius: var(--radius-sm);
}

.column-chart__plot {
  position: relative;
  display: flex;
  align-items: flex-end;
  gap: 3px;
  flex: 1;
  min-height: 3rem;
}

/* A bucket with nothing in it is a stub on the baseline, not a gap: a
   chart drawn before its answer, or over a quiet day, still reads as a
   chart. The segments cover the track wherever there is something. */
.column-chart__column {
  flex: 1;
  min-width: 2px;
  min-height: 2px;
  display: flex;
  flex-direction: column;
  border-radius: var(--radius-sm) var(--radius-sm) 0 0;
  overflow: hidden;
  background: var(--pn-track);
}

/* A bucket that has not happened yet, in the track colour: the axis
   still runs to the end of the day. */
.column-chart__column[data-pending="true"] {
  background: var(--pn-track);
  border-radius: var(--radius-sm);
}

.column-chart__segment {
  width: 100%;
}

/* One hue, stepped: these are shares of one total. Mixed toward the
   card it sits on (`--pn-raise`) rather than toward white, so the
   ramp holds on whatever card carries the chart. */
.column-chart__segment[data-step="0"],
.column-chart__swatch[data-step="0"] {
  background: var(--brand-from);
}
.column-chart__segment[data-step="1"],
.column-chart__swatch[data-step="1"] {
  background: color-mix(in oklab, var(--brand-from) 58%, var(--pn-raise));
}
.column-chart__segment[data-step="2"],
.column-chart__swatch[data-step="2"] {
  background: color-mix(in oklab, var(--brand-from) 36%, var(--pn-raise));
}
.column-chart__segment[data-step="3"],
.column-chart__swatch[data-step="3"] {
  background: color-mix(in oklab, var(--brand-from) 20%, var(--pn-raise));
}

.column-chart__average {
  position: absolute;
  left: 0;
  right: 0;
  height: 0;
  border-top: 1px dashed color-mix(in oklab, var(--pn-dim) 55%, transparent);
  pointer-events: none;
}

.column-chart__axis {
  display: flex;
  justify-content: space-between;
  /* A line's height while it waits for its labels, so the chart does not
     grow when they arrive. */
  min-height: 1lh;
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  color: var(--pn-dim);
}

/* --- tick strip ---------------------------------------------------------
   One tick per occasion, oldest first. The row is a flex of equal columns
   rather than a grid of fixed cells, so thirty days and seven shifts both
   fill the width they are given without the call site doing arithmetic. */
.tick-strip {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  min-width: 0;
}

.tick-strip__row {
  display: flex;
  align-items: flex-end;
  gap: 2px;
  height: 1.75rem;
}

.tick-strip__tick {
  flex: 1;
  min-width: 2px;
  border-radius: var(--radius-sm);
  background: var(--pn-track);
}

.tick-strip__tick[data-tone^="status-"] {
  background: var(--tone);
}

.tick-strip__caption {
  display: flex;
  justify-content: space-between;
  gap: var(--space-md);
  font-size: var(--text-2xs);
  color: var(--pn-dim);
}

/* --- conductor ---------------------------------------------------------- */

.conductor {
  display: block;
  max-width: 100%;
}

/* Drawn at its declared width and shrunk to its box by its viewBox, so a
   760px supply fits a 375px phone with its labels still in proportion.
   Shrunk only: `max-width` above, never `width: 100%`, or a wide box
   scales the whole drawing up to a poster. */
.conductor {
  height: auto;
}

.conductor__track {
  stroke: var(--pn-track);
  stroke-width: 5;
  stroke-linecap: round;
}

.conductor__flow {
  stroke-width: 5;
  stroke-linecap: round;
  stroke-dasharray: 4 8;
}

.conductor__flow:is([data-tone="status-ok"], [data-tone="status-busy"], [data-tone="status-warn"]) {
  stroke: var(--tone);
}

.conductor__node {
  fill: var(--pn-raise);
  stroke: var(--pn-dim);
  stroke-width: 1.5;
}

[data-on="true"] > .conductor__node {
  stroke: var(--status-ok);
}

[data-subject="true"] > .conductor__node {
  stroke-width: 2;
}

.conductor__ring {
  fill: none;
  stroke: var(--status-ok);
  stroke-opacity: 0.35;
}

.conductor__label {
  font-family: var(--font-sans);
  font-size: 11px;
  font-weight: var(--weight-medium);
  fill: var(--pn-dim);
  text-anchor: middle;
}

[data-subject="true"] > .conductor__label {
  font-weight: var(--weight-semibold);
  fill: var(--pn-text);
}

.conductor__readout {
  font-family: var(--font-mono);
  font-size: 11px;
  fill: var(--pn-dim);
  text-anchor: middle;
}

[data-on="true"] > .conductor__readout {
  fill: var(--pn-text);
}

@media (prefers-reduced-motion: no-preference) {
  .conductor__flow {
    animation: flow var(--flow-period) linear infinite;
  }

  .conductor__ring,
.live[data-live="true"] .live__dot {
    animation: breathe 2.6s ease-in-out infinite;
  }
}

@keyframes flow {
  to {
    stroke-dashoffset: -24;
  }
}

/* --- busy grid ---------------------------------------------------------- */

.busy-grid {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  min-width: 0;
}

.busy-grid__plot {
  display: grid;
  grid-template-columns: 2.25rem repeat(24, minmax(0, 1fr));
  gap: 3px;
  align-items: center;
}

.busy-grid__day,
.busy-grid__hour {
  font-size: var(--text-xs);
  color: var(--pn-dim);
  font-variant-numeric: tabular-nums;
}

.busy-grid__hour {
  white-space: nowrap;
}

/* The empty cell under the weekday column, so the hours line up with
   the columns above them. */
.busy-grid__corner {
  min-height: 1px;
}

.busy-grid__cell {
  position: relative;
  height: 1.375rem;
  border-radius: var(--radius-sm);
  background: var(--pn-line);
}

.busy-grid__cell[data-level="0"] {
  background: color-mix(in oklch, var(--status-ok) 0%, var(--pn-track));
}

.busy-grid__cell[data-level="1"] {
  background: color-mix(in oklch, var(--status-ok) 10%, var(--pn-track));
}

.busy-grid__cell[data-level="2"] {
  background: color-mix(in oklch, var(--status-ok) 20%, var(--pn-track));
}

.busy-grid__cell[data-level="3"] {
  background: color-mix(in oklch, var(--status-ok) 30%, var(--pn-track));
}

.busy-grid__cell[data-level="4"] {
  background: color-mix(in oklch, var(--status-ok) 40%, var(--pn-track));
}

.busy-grid__cell[data-level="5"] {
  background: color-mix(in oklch, var(--status-ok) 50%, var(--pn-track));
}

.busy-grid__cell[data-level="6"] {
  background: color-mix(in oklch, var(--status-ok) 60%, var(--pn-track));
}

.busy-grid__cell[data-level="7"] {
  background: color-mix(in oklch, var(--status-ok) 70%, var(--pn-track));
}

.busy-grid__cell[data-level="8"] {
  background: color-mix(in oklch, var(--status-ok) 80%, var(--pn-track));
}

.busy-grid__cell[data-level="9"] {
  background: color-mix(in oklch, var(--status-ok) 90%, var(--pn-track));
}

.busy-grid__cell[data-level="10"] {
  background: color-mix(in oklch, var(--status-ok) 100%, var(--pn-track));
}

.busy-grid__cell[data-full="true"]::after {
  content: "";
  position: absolute;
  inset: 0;
  margin: auto;
  width: 0.375rem;
  height: 0.375rem;
  border-radius: 999px;
  background: var(--status-warn);
  box-shadow: 0 0 0 1.5px var(--pn-raise);
}

.busy-grid__legend {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-sm) var(--space-xl);
  font-size: var(--text-xs);
  color: var(--pn-dim);
}

.busy-grid__key {
  display: inline-flex;
  align-items: center;
  gap: var(--space-sm);
}

.busy-grid__ramp {
  width: 6rem;
  height: 0.75rem;
  border-radius: var(--radius-sm);
  background: linear-gradient(90deg, var(--pn-track), var(--status-ok));
}

.busy-grid__swatch {
  display: inline-block;
  width: 1rem;
  height: 1rem;
}

.quarter-chart__scroll,
.quarter-chart__table {
  min-width: 0;
  overflow-x: auto;
}

.quarter-chart__scroll:focus-visible,
.quarter-chart__table:focus-visible {
  outline: 2px solid var(--ring);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

/* The same data as a table, folded under the chart. */
.quarter-chart__table table {
  border-collapse: collapse;
  font-size: var(--text-xs);
  font-variant-numeric: tabular-nums;
  color: var(--pn-text);
}

.quarter-chart__table caption {
  text-align: left;
  padding-bottom: var(--space-sm);
  color: var(--pn-dim);
}

.quarter-chart__table th,
.quarter-chart__table td {
  padding: var(--space-2xs) var(--space-sm);
  text-align: right;
  white-space: nowrap;
  border-bottom: 1px solid var(--pn-line);
}

.quarter-chart__table th[scope="row"],
.quarter-chart__table thead th:first-child {
  position: sticky;
  left: 0;
  text-align: left;
  background: var(--pn-raise);
}

/* --- quarter chart (quarter_chart.rs) --------------------------------------- */

/* One session's quarters: bars for the energy on the left-hand axis, a step
   line for the price on the right-hand one. The bars and axes are HTML placed
   by percentage, so their words stay type at the page's sizes; only the line
   is a drawing. A long session scrolls inside its own box. */
.quarter-chart {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  min-width: 0;
  font-family: var(--font-sans);
}

.quarter-chart__legend {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-sm) var(--space-xl);
  font-size: var(--text-xs);
  color: var(--pn-dim);
}

.quarter-chart__key {
  display: inline-flex;
  align-items: center;
  gap: var(--space-sm);
}

.quarter-chart__swatch {
  display: inline-block;
  width: 1rem;
  height: 0.75rem;
}

.quarter-chart__swatch[data-kind="bar"] {
  border-radius: 2px;
  background: var(--status-ok);
}

.quarter-chart__swatch[data-kind="estimated"] {
  border-radius: 2px;
  background: repeating-linear-gradient(
    135deg,
    var(--status-ok) 0 2px,
    transparent 2px 5px
  );
  box-shadow: inset 0 0 0 1px var(--status-ok);
}

.quarter-chart__swatch[data-kind="line"] {
  height: 0;
  align-self: center;
  border-top: 2px solid var(--price-dear);
}

/* The two axes beside the plot, the hours under it. The plot's column is
   `1fr` and not `minmax(0, 1fr)` on purpose: its slots have a width they
   will not go under, so a long session widens the chart and the box scrolls. */
.quarter-chart__chart {
  display: grid;
  grid-template-columns: auto 1fr auto;
  grid-template-rows: auto 10rem auto;
  column-gap: var(--space-sm);
  row-gap: var(--space-xs);
  margin-top: var(--space-sm);
}

.quarter-chart__unit {
  grid-row: 1;
  grid-column: 1;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--pn-dim);
}

.quarter-chart__unit[data-side="price"] {
  grid-column: 3;
  text-align: right;
}

/* The axes stay at the edges of the box while the bars scroll under them, so a
   long session still reads against its scales. Sticky still contains the
   ticks it positions. */
.quarter-chart__axis,
.quarter-chart__unit {
  position: sticky;
  left: 0;
  z-index: 1;
  background: var(--pn-raise);
}

.quarter-chart__axis {
  grid-row: 2;
  grid-column: 1;
  min-width: 2rem;
}

.quarter-chart__axis[data-side="price"] {
  grid-column: 3;
  min-width: 2.5rem;
}

.quarter-chart__axis[data-side="price"],
.quarter-chart__unit[data-side="price"] {
  left: auto;
  right: 0;
}

.quarter-chart__tick {
  position: absolute;
  right: 0;
  transform: translateY(50%);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  font-variant-numeric: tabular-nums;
  line-height: 1;
  color: var(--pn-dim);
}

.quarter-chart__axis[data-side="price"] .quarter-chart__tick {
  right: auto;
  left: 0;
}

.quarter-chart__plot {
  position: relative;
  grid-row: 2;
  grid-column: 2;
  display: flex;
  border-bottom: 1px solid var(--pn-bar);
}

.quarter-chart__grid {
  position: absolute;
  left: 0;
  right: 0;
  height: 0;
  border-top: 1px solid var(--pn-line);
  pointer-events: none;
}

.quarter-chart__empty {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--text-sm);
  color: var(--pn-dim);
}

.quarter-chart__slot {
  position: relative;
  flex: 1 0 0.875rem;
}

.quarter-chart__bar {
  position: absolute;
  left: 1px;
  right: 1px;
  bottom: 0;
  border-radius: 2px 2px 0 0;
  background: var(--status-ok);
}

/* An estimate is a different kind of number, so it is hatched: the same
   green drawn as stripes inside an outline. */
.quarter-chart__bar[data-estimated="true"] {
  background: repeating-linear-gradient(
    135deg,
    var(--status-ok) 0 2px,
    transparent 2px 5px
  );
  box-shadow: inset 0 0 0 1px var(--status-ok);
}

.quarter-chart__line {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
  pointer-events: none;
}

.quarter-chart__step {
  fill: none;
  stroke: var(--price-dear);
  stroke-width: 2;
  stroke-linejoin: miter;
  vector-effect: non-scaling-stroke;
}

.quarter-chart__hours {
  grid-row: 3;
  grid-column: 2;
  display: flex;
  padding-right: 2rem;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--pn-dim);
}

.quarter-chart__hour {
  position: relative;
  flex: 1 0 0.875rem;
  height: 1rem;
}

.quarter-chart__hour > span {
  position: absolute;
  left: 0;
  white-space: nowrap;
}

/* --- day ribbon --------------------------------------------------------- */

/* Twenty-four cells, same width, coloured by tone. Every cell is the same
   height except now, which is lifted: a band is a fact, not a measurement,
   so nothing here has a magnitude. */
.ribbon {
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
  width: 100%;
}

.ribbon__cells {
  display: flex;
  align-items: flex-end;
  gap: 1.5px;
  height: 0.875rem;
}

.ribbon__cell {
  flex: 1;
  height: 0.625rem;
  border-radius: 1px;
  background: var(--pn-bar);
  opacity: 0.5;
}

.ribbon__cell[data-tone="cheap"] {
  background: var(--price-cheap);
  opacity: 0.85;
}

.ribbon__cell[data-tone="dear"] {
  background: var(--price-dear);
  opacity: 0.85;
}

.ribbon__cell[data-now="true"] {
  height: 0.875rem;
  background: var(--pn-text);
  opacity: 1;
}

.ribbon__axis {
  display: flex;
  justify-content: space-between;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  line-height: var(--text-xs-leading);
  color: var(--pn-dim);
}

/* --- data arriving ------------------------------------------------------- */

/* A chart in a panel of its own takes a height of its own; inside a card it
   fills the card. */
.column-chart[data-standalone="true"] .column-chart__plot {
  flex: none;
  height: 10rem;
}

/* Data arriving is "something appearing" (tokens.css): drawn flat first,
   the columns rise into place left to right and a week's hours fill in
   by hour. The stagger is each element's own transition-delay, a fraction
   of --motion-fast, so reduced motion zeroes it with the duration. */
.column-chart__column {
  transition: height var(--motion-base) var(--ease-out);
}

.busy-grid__cell {
  transition: background var(--motion-base) var(--ease-out);
}

@keyframes figure-arrive {
  from {
    opacity: 0;
    transform: translateY(0.25rem);
  }
}
/* --- chart frame (chart_frame.rs) ----------------------------------------- */

.chart-frame {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
}

.chart-frame[data-holds="height"] {
  min-height: 12rem;
}

/* A power trace's own proportions (power_trace.rs, 720 × 220), so its
   column does not grow when the readings arrive. */
.chart-frame[data-holds="trace"] {
  aspect-ratio: 720 / 220;
  justify-content: center;
  min-width: 0;
}

.chart-frame__summary {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-xs) var(--space-lg);
}


/* cards.css -- Cards that stand for one thing and link to it: locations, projects, plans, a catalog's systems, brands and answers. */

/* --- location card ------------------------------------------------------- */

.location-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  padding: var(--space-md) var(--space-lg);
  border-radius: var(--radius-lg);
  border: 1px solid var(--pn-line);
  background: var(--pn-raise);
  color: var(--pn-text);
  text-decoration: none;
  transition: border-color var(--motion-fast) var(--ease-out);
}

@media (hover: hover) {
  .location-card:hover {
    border-color: var(--ring);
  }
}

.location-card:focus-visible {
  outline: 2px solid var(--ring);
  outline-offset: 2px;
}

.location-card__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-sm);
}

.location-card__name {
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
}

.location-card__place {
  font-size: var(--text-xs);
  color: var(--pn-dim);
}

.location-card__track {
  display: block;
  height: 0.375rem;
  border-radius: 999px;
  overflow: hidden;
  background: var(--pn-raise-2);
}

.location-card__fill {
  display: block;
  height: 100%;
  border-radius: 999px;
  background: linear-gradient(90deg, var(--brand-from), var(--night-accent-2));
}

.location-card__detail {
  font-size: var(--text-xs);
  color: var(--pn-dim);
}

/* --- project cards (ADR-0125, point 7) ---------------------------------- */

.project-cards {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-xl);
  margin-top: var(--space-md);
}

@media (max-width: 47.9375rem) {
  .project-cards {
    grid-template-columns: minmax(0, 1fr);
  }
}

.project-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-xl);
  padding: var(--space-xl) var(--space-2xl);
  border-radius: var(--radius-3xl);
  border: 0;
  background: var(--pn-raise);
  box-shadow:
    inset 0 1px 0 var(--pn-edge),
    var(--shadow-md);
  color: var(--pn-text);
  text-decoration: none;
}

@media (hover: hover) {
  .project-card:hover {
    box-shadow: var(--shadow-lg);
  }
}

.project-card:focus-visible {
  outline: 2px solid var(--brand-from);
  outline-offset: 2px;
}

.project-card__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-md);
}

.project-card__title {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-md);
  min-width: 0;
  /* As tall as a tag, so a card without one lines up with its neighbour. */
  min-height: 1.625rem;
}

.project-card__tags {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-md);
}

.project-card__tags:empty {
  display: none;
}

/* A phone has no room beside the name, so the tags are a row under it --
   always there, as tall as a tag, so a card never grows once drawn. */
@media (max-width: 47.9375rem) {
  .project-card__title {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-xs);
  }

  .project-card__tags,
  .project-card__tags:empty {
    display: flex;
    min-height: 1.625rem;
  }
}

.project-card__name {
  font-family: var(--font-heading);
  font-size: var(--text-lg);
  font-weight: var(--weight-semibold);
  letter-spacing: -0.01em;
}

.project-card__default {
  padding: var(--space-2xs) var(--space-md);
  border-radius: var(--radius-lg);
  border: 1px solid var(--pn-line);
  background: var(--pn-raise-2);
  color: var(--pn-dim);
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
}

.project-card__chevron {
  display: inline-flex;
  color: var(--pn-dim);
}

.project-card__figures {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--space-lg);
}

.project-card__open {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
  font-size: var(--text-sm);
  color: var(--pn-dim);
}

/* Dashed, because the project does not exist yet; the constellation
   stands where its figures will be. */
.project-card-new {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-md);
  min-height: 9.5rem;
  padding: var(--space-xl);
  border-radius: var(--radius-2xl);
  border: 1px dashed var(--pn-line);
  color: var(--pn-dim);
  font-size: var(--text-sm);
  text-align: center;
  text-decoration: none;
}

@media (hover: hover) {
  .project-card-new:hover {
    border-color: var(--pn-dim);
  }
}

.project-card-new:focus-visible {
  outline: 2px solid var(--brand-from);
  outline-offset: 2px;
}

.project-card-new__sky {
  width: 2.5rem;
  height: 2.5rem;
  fill: none;
  stroke: var(--primary);
  stroke-width: 1.4;
  stroke-linejoin: round;
}

.project-card-new__sky circle {
  fill: var(--primary);
  stroke: none;
}

.project-card-new__title {
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  color: var(--pn-text);
}

.project-card-new__lede {
  max-width: 22rem;
  font-size: var(--text-xs);
}

/* The billing page's anchor (ADR-0126, point 2): what the organization is
   billed on, on Night. `data-theme="dark"` on the card gives everything inside it the
   dark palette; the ground is the Night button's ink in both themes. */
.plan-card {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-2xl) var(--space-3xl);
  padding: var(--space-2xl);
  overflow: hidden;
  border: 1px solid var(--ink-line);
  border-radius: var(--radius-2xl);
  background: var(--ink-bg);
  color: var(--ink-text);
  box-shadow: var(--shadow-sm);
}

.plan-card__sky {
  position: absolute;
  left: 38%;
  bottom: -5rem;
  width: 13rem;
  height: 13rem;
  opacity: 0.14;
  pointer-events: none;
  fill: var(--night-accent);
}

.plan-card__sky polygon {
  fill: none;
  stroke: var(--night-accent);
  stroke-width: 0.6;
  stroke-linejoin: round;
}

.plan-card__plan {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  min-width: 0;
}

.plan-card__label {
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-dim);
}

.plan-card__line {
  display: flex;
  align-items: center;
  gap: var(--space-lg);
  flex-wrap: wrap;
}

.plan-card__tier {
  font-family: var(--font-heading);
  font-size: var(--text-3xl);
  font-weight: var(--weight-semibold);
  letter-spacing: -0.02em;
  line-height: 1.1;
  color: var(--ink-text);
}

.plan-card__summary {
  margin: 0;
  font-size: var(--text-sm);
  line-height: var(--text-sm-leading);
  color: var(--ink-dim);
}

.plan-card__note {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

.plan-card__actions {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  flex-wrap: wrap;
  margin-top: var(--space-sm);
}

@media (max-width: 47.9375rem) {
  .plan-card {
    grid-template-columns: minmax(0, 1fr);
    padding: var(--space-xl);
  }
}

/* --- vehicle plug picker -------------------------------------------------- */

/* --- location price tile ------------------------------------------------- */

.price-tile {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--space-lg);
  padding: var(--space-xl);
  background: var(--pn-raise);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  color: var(--pn-text);
  min-width: 0;
}

.price-tile[data-selected="true"] {
  box-shadow: 0 0 0 2px var(--brand-from), var(--shadow-md);
}

/* Decoration: the foot's AssignmentMark says the same in words. Sits
   half over the top edge, so give the tile's row room above it. */
.price-tile__sticker {
  position: absolute;
  top: -0.5625rem;
  right: var(--space-lg);
  transform: rotate(3deg);
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
  height: 1.25rem;
  padding: 0 var(--space-md);
  border-radius: var(--radius-sm);
  background: var(--resource-transaction);
  color: var(--primary-foreground);
  font-family: var(--font-mono);
  font-size: 0.65rem;
  font-weight: var(--weight-medium);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  box-shadow: var(--shadow-sm);
}

.price-tile__head {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  min-width: 0;
}

.price-tile__stations {
  margin-left: auto;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--pn-dim);
  white-space: nowrap;
}

.price-tile__kinds {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-lg);
}

.price-tile__kind {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  min-width: 0;
}

/* No DC here. Said by muting the colour, never by opacity: at 0.45 the
   "no DC" line and its badge measured 1.93:1 and 2.04:1 -- the column
   read as absent by making its one sentence unreadable. Dim ink passes on
   its own; the badge goes to an outline, so the column still reads as the
   quiet one beside a real price. */
.price-tile__kind[data-absent="true"] :is(.price-readout__amount, .price-readout__unit) {
  color: var(--pn-dim);
}

.price-tile__kind[data-absent="true"] .kind {
  color: var(--pn-dim);
  background: transparent;
  box-shadow: inset 0 0 0 1px var(--pn-line);
}

/* Where a ribbon would be: the column keeps its height so tiles line up. */
.price-tile__no-ribbon {
  height: 0.875rem;
  border-top: 1px dashed var(--pn-line);
  margin-top: var(--space-sm);
}

.price-tile__foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-md);
}

/* ---- VehicleBatteryTile (spec 119) ---- */

/* The vehicle page's anchor on Night (ADR-0126): the charge as one figure,
   a bar with the limit on it, and the other readings in a row under a
   hairline. Every ink here is an ink-* token, so both schemes hold. */
.battery-tile {
  display: flex;
  flex-direction: column;
  gap: var(--space-lg);
  padding: var(--space-2xl);
  border-radius: var(--radius-2xl);
  background: var(--ink-bg);
  color: var(--ink-text);
  box-shadow: var(--shadow-sm);
  min-width: 0;
}

.battery-tile__top {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-xs) var(--space-lg);
}

.battery-tile__top:empty {
  display: none;
}

.battery-tile__status {
  font-size: var(--text-sm);
  color: var(--ink-dim);
}

.battery-tile__status a {
  color: var(--night-accent);
  font-weight: var(--weight-medium);
  text-decoration: none;
}

@media (hover: hover) {
  .battery-tile__status a:hover {
    text-decoration: underline;
    text-underline-offset: 3px;
  }
}

.battery-tile__note {
  font-size: var(--text-sm);
  color: var(--ink-text);
}

.battery-tile__waiting {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
}

.battery-tile__lead {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-xs) var(--space-md);
}

.battery-tile__lead .battery-tile__label {
  flex-basis: 100%;
}

.battery-tile__figure {
  font-family: var(--font-heading);
  font-size: var(--text-5xl);
  font-weight: var(--weight-semibold);
  line-height: 1;
  letter-spacing: -0.03em;
}

/* Unknown is said in words at the reading size, never as a large dim figure. */
.battery-tile__lead[data-known="false"] .battery-tile__figure {
  font-family: inherit;
  font-size: var(--text-lg);
  font-weight: var(--weight-medium);
  letter-spacing: 0;
  color: var(--ink-dim);
}

.battery-tile__bar {
  position: relative;
}

.battery-tile__track {
  height: 0.75rem;
  border-radius: 999px;
  overflow: hidden;
  background: var(--ink-track);
}

.battery-tile__fill {
  height: 100%;
  border-radius: 999px;
  background: linear-gradient(90deg, var(--brand-from), var(--night-accent-2));
}

.battery-tile__mark {
  position: absolute;
  top: -0.25rem;
  height: 1.25rem;
  width: 2px;
  margin-inline-start: -1px;
  border-radius: 1px;
  background: var(--ink-text);
}

.battery-tile__facts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
  gap: var(--space-lg);
  margin: 0;
  padding-top: var(--space-lg);
  border-top: 1px solid var(--ink-line);
}

.battery-tile__fact {
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
  min-width: 0;
}

.battery-tile__label {
  font-size: var(--text-xs);
  color: var(--ink-dim);
}

.battery-tile__reading {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-xs) var(--space-md);
  margin: 0;
}

.battery-tile__value {
  font-family: var(--font-heading);
  font-size: var(--text-xl);
  font-weight: var(--weight-semibold);
}

/* Unknown is said by dim ink, never by opacity, and never by a figure. */
.battery-tile__fact[data-known="false"] .battery-tile__value {
  color: var(--ink-dim);
  font-family: inherit;
  font-size: var(--text-base);
  font-weight: inherit;
}

.battery-tile__age,
.battery-tile__source {
  font-size: var(--text-xs);
  color: var(--ink-dim);
}

.battery-tile__source {
  font-family: var(--font-mono);
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

/* A stale value is worded, not just coloured: the marker is text. */
.battery-tile__stale {
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  color: var(--ink-text);
  padding: var(--space-2xs) var(--space-sm);
  border: 1px solid var(--ink-line);
  border-radius: var(--radius-sm);
}

/* --- catalog ------------------------------------------------------------ */

/* A part of a catalog page (catalog.rs, `CatalogSection`): a main part has
   a section's worth of air above it, the parts inside it a block apart. */
.catalog-section {
  display: flex;
  flex-direction: column;
  gap: var(--space-lg);
  min-width: 0;
}

.catalog-section[data-major="true"] {
  gap: var(--gap-block);
  margin-top: var(--gap-section);
}

.catalog-section__head {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--space-sm) var(--space-lg);
}

.catalog-section__title {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  min-width: 0;
}

.catalog-section__action {
  display: flex;
}

/* A catalog's entries (catalog.rs). Roomy: systems two across, each with a
   sentence and a flow. Dense: brands by logo, six across, two on a phone --
   never a row that scrolls sideways. */
.catalog-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(22rem, 1fr));
  gap: var(--gap-stack);
  margin: 0;
  padding: 0;
  list-style: none;
}

.catalog-grid[data-density="dense"] {
  grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
  gap: var(--gap-stack);
}

/* The name is an entry's target: its ::after stretches over the whole
   entry, so a click anywhere opens the details, and a focus ring draws
   round the entry rather than the word. */
:is(.system-card__open, .product-tile__open) {
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  text-align: left;
  cursor: pointer;
}

:is(.system-card__open, .product-tile__open)::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
}

:is(.system-card__open, .product-tile__open):focus-visible {
  outline: none;
}

:is(.system-card, .product-tile):has(:is(.system-card__open, .product-tile__open):focus-visible) {
  outline: 2px solid var(--brand-from);
  outline-offset: 2px;
}

:is(.system-card, .product-tile)[data-selected="true"] {
  border-color: var(--brand-from);
  box-shadow: 0 0 0 3px color-mix(in oklab, var(--brand-from) 18%, transparent);
}

@media (hover: hover) {
  :is(.system-card, .product-tile):hover {
    border-color: var(--pn-dim);
  }
}

/* A system the organization links. */
.system-card {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--space-lg);
  min-width: 0;
  padding: var(--space-xl);
  border-radius: var(--radius-3xl);
  border: 0;
  background: var(--pn-raise);
  color: var(--pn-text);
  box-shadow:
    inset 0 1px 0 var(--pn-edge),
    var(--shadow-md);
}

.system-card__head {
  display: flex;
  align-items: flex-start;
  gap: var(--space-lg);
  min-width: 0;
}

.system-card__title {
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
  flex-grow: 1;
  min-width: 0;
}

.system-card__open {
  font-family: var(--font-heading);
  font-size: var(--text-lg);
  line-height: var(--text-lg-leading);
  font-weight: var(--weight-semibold);
  letter-spacing: -0.01em;
}

.system-card__standing {
  flex-shrink: 0;
}

.system-card__foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-md);
  margin-top: auto;
}

/* Above the name's stretch, so the action stays its own target. */
.system-card__action {
  position: relative;
  z-index: 1;
}

/* Which way a system's data goes: the two ends, and what rides between. */
.data-flow {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--space-md);
  padding: var(--space-md) var(--space-lg);
  border-radius: var(--radius-lg);
  border: 1px solid var(--pn-line);
  background: var(--pn-raise-2);
}

.data-flow__end {
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  white-space: nowrap;
}

.data-flow__way {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: var(--space-2xs);
  min-width: 0;
}

/* The words are text, so they take a text colour; the accent rides the
   line under them. */
.data-flow__what {
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  text-align: center;
  color: var(--pn-text);
}

.data-flow__line {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  height: 0.75rem;
  color: var(--brand-from);
  background-image: linear-gradient(
    to right,
    color-mix(in oklab, var(--brand-from) 55%, transparent) 50%,
    transparent 50%
  );
  background-size: 0.5rem 1.5px;
  background-repeat: repeat-x;
  background-position: 0 50%;
}

@media (prefers-reduced-motion: no-preference) {
  .data-flow__line {
    animation: data-flow-ride var(--flow-period) linear infinite;
  }
}

@keyframes data-flow-ride {
  to {
    background-position: 0.5rem 50%;
  }
}

/* A brand: its logo on the stage, which is most of the tile. The stage is
   light in both themes (`--logo-stage`), so every logo keeps its colours. */
.product-tile {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  min-width: 0;
  padding: var(--space-sm) var(--space-sm) var(--space-lg);
  border-radius: var(--radius-3xl);
  border: 0;
  background: var(--pn-raise);
  box-shadow:
    inset 0 1px 0 var(--pn-edge),
    var(--shadow-md);
  color: var(--pn-text);
}

.product-tile__stage {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  height: 8rem;
  border-radius: var(--radius-xl);
  background: var(--logo-stage);
  color: var(--logo-ink);
}

.product-tile__here,
.product-tile__verified {
  position: absolute;
  top: var(--space-sm);
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
  height: 1.375rem;
  padding: 0 var(--space-sm);
  border-radius: var(--radius-pill);
  font-size: var(--text-2xs);
  font-weight: var(--weight-semibold);
  white-space: nowrap;
}

.product-tile__here {
  left: var(--space-sm);
  background: var(--ink-bg);
  color: var(--ink-text);
}

.product-tile__here::before {
  content: "";
  width: 0.375rem;
  height: 0.375rem;
  border-radius: 50%;
  background: var(--night-accent);
}

/* On the stage, which is light in both themes: the stage's own ink,
   whatever the page's theme. */
.product-tile__verified {
  right: var(--space-sm);
  border: 1px solid color-mix(in oklab, var(--logo-ink) 22%, transparent);
  background: var(--logo-stage);
  color: var(--logo-ink);
}

.product-tile__body {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  padding: 0 var(--space-sm);
  min-width: 0;
}

.product-tile__open {
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  text-decoration: none;
}

/* A tile whose logo says its name has nothing to write here: the button
   is then the tile's whole face rather than a word, so it still has a box
   to be seen, focused and pressed. */
.product-tile__open:empty {
  position: absolute;
  inset: 0;
  border-radius: inherit;
}

.product-tile__ways {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xs) var(--space-md);
}

.product-tile__way {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2xs);
  font-size: var(--text-xs);
  color: var(--pn-dim);
}

/* "Cloud soon" says it in words. Not dimmed with `opacity`: text faded
   that way is measured at its blended colour, and fails contrast. */
.product-tile__way[data-way="cloud-soon"] {
  font-style: italic;
}


/* --- ChargingCar (canvas v19 OrgSeesCars) ------------------------------- */

/* One outlet with a car on it. A card of its own rather than a row: the
   three states it can be in differ in what they say, not in a column, so a
   table would leave two of the three mostly empty. */
.charging-car {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  padding: var(--space-lg);
  border: 0;
  border-radius: var(--radius-2xl);
  background: var(--pn-raise-2);
}

.charging-car__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-md);
}

.charging-car__place {
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--pn-dim);
}

/* What it is drawing, with the dot that says it is drawing it. */
.charging-car__draw {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
  font-size: var(--text-xs);
  font-variant-numeric: tabular-nums;
  color: var(--pn-text);
}

.charging-car__draw::before {
  content: "";
  width: 0.5rem;
  height: 0.5rem;
  border-radius: var(--radius-pill);
  background: var(--status-ok);
}

.charging-car__who {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  min-width: 0;
}

.charging-car__picture {
  display: flex;
  flex-shrink: 0;
}

.charging-car__names {
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
  min-width: 0;
}

/* The figure, what it is heading for, and what it still needs -- the last
   pushed to the end, because it is the number a site acts on. */
.charging-car__battery {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--space-sm);
}

.charging-car__figure {
  font-family: var(--font-heading);
  font-size: var(--text-3xl);
  font-weight: var(--weight-semibold);
  font-variant-numeric: tabular-nums;
  line-height: 1;
}

/* The number a site acts on, so it carries weight -- but the page's own
   text colour and not the brand green. At this size the green is 3.76:1 on
   a light card, under the 4.5:1 small text has to meet, and there is no
   brand-on-surface pair in the token layer's contrast table for exactly
   that reason. The bar below it still carries the colour: it is not text. */
.charging-car__needs {
  margin-inline-start: auto;
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  color: var(--pn-text);
}

.charging-car__track {
  position: relative;
  display: block;
  height: 6px;
  border-radius: var(--radius-pill);
  background: var(--pn-track);
}

.charging-car__fill {
  display: block;
  height: 100%;
  border-radius: var(--radius-pill);
  background: linear-gradient(90deg, var(--brand-from), var(--night-accent-2));
}

.charging-car__mark {
  position: absolute;
  top: -3px;
  bottom: -3px;
  width: 2px;
  margin-inline-start: -1px;
  border-radius: 1px;
  background: var(--pn-text);
}

/* No battery to show: the reason, and what the charge has delivered
   anyway, on one quiet shelf. */
.charging-car__instead {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--space-md);
  padding: var(--space-md);
  border-radius: var(--radius-lg);
  background: var(--pn-bg);
}

.charging-car__energy {
  margin-inline-start: auto;
  font-family: var(--font-heading);
  font-size: var(--text-lg);
  font-weight: var(--weight-semibold);
  font-variant-numeric: tabular-nums;
}

.charging-car__foot {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--space-md);
  padding-top: var(--space-md);
  border-top: 1px solid var(--pn-line);
}

.charging-car__aside {
  margin-inline-start: auto;
  font-size: var(--text-xs);
  color: var(--pn-dim);
  text-align: end;
}

/* The reply to a search: light, standing on the dark question it answers
   (`data-theme="light"`), so it reads as the answer and not as more of
   the question. */
.search-answer {
  display: flex;
  gap: var(--space-lg);
  width: 100%;
  max-width: 32.5rem;
  box-sizing: border-box;
  padding: var(--space-lg);
  border-radius: var(--radius-2xl);
  background: var(--pn-raise);
  color: var(--pn-text);
  box-shadow: var(--shadow-lg);
}

.search-answer__body {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  min-width: 0;
}

.search-answer__top {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-sm);
}

.search-answer__verdict {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  color: var(--status-ok);
}

.search-answer[data-verdict="soon"] .search-answer__verdict {
  color: var(--pn-dim);
}

.search-answer[data-verdict="unlisted"] .search-answer__verdict {
  color: var(--pn-text);
}

.search-answer__title {
  margin: 0;
  font-family: var(--font-heading);
  font-size: var(--text-xl);
  line-height: var(--text-xl-leading);
  font-weight: var(--weight-semibold);
  letter-spacing: -0.015em;
}

.search-answer__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-sm);
  margin-top: var(--space-xs);
}

/* Continuous corners (ADR-0212): the curve SwiftUI draws, where the browser
   can draw it. A guarded enhancement -- a browser without `corner-shape`
   keeps the plain radius above, and nothing else changes. */
@supports (corner-shape: squircle) {
  .project-card {
    corner-shape: var(--corner-continuous);
  }

  .system-card {
    corner-shape: var(--corner-continuous);
  }

  .product-tile {
    corner-shape: var(--corner-continuous);
  }

  .charging-car {
    corner-shape: var(--corner-continuous);
  }
}

@container shell (max-width: 47.9375rem) {

  /* Brands two across: a grid a thumb scrolls down, never sideways. */
.shell .catalog-section[data-major="true"] {
    margin-top: var(--gap-block);
  }

.shell .catalog-grid[data-density="dense"] {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--space-md);
  }

.shell .product-tile__stage {
    height: 6rem;
  }

.shell .catalog-grid:not([data-density="dense"]) {
    grid-template-columns: minmax(0, 1fr);
  }
}


/* supply.css -- The supply: the location's supply table and the line chips. */

/* --- supply table ------------------------------------------------------- */

.supply-table__name {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  min-width: 0;
}

.supply-table__name[data-depth="1"] {
  padding-inline-start: var(--space-2xl);
}

.supply-table__name[data-depth="2"] {
  padding-inline-start: calc(var(--space-2xl) * 2);
}

.supply-table__name[data-depth="3"] {
  padding-inline-start: calc(var(--space-2xl) * 3);
}

.supply-table__name[data-depth="4"] {
  padding-inline-start: calc(var(--space-2xl) * 4);
}

.supply-table__glyph {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 1.625rem;
  height: 1.625rem;
  border-radius: var(--radius-md);
  border: 1px solid var(--pn-line);
  background: var(--pn-raise);
  color: var(--pn-dim);
}

.supply-table__name[data-kind="main"] .supply-table__glyph {
  color: var(--status-ok);
}

.supply-table__text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

/* --- line chips ---------------------------------------------------------- */

/* One set of lines reads as one unit: a three-phase device's chips never
   break across two rows of a narrow column. */
.line-chips {
  display: inline-flex;
  flex-wrap: nowrap;
  gap: var(--space-xs);
}

.line-chip {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 1.75rem;
  height: 1.25rem;
  padding: 0 var(--space-xs);
  border-radius: var(--radius-sm);
  font-size: var(--text-2xs);
  font-weight: var(--weight-semibold);
  box-shadow: 0 0 0 1px var(--ink-line);
}

.line-chip[data-line="l1"] {
  background: var(--line-l1);
  color: var(--line-l1-ink);
}

.line-chip[data-line="l2"] {
  background: var(--line-l2);
  color: var(--line-l2-ink);
}

.line-chip[data-line="l3"] {
  background: var(--line-l3);
  color: var(--line-l3-ink);
}

/* overlays.css -- What opens over a page: menus, drawers, sheets, slide-overs, dialogs, disclosures. */

/* --- menu ---------------------------------------------------------------- */

/* A control and, while open, its menu under it, flush with its end edge. */
.menu-host {
  position: relative;
  display: inline-flex;
}

/* Catches the click outside. Transparent: the page stays readable. */
.menu__scrim {
  position: fixed;
  inset: 0;
  z-index: 19;
}

.menu {
  position: absolute;
  top: calc(100% + var(--space-sm));
  right: 0;
  z-index: 20;
  width: 16.5rem;
  max-width: calc(100vw - 2 * var(--space-lg));
  box-sizing: border-box;
  padding: var(--space-xs);
  /* Glass (ADR-0212): a menu floats over the page and carries only
     actions, so the page shows through it. */
  border: 1px solid var(--glass-line);
  border-radius: var(--radius-2xl);
  background: var(--glass);
  backdrop-filter: blur(var(--glass-blur));
  box-shadow:
    inset 0 1px 0 var(--glass-highlight),
    var(--shadow-lg);
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.menu__header {
  padding: var(--space-sm) var(--space-sm) var(--space-md);
  margin-bottom: var(--space-xs);
  border-bottom: 1px solid var(--pn-line);
}

.menu__account {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  min-width: 0;
}

.menu__portrait {
  flex-shrink: 0;
  width: 2rem;
  height: 2rem;
  border-radius: var(--radius-pill);
  overflow: hidden;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: linear-gradient(135deg, var(--brand-from), var(--brand-to));
  color: var(--pn-bg);
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
}

.menu__portrait img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.menu__who {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.menu__name,
.menu__email {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.menu__name {
  color: var(--pn-text);
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
}

.menu__email {
  color: var(--pn-dim);
  font-size: var(--text-xs);
}

.menu__item {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  min-height: 2.25rem;
  padding: 0 var(--space-md);
  border: 0;
  border-radius: var(--radius-xl);
  background: transparent;
  color: var(--pn-text);
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  text-align: left;
  text-decoration: none;
  cursor: pointer;
}

@media (hover: hover) {
  .menu__item:hover {
    background: color-mix(in oklab, var(--pn-text) 7%, transparent);
  }
}

.menu__item:focus-visible {
  outline: 2px solid var(--brand-from);
  outline-offset: -2px;
}

.menu__icon {
  display: inline-flex;
  color: var(--pn-dim);
}

.menu__label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.menu__separator {
  height: 1px;
  margin: var(--space-xs) 0;
  background: var(--pn-line);
}

/* Fingers, not pointers: taller rows on a phone. */
@media (max-width: 40rem) {
  .menu__item {
    min-height: var(--touch-target);
  }
}

/* The disclosure: what is kept but not usually wanted, folded under one
   line -- see libs/ui/src/disclosure.rs. The mark turns with the
   browser's own open state. */
.disclosure {
  display: flex;
  flex-direction: column;
}

.disclosure__summary {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
  align-self: flex-start;
  padding: var(--space-xs) 0;
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  color: var(--pn-dim);
  list-style: none;
  cursor: pointer;
}

@media (pointer: coarse) {
  .disclosure__summary {
    min-height: var(--touch-target);
  }
}

.disclosure__summary::-webkit-details-marker {
  display: none;
}

.disclosure__summary:focus-visible {
  outline: 2px solid var(--ring);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

@media (hover: hover) {
  .disclosure__summary:hover {
    color: var(--pn-text);
  }
}

.disclosure__mark {
  display: inline-flex;
  transition: transform var(--motion-fast) var(--ease-out);
}

.disclosure[open] > .disclosure__summary .disclosure__mark {
  transform: rotate(90deg);
}

.disclosure__body {
  display: flex;
  flex-direction: column;
  gap: var(--gap-stack);
  padding-top: var(--space-sm);
}

/* --- slide over ---------------------------------------------------------- */

/* A column at the page's right edge, sized by its container: the page's
   grid decides where the edge is. Not modal, so no scrim. */
.slide-over {
  width: 25rem;
  max-width: 100%;
  height: 100%;
  min-height: 0;
  display: flex;
  flex-direction: column;
  background: var(--pn-raise);
  border-left: 1px solid var(--pn-line);
  box-shadow: -8px 0 24px color-mix(in oklab, var(--foreground) 6%, transparent);
  color: var(--pn-text);
}

.slide-over__head {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  padding: var(--space-xl) var(--space-2xl) var(--space-lg);
  border-bottom: 1px solid var(--pn-line);
}

.slide-over__title-row {
  display: flex;
  align-items: center;
  gap: var(--space-md);
}

.slide-over__title {
  flex: 1;
  min-width: 0;
  margin: 0;
  font-family: var(--font-heading);
  font-size: var(--text-base);
  line-height: var(--text-base-leading);
  font-weight: var(--weight-semibold);
}

.slide-over__close {
  appearance: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  border: 0;
  border-radius: var(--radius-xl);
  background: none;
  color: var(--pn-dim);
  cursor: pointer;
}

@media (hover: hover) {
  .slide-over__close:hover {
    color: var(--pn-text);
    background: var(--pn-raise-2);
  }
}

.slide-over__close:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px color-mix(in oklch, var(--ring), transparent 70%);
}

.slide-over__body {
  flex: 1;
  min-height: 0;
  overflow: auto;
  padding: var(--space-lg) var(--space-2xl);
  display: flex;
  flex-direction: column;
  gap: var(--space-lg);
}

.slide-over__foot {
  display: flex;
  align-items: center;
  gap: var(--space-lg);
  padding: var(--space-lg) var(--space-2xl);
  border-top: 1px solid var(--pn-line);
  background: var(--pn-raise-2);
}

.slide-over__foot > :first-child {
  flex: 1;
  min-width: 0;
}

/* --- drawer ------------------------------------------------------------- */

/* Slides in from the right edge and holds the page's attention. Fixed to
   the viewport rather than the shell, because it belongs to the document
   while it is open, and `contain: layout` on the shell makes the shell
   its containing block anyway -- which is what keeps the storybook's
   375px frame from being escaped. Above the top bar (20) and the phone
   bottom bar (20); nothing else on the page is above it. */
/* Absolute, not fixed, and that is what makes it whole. A page renders
   its drawer inside `.page`, which scrolls, inside `.shell__main`, which
   scrolls; a *fixed* drawer's containing block is the shell (`contain:
   layout`), so both of those sat between the drawer and its containing
   block and clipped it -- every drawer lost its title off the top edge,
   and on a phone its buttons behind the bottom bar. Positioned against
   `.shell__body` instead, the scrollers are not in its containing block
   chain and cannot clip it. The cost is that the bars stay visible
   beside the scrim, which on a phone is what a sheet does anyway. */
.drawer {
  position: absolute;
  inset: 0;
  z-index: 30;
  display: flex;
  justify-content: flex-end;
}

/* Beside the standing column (ADR-0213) a drawer covers the page, not the
   column: a proposal's form and the conversation that filled it stay on
   screen together. The rail, folded or under 1360px, keeps its strip too. */
.shell[data-aside="standing"] .drawer {
  right: var(--aside-width);
}

.shell[data-aside="folded"] .drawer {
  right: var(--rail-width);
}

@container shell (max-width: 84.9375rem) {
  .shell[data-aside] .drawer {
    right: var(--rail-width);
  }
}

/* On the chooser's sky (`.scene`: the profile, an invitation) there is no
   shell. The document itself scrolls and `.scene__body` is a centred
   reading column, so a drawer positioned against it opened as a strip in
   the middle of the page. There nothing scrolls between the drawer and the
   viewport, so it belongs to the viewport. */
.scene .drawer {
  position: fixed;
}

.drawer__scrim {
  position: absolute;
  inset: 0;
  background: color-mix(in oklch, var(--foreground), transparent 55%);
  animation: drawer-fade var(--motion-base) var(--ease-out) both;
}

/* Two columns at desktop width: the reading column at a fixed measure,
   the doing column taking the rest. The panel is as wide as both need
   and no wider than the viewport. */
.drawer__panel {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  width: min(100%, 34rem);
  height: 100%;
  background: var(--pn-bg);
  color: var(--pn-text);
  box-shadow: var(--shadow-lg);
  border-left: 1px solid var(--hairline);
  animation: drawer-slide 0.25s cubic-bezier(0.2, 0, 0, 1) both;
  overflow: hidden;
}

.drawer__panel[data-aside="true"] {
  width: min(100%, 56rem);
  grid-template-columns: 19rem minmax(0, 1fr);
}

.drawer__panel:focus-visible {
  outline: none;
}

.drawer__aside {
  display: flex;
  flex-direction: column;
  gap: var(--gap-stack);
  padding: var(--space-2xl) var(--space-xl);
  background: var(--pn-raise-2);
  border-right: 1px solid var(--hairline);
  overflow: auto;
  min-height: 0;
}

.drawer__main {
  display: flex;
  flex-direction: column;
  min-width: 0;
  min-height: 0;
  background: var(--pn-raise);
}

.drawer__head {
  display: flex;
  align-items: flex-start;
  gap: var(--space-lg);
  padding: var(--space-xl) var(--space-xl) var(--space-lg);
  border-bottom: 1px solid var(--hairline);
}

/* A picture in the chip's place keeps the chip's footprint. */
.drawer__figure {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}

.drawer__text {
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
  min-width: 0;
  flex: 1;
}

.drawer__title {
  margin: 0;
  font-family: var(--font-heading);
  font-size: var(--text-lg);
  line-height: var(--text-lg-leading);
  font-weight: var(--weight-semibold);
  color: var(--pn-text);
}

/* Focused by script on open and on each new question, so a screen reader
   hears it; a heading needs no ring of its own. */
.drawer__title:focus {
  outline: none;
}

.drawer__close {
  border: 0;
  background: none;
  padding: var(--space-xs);
  display: inline-flex;
  flex-shrink: 0;
  border-radius: var(--radius-md);
  color: var(--pn-dim);
  cursor: pointer;
}

@media (hover: hover) {
  .drawer__close:hover {
    color: var(--pn-text);
    background: var(--pn-raise-2);
  }
}

.drawer__close:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px color-mix(in oklch, var(--ring), transparent 70%);
}

.drawer__steps {
  padding: var(--space-md) var(--space-xl);
  border-bottom: 1px solid var(--hairline);
}

/* The body scrolls; the head, the steps and the foot stay. */
.drawer__phone-aside {
  display: none;
}

.drawer__body {
  flex: 1;
  min-height: 0;
  overflow: auto;
  display: flex;
  flex-direction: column;
  gap: var(--gap-stack);
  padding: var(--space-xl);
}

.drawer__foot {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--space-md);
  flex-wrap: wrap;
  padding: var(--space-lg) var(--space-xl);
  background: var(--pn-raise-2);
  border-top: 1px solid var(--hairline);
}

@keyframes drawer-slide {
  from {
    transform: translateX(100%);
  }

  to {
    transform: translateX(0);
  }
}

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

  to {
    opacity: 1;
  }
}

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

/* --- sheet -------------------------------------------------------------- */

/* A panel rising from the bottom edge over a dimmed page. Absolute, not
   fixed, for the drawer's reason (see `.drawer`): a page renders its sheet
   inside `.page`, which scrolls, inside `.shell__main`, which scrolls, and a
   fixed box's containing block is the shell (`contain: layout`), so a fixed
   sheet was drawn *under* the phone's bottom bar with its last controls
   behind it. Positioned against `.shell__body` it ends where the bar
   begins, and the bar keeps its own place beneath the scrim. A specimen
   in the storybook is positioned against its own frame. Level with the
   drawer (30); the two are never open together. */
.sheet {
  position: absolute;
  inset: 0;
  z-index: 30;
  display: flex;
  align-items: flex-end;
  justify-content: center;
}

/* The drawer's fade: one keyframe for both, because a page dimming is the
   same motion from either edge. */
.sheet__scrim {
  position: absolute;
  inset: 0;
  background: color-mix(in oklch, var(--foreground), transparent 55%);
  animation: drawer-fade var(--motion-base) var(--ease-out) both;
}

/* As tall as its content up to the ceiling: the box it sits in less the
   peek, or 70% of it where that would leave too little to read.
   `backwards`, not `both`: a held `translateY(0)` at the end of the rise
   would beat the drag's own transform for as long as the sheet is open. */
.sheet__panel {
  position: relative;
  display: flex;
  flex-direction: column;
  width: min(100%, 40rem);
  max-height: max(70%, calc(100% - var(--sheet-peek)));
  overflow: hidden;
  background: var(--pn-raise);
  color: var(--pn-text);
  border-radius: var(--radius-3xl) var(--radius-3xl) 0 0;
  box-shadow: var(--shadow-lg);
  padding-bottom: env(safe-area-inset-bottom, 0px);
  transform: translateY(var(--sheet-drag));
  transition: transform var(--motion-base) var(--ease-out);
  animation: sheet-rise var(--motion-slow) var(--ease-move) backwards;
}

/* It follows the finger exactly; the settle back is the transition above. */
.sheet[data-dragging="true"] .sheet__panel {
  transition: none;
}

/* The whole screen: a conversation wants every line a phone has. `fixed`
   measures the shell (`contain: layout`), which on a phone is the screen,
   so it covers the top bar and the bottom bar too, and clears the notch
   itself. The body is a column, so a section that scrolls
   (`.panel-section[data-scrolls]`) takes the height and the composer
   under it stays in view. */
.sheet[data-full="true"] {
  position: fixed;
  inset: 0;
  z-index: 40;
}

.sheet[data-full="true"] .sheet__panel {
  width: 100%;
  height: 100%;
  max-height: none;
  border-radius: 0;
  padding-top: env(safe-area-inset-top, 0px);
}

.sheet[data-full="true"] .sheet__body {
  display: flex;
  flex-direction: column;
}

.sheet__panel:focus-visible {
  outline: none;
}

/* The two ends of the focus trap, either side of the panel: focusable, and
   nothing to see. Taken out of the flex row so they take no room, and
   pinned to the corner so that focusing one scrolls nothing. */
.sheet__sentinel {
  position: absolute;
  top: 0;
  left: 0;
  width: 1px;
  height: 1px;
  overflow: hidden;
  opacity: 0;
}

/* The whole row is the target, the bar is what is drawn. `touch-action`
   is what lets a finger drag it without the browser taking the gesture
   for a scroll and cancelling the pointer. */
.sheet__grab {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  height: var(--touch-target);
  cursor: grab;
  touch-action: none;
}

.sheet[data-dragging="true"] .sheet__grab {
  cursor: grabbing;
}

.sheet__handle {
  width: 2.5rem;
  height: 0.25rem;
  border-radius: var(--radius-pill);
  background: var(--pn-dim);
  opacity: 0.5;
}

/* Over the handle's row, at its end, so the panel needs no header. */
.sheet__close {
  position: absolute;
  top: 0;
  inset-inline-end: var(--space-xs);
  z-index: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: var(--touch-target);
  min-height: var(--touch-target);
  padding: 0;
  border: 0;
  border-radius: var(--radius-lg);
  background: none;
  color: var(--pn-dim);
  cursor: pointer;
}

@media (hover: hover) {
  .sheet__close:hover {
    color: var(--pn-text);
    background: var(--pn-raise-2);
  }
}

.sheet__close:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px color-mix(in oklch, var(--ring), transparent 70%);
}

/* The body scrolls; the handle and the close button stay. It brings no
   padding: what is inside is the caller's, and often has its own. */
.sheet__body {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
}

.sheet__body:focus-visible {
  outline: 2px solid var(--ring);
  outline-offset: -2px;
}

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

  to {
    transform: translateY(0);
  }
}

@media (prefers-reduced-motion: reduce) {
  .sheet__panel,
  .sheet__scrim {
    animation: none;
    transition: none;
  }
}

/* --- steps -------------------------------------------------------------- */

.steps {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  margin: 0;
  padding: 0;
  list-style: none;
}

.steps__step {
  display: inline-flex;
  align-items: center;
  gap: var(--space-sm);
  padding: var(--space-2xs) var(--space-md) var(--space-2xs) var(--space-xs);
  border-radius: var(--radius-pill);
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--pn-dim);
}

.steps__step[data-state="current"] {
  background: color-mix(in oklch, var(--primary), transparent 88%);
  color: var(--primary);
}

.steps__step + .steps__step::before {
  content: "";
  width: 1rem;
  height: 1px;
  margin-right: var(--space-xs);
  background: var(--pn-line);
}

.steps__number {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.125rem;
  height: 1.125rem;
  border-radius: var(--radius-pill);
  background: var(--pn-raise-2);
  color: var(--pn-dim);
  font-size: var(--text-2xs);
  letter-spacing: 0;
}

.steps__step[data-state="current"] .steps__number {
  background: var(--primary);
  color: var(--primary-foreground);
}

.steps__step[data-state="done"] .steps__number {
  background: color-mix(in oklch, var(--status-ok), transparent 85%);
  color: var(--status-ok);
}

.steps__name {
  white-space: nowrap;
}

@media (max-width: 47.9375rem) {
  .drawer__panel,
  .drawer__panel[data-aside="true"] {
    width: 100%;
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: minmax(0, 1fr);
    border-left: 0;
  }

  /* The aside is the reading column, and a phone has no room for a
     reading column beside the doing one: at 40vh it was a paragraph cut
     mid-sentence above the form. The form takes the height; the aside's
     prose is the page's, and the page is one tap back. */
  .drawer__aside {
    display: none;
  }

  /* What of the aside a phone keeps -- a live preview of what the form
     makes -- after the form, under a rule. */
  .drawer__phone-aside {
    display: flex;
    flex-direction: column;
    gap: var(--space-md);
    padding-top: var(--space-lg);
    border-top: 1px solid var(--pn-line);
  }

  .steps__name {
    display: none;
  }

  .steps__step[data-state="current"] .steps__name {
    display: inline;
  }
}

/* The modal question. Rendered open, so focus, the trap and Escape are
 * dialog.rs's own (its module docs); this styles the box inside it. */

.dialog {
  max-width: 32rem;
  padding: 0;
  border: 0;
  border-radius: var(--radius-3xl);
  /* A solid surface, as the sheet and the drawer have: `--raised` is a 4%
     tint meant to lie over a card, and a dialog is drawn over the page, so
     the page's own text showed through it. */
  background: var(--pn-raise);
  color: var(--foreground);
  box-shadow: var(--shadow-lg);
}

/* Rendered `open` rather than through `showModal()` -- this crate cannot
   reach the browser's API -- so it is not in the top layer: it would sit
   wherever it falls in the page, which on a phone was behind the tab bar.
   Held in the middle of the shell instead, clear of both bars. */
.dialog[open] {
  position: fixed;
  inset: 0;
  z-index: 50;
  width: min(32rem, calc(100% - 2 * var(--space-lg)));
  height: fit-content;
  max-height: calc(100% - 2 * var(--space-lg));
  margin: auto;
  overflow: auto;
}

.dialog::backdrop {
  background: var(--sky-ground);
  opacity: 0.7;
}

.dialog__panel {
  display: grid;
  gap: var(--space-md);
  padding: var(--space-xl);
}

.dialog__title {
  margin: 0;
  font-family: var(--font-heading);
  font-size: var(--text-lg);
  line-height: var(--text-lg-leading);
  font-weight: var(--weight-semibold);
}

.dialog__description {
  display: grid;
  gap: var(--space-xs);
}

.dialog__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-sm);
  justify-content: flex-end;
}

/* Programmatic focus lands on the title when the dialog opens; the
   question is what is already in view, so it wears no ring. */
.dialog__title:focus {
  outline: none;
}

/* The two ends of the focus trap, either side of the panel: focusable, and
   nothing to see, as the sheet's. */
.dialog__sentinel {
  position: absolute;
  top: 0;
  left: 0;
  width: 1px;
  height: 1px;
  overflow: hidden;
  opacity: 0;
}

/* --- docked panel (docked_panel.rs) --------------------------------------- */

.docked-panel {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 25rem;
  gap: var(--space-xl);
  align-items: start;
}

.docked-panel__main {
  display: flex;
  flex-direction: column;
  gap: var(--space-lg);
  min-width: 0;
}

.docked-panel__dock {
  position: sticky;
  top: var(--space-lg);
  min-width: 0;
}

.docked-panel__pin {
  display: none;
}

.panel-head {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  padding: var(--space-lg);
  background: var(--ink-bg);
  color: var(--ink-text);
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
}

/* The mark, the words and the small actions on one row. */
.panel-head__row {
  display: flex;
  align-items: center;
  gap: var(--space-md);
}

.panel-head__figure {
  display: inline-flex;
  flex-shrink: 0;
}

.panel-head__words {
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
  min-width: 0;
  flex: 1;
}

.panel-head__actions {
  display: flex;
  gap: var(--space-xs);
  flex-shrink: 0;
}

/* A ghost button on Night takes the ink's own colours: the page's would
   be dark on dark. */
.panel-head__actions .button {
  color: var(--ink-text);
}

@media (hover: hover) {
  .panel-head__actions .button:hover {
    background: var(--ink-raise);
  }
}

.panel-head__eyebrow {
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--brand-from);
}

/* The head is Night in both themes, so its prose takes the ink's own dim,
   not the panel's muted tone (which fails 4.5:1 there). */
.panel-head__lede {
  margin: 0;
  font-size: var(--text-xs);
  color: var(--ink-dim);
}

.panel-head__control {
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
  padding: var(--space-sm) var(--space-md);
  border-radius: var(--radius-md);
  background: var(--pn-raise);
  color: var(--pn-text);
}

.panel-section {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  padding: var(--space-md) var(--space-lg);
}

.panel-section + .panel-section {
  border-top: 1px solid var(--pn-line);
}

/* Inside a height-bound panel (the shell's standing column) this is the
   part that scrolls, so the composer after it stays in view. */
.panel-section[data-scrolls="true"] {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
}

.panel-section[data-scrolls="true"]:focus-visible {
  outline: 2px solid var(--ring);
  outline-offset: -2px;
}

.panel-section__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-sm);
}

.panel-section__heading {
  margin: 0;
  font-family: var(--font-heading);
  font-size: var(--text-base);
  font-weight: var(--weight-semibold);
}

/* Continuous corners (ADR-0212): the curve SwiftUI draws, where the browser
   can draw it. A guarded enhancement -- a browser without `corner-shape`
   keeps the plain radius above, and nothing else changes. */
@supports (corner-shape: squircle) {
  .menu {
    corner-shape: var(--corner-continuous);
  }

  .sheet__panel {
    corner-shape: var(--corner-continuous);
  }

  .dialog {
    corner-shape: var(--corner-continuous);
  }
}

@container shell (max-width: 68.6875rem) {
  /* Under the tablet width there is no room beside the page: the dock
     goes, and a pinned button opens the same panel in a sheet. */
  .docked-panel {
    grid-template-columns: minmax(0, 1fr);
  }

  .docked-panel__dock {
    display: none;
  }

  .docked-panel__pin {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    position: fixed;
    right: var(--space-lg);
    bottom: calc(var(--space-lg) + env(safe-area-inset-bottom, 0px));
    z-index: 20;
    height: var(--touch-target);
    padding: 0 var(--space-lg);
    border-radius: var(--radius-pill);
    border: none;
    background: var(--primary);
    color: var(--primary-foreground);
    font-size: var(--text-sm);
    font-weight: var(--weight-semibold);
    cursor: pointer;
    box-shadow: var(--shadow-md);
  }
}

@container shell (max-width: 47.9375rem) {
  /* No rail on a phone: the drawer is the screen's width again. */
.shell[data-aside] .drawer {
    right: 0;
  }

  /* On a phone the bottom bar owns the screen's foot and its own inset, and
   the sheet ends above it: padding for the home indicator here would be a
   gap over the bar. */

.sheet__panel {
    padding-bottom: 0;
  }

  /* A full sheet covers the bar, so it keeps the home indicator's inset. */
.sheet[data-full="true"] .sheet__panel {
    padding-bottom: env(safe-area-inset-bottom, 0px);
  }

  /* Menus are sheets: from the bottom edge, full width, over a dimmed
     page. `fixed` measures the shell here (`contain: layout`), which on a
     phone is the screen. The scrim that catches the click outside is the
     dimming. */
.shell .menu__scrim {
    background: color-mix(in oklch, var(--foreground), transparent 60%);
    z-index: 39;
  }

.shell .menu,
  .shell .switcher__menu,
  .shell .sidebar__account .menu,
  .shell .bottombar__more .menu {
    position: fixed;
    top: auto;
    right: 0;
    bottom: 0;
    left: 0;
    z-index: 40;
    width: auto;
    max-width: none;
    max-height: 85%;
    overflow: auto;
    padding: var(--space-xl) var(--space-lg) calc(var(--space-2xl) + env(safe-area-inset-bottom, 0px));
    border: 0;
    border-radius: var(--radius-2xl) var(--radius-2xl) 0 0;
    background: var(--pn-bg);
    animation-name: sheet-rise;
    animation-duration: var(--motion-slow);
    animation-timing-function: var(--ease-move);
  }

.shell .menu__item,
  .shell .switcher__link {
    min-height: 3.25rem;
    padding-inline: var(--space-xl);
    border-radius: 0;
    background: var(--pn-raise);
    color: var(--pn-text);
    font-size: var(--text-base);
  }

.shell .menu__item:first-of-type,
  .shell .switcher__section > .switcher__link:first-of-type {
    border-start-start-radius: var(--radius-2xl);
    border-start-end-radius: var(--radius-2xl);
  }

.shell .menu__item:last-of-type,
  .shell .switcher__section > .switcher__link:last-of-type {
    border-end-start-radius: var(--radius-2xl);
    border-end-end-radius: var(--radius-2xl);
  }

.shell .menu__header {
    border-bottom: 0;
    padding-inline: var(--space-xl);
  }

  /* On a phone the tab bar owns the screen's foot, and the shell (which
     `contain: layout` makes the containing block for anything fixed) ends
     below it: the pin is lifted clear of the bar. */
.shell .docked-panel__pin {
    bottom: calc(var(--bottombar-height) + var(--space-md) + env(safe-area-inset-bottom, 0px));
  }
}

/* chrome.css -- The console's frame: shell, sidebar, header, trail, tab bar, page, settings, scope and mode. */

/* --- index with a preview ----------------------------------------------- */

/* The list, and beside it the selected row opened. The panel stays in
   view while the list scrolls under it. */
.index-preview {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(18rem, 22rem);
  align-items: start;
  gap: var(--gap-block);
}

.index-preview__list {
  min-width: 0;
}

/* On an index the table is porcelain of its own: inside a card it bleeds
   to the card's edge, here it is the surface. */
.index-preview__list .table {
  margin: 0;
  padding: var(--space-sm) var(--space-md);
  background: var(--pn-raise);
  border-radius: var(--radius-3xl);
  box-shadow:
    inset 0 1px 0 var(--pn-edge),
    var(--shadow-md);
}

.index-preview__aside {
  position: sticky;
  top: var(--space-lg);
  display: flex;
  flex-direction: column;
  gap: var(--space-lg);
  min-width: 0;
  padding: var(--space-xl);
  border: 0;
  border-radius: var(--radius-3xl);
  background: var(--pn-raise);
  box-shadow:
    inset 0 1px 0 var(--pn-edge),
    var(--shadow-md);
}

.index-preview__back {
  display: none;
}

/* Below a laptop's width there is room for one of the two: the list, or,
   once a row is opened, the preview with a way back. */
@media (max-width: 64rem) {
  .index-preview {
    grid-template-columns: minmax(0, 1fr);
  }

  .index-preview[data-open="true"] > .index-preview__list,
  .index-preview:not([data-open="true"]) > .index-preview__aside {
    display: none;
  }

  .index-preview__aside {
    position: static;
  }

  .index-preview__back {
    display: inline-flex;
    align-items: center;
    align-self: flex-start;
    gap: var(--space-xs);
    min-height: var(--touch-target);
    margin: calc(var(--space-sm) * -1) 0 0 calc(var(--space-xs) * -1);
    padding: 0 var(--space-sm);
    border: 0;
    border-radius: var(--radius-md);
    background: none;
    color: var(--pn-dim);
    font: inherit;
    font-size: var(--text-sm);
    cursor: pointer;
  }

  .index-preview__back:focus-visible {
    outline: 2px solid var(--ring);
  }
}

/* The search and filters above an index. */
.index-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--space-md) var(--space-xl);
}

/* A preview's parts: a head, labelled sections, lines with an action at
   the end, and a foot. */
.preview-head {
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
}

.preview-head__line {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-sm);
}

.preview-head__title {
  margin: 0;
  font-family: var(--font-heading);
  font-size: var(--text-lg);
  font-weight: var(--weight-semibold);
  color: var(--pn-text);
  overflow-wrap: anywhere;
}

.preview-head__detail {
  margin: 0;
  font-size: var(--text-xs);
  line-height: var(--text-xs-leading);
  color: var(--pn-dim);
}

.preview-section {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
}

.preview-section__label {
  margin: 0;
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  color: var(--pn-dim);
}

.preview-section__body {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-xs);
}

.preview-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-md);
  align-self: stretch;
  min-width: 0;
}

.preview-item__body {
  display: flex;
  flex: 1;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-xs) var(--space-sm);
  min-width: 0;
}

.preview-item__action {
  flex-shrink: 0;
}

.preview-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-sm);
  padding-top: var(--space-md);
  border-top: 1px solid var(--pn-line);
}

.preview-foot__hint {
  font-size: var(--text-xs);
  color: var(--pn-dim);
}

/* No arrow keys to press on a touch screen. */
@media (pointer: coarse) {
  .preview-foot__hint {
    display: none;
  }
}

.preview-foot__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xs);
  margin-left: auto;
}

/* --- shell -------------------------------------------------------------- */

/* Three widths, each its own layout (frontend-architecture.md, "Small
   screens are written as their own layout"; ADR-0125):
     >= 1100px  sidebar + list + detail
     768–1099   the sidebar is a drawer the Shell's `nav_open` controls;
                the list detaches into an overlay `list_open` controls
     <  768     the sidebar is the bar across the top, its destinations
                move to the bottom bar, the list is the screen, and an
                entity is pushed on top of it (`screen`) */

.shell {
  /* The three widths are container queries against the shell itself, not
     media queries against the window: the storybook narrows the specimen
     to 768 or 375 and the console is always the whole viewport, so the
     two agree only if the shell measures its own box. The same trap as
     `.scene`: an inline-size container's width no longer follows its
     content, so it must say `width: 100%` or collapse. `contain: layout`
     is separate and deliberate: a container query no longer implies it,
     and it is what makes the shell the containing block for anything
     fixed, so nothing escapes the storybook's 375px frame.

     The shell is exactly the viewport, never taller: the document does
     not scroll, and the rail, the list and the page each scroll on their
     own. A rail of five never needs it; a list of two hundred chargers
     does, and it used to take the rail and the top of the page with it. */
  container-type: inline-size;
  container-name: shell;
  contain: layout;
  width: 100%;
  display: flex;
  flex-direction: column;
  height: 100dvh;
  color: var(--pn-text);
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  line-height: var(--text-sm-leading);
  /* The ground, and the wash over it (ADR-0212): the brand's green from
     the top left, its cyan from the bottom right, each fading out well
     before the middle so the centre of a page is the plain panel. On the
     shell and nowhere else: the shell is exactly the viewport and never
     scrolls, so the wash is a still sky that content and the floating
     layer slide over, and it costs the layout nothing. The two colours
     carry the alpha; see them in tokens.css for why it is as faint as it is. */
  background-color: var(--pn-bg);
  background-image:
    radial-gradient(55% 45% at 8% 0%, var(--pn-wash-a), transparent 70%),
    radial-gradient(45% 40% at 100% 100%, var(--pn-wash-b), transparent 70%);
}

/* The sidebar beside the content, the content being the page's header
   over the body. Positioned, because between 768 and 1100px the sidebar
   is a drawer laid over this box. */
.shell__frame {
  display: flex;
  flex: 1;
  min-height: 0;
  position: relative;
}

.shell__content {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  min-height: 0;
}

.shell__nav-scrim {
  display: none;
}

/* Height-bound by the shell and clipped, so each column below is its own
   scroller and none can push the others off the screen. It is also what a
   drawer is positioned against -- see `.drawer`. */
.shell__body {
  display: flex;
  flex: 1;
  min-height: 0;
  /* Clip, not hidden: a hidden box can still be scrolled from code, and
     focus is code. A drawer focuses its title as it mounts, while its
     panel still stands a full width off to the right at the start of its
     slide, so the browser scrolled this box sideways to show it and the
     whole page slid back in behind the drawer. A clipped box cannot be
     scrolled at all. */
  overflow: clip;
  /* At every width, not only where the tablet's overlay list needs it: a
     drawer is positioned against this box, and without it one fell back
     to the viewport and slid under the top bar. */
  position: relative;
}

/* `.page` is `flex: 1` with its own scroll, so a page that has one never
   overflows this box and the page title and tabs above it stay put.
   The fallback is for a route that renders a bare `Container` instead. */
.shell__main {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
}

.shell__scrim {
  display: none;
}

/* --- the standing column (ADR-0213) -------------------------------------
   A column at the page's trailing edge (the console's assistant), in the
   flow from 1360px up while `data-aside="standing"`, and folded to the
   rail -- a strip that keeps its place and its name -- when "folded".
   Between 768 and 1360px there is no room beside the page for a column
   and a table, so the rail is always the one in the flow and the column
   lies over the page while `data-aside-over="open"`, behind a scrim; a
   phone draws neither, its sheet is the caller's. The widths are the
   shell's own container queries, so the showcase and the console agree,
   and nothing in Rust ever asks how wide the screen is. The column is a
   flex column so its one scrolling section (`.panel-section[data-scrolls]`)
   takes the room between the head and the composer. */
.shell__aside {
  width: var(--aside-width);
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  min-height: 0;
  min-width: 0;
  background: var(--pn-raise);
  border-left: 1px solid var(--pn-line);
}

.shell__aside > .panel-head {
  border-radius: 0;
}

.shell__rail {
  display: none;
  width: var(--rail-width);
  flex-shrink: 0;
  background: var(--pn-raise);
  border-left: 1px solid var(--pn-line);
}

.shell__aside-scrim {
  display: none;
}

/* The phone's mount of the column's content, in a sheet. */
.shell__aside-sheet {
  display: none;
}

.shell[data-aside="folded"] .shell__aside {
  display: none;
}

.shell[data-aside="folded"] .shell__rail {
  display: block;
}

.aside-rail {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-lg);
  width: 100%;
  padding: var(--space-lg) 0;
  border: 0;
  background: none;
  color: var(--pn-dim);
  font: inherit;
  cursor: pointer;
}

@media (hover: hover) {
  .aside-rail:hover {
    color: var(--pn-text);
  }
}

.aside-rail:focus-visible {
  outline: 2px solid var(--ring);
  outline-offset: -2px;
}

.aside-rail__mark {
  position: relative;
  display: inline-flex;
}

.aside-rail__pip {
  position: absolute;
  top: calc(-1 * var(--space-2xs));
  right: calc(-1 * var(--space-2xs));
  width: var(--space-md);
  height: var(--space-md);
  border-radius: var(--radius-pill);
  background: var(--status-fault);
  box-shadow: 0 0 0 2px var(--pn-raise);
}

/* The column's name, read upwards, as a book's spine is. */
.aside-rail__label {
  writing-mode: vertical-rl;
  transform: rotate(180deg);
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

/* No `overscroll-behavior` here, deliberately. `overflow: auto` makes this
   a scroll container even where it is not height-bound and has nothing to
   scroll -- which is most pages, inside a `Container` -- and `contain` on
   such a box stops the wheel chaining to `.shell__main`, the box that does
   scroll. It made a page impossible to scroll with the pointer over its
   content (layout.spec.ts, "the wheel scrolls the page"). `.shell__main`
   carries the `contain` that keeps the wheel from reaching the document. */
.page {
  flex: 1;
  overflow: auto;
  padding: var(--space-xl) var(--space-2xl) var(--space-3xl);
  display: flex;
  flex-direction: column;
  gap: var(--gap-stack);
}

/* --- page header (ADR-0125) --------------------------------------------- */

/* Which page, and nothing about where in the product: that is the
   sidebar's. It sits on the page's own ground, unbordered, so the trail
   reads as the top line of the page rather than as a second bar. */
.page-header {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  gap: var(--space-lg);
  min-height: var(--header-height);
  padding: 0 var(--space-2xl);
}

.page-header__menu,
.page-header__back {
  display: none;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 2.25rem;
  height: 2.25rem;
  margin-left: calc(-1 * var(--space-sm));
  padding: 0;
  border: 0;
  border-radius: var(--radius-lg);
  background: none;
  color: var(--pn-text);
  cursor: pointer;
}

.page-header__trail {
  display: flex;
  align-items: center;
  flex: 1;
  min-width: 0;
  font-size: var(--text-sm);
}

.page-header__actions {
  display: flex;
  align-items: center;
  gap: var(--space-xl);
  flex-shrink: 0;
  color: var(--pn-dim);
}

/* Above a phone, the actions are the header's own flex items rather than one
   box: the trail beside them absorbs a change of width, so an item that comes
   and goes (the stream's "Connecting", a stale-data chip) moves nothing to its
   right. As one right-aligned box, every such change moved the whole box, and
   with the console's Assistant control and the bell both there that alone
   spent most of a page's layout-shift budget (spec 135). The spacing between
   them is the box's old gap: the header's own gap plus the difference. A
   phone keeps the box: its header is a positioned strip over the top bar,
   sized by the box. */
@container shell (min-width: 48rem) {
  .page-header__actions {
    display: contents;
  }

  .page-header__actions > * {
    flex-shrink: 0;
  }

  .page-header__actions > * + * {
    margin-inline-start: calc(var(--space-xl) - var(--space-lg));
  }
}

/* --- scope switcher (ADR-0125) ------------------------------------------ */

/* One trigger on the sidebar's Night, and one menu on the panel's own
   raised surface. Nothing here is tinted but the sandbox tag: the default
   project says "Default" in words (scope_switcher.rs). */
.switcher {
  position: relative;
}

/* Above the click-outside scrim its menu brings, so pressing the trigger
   again closes the menu the way the trigger says it will. */
.switcher__trigger {
  position: relative;
  z-index: 20;
  display: flex;
  align-items: center;
  gap: var(--space-md);
  width: 100%;
  padding: var(--space-sm) var(--space-md);
  border-radius: var(--radius-xl);
  border: 1px solid var(--night-line);
  background: var(--night-raise);
  color: var(--night-text);
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.switcher__trigger[aria-expanded="true"] {
  border-color: var(--night-dim);
}

.switcher__trigger:focus-visible {
  outline: 2px solid var(--night-accent);
  outline-offset: 2px;
}

.switcher__initials {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 1.875rem;
  height: 1.875rem;
  border-radius: var(--radius-lg);
  background: linear-gradient(135deg, var(--night-accent), var(--night-accent-2));
  color: var(--night-bg);
  font-family: var(--font-heading);
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
}

/* Two lines from the first frame: the slug alone is drawn while the
   organization is read, then its name and the reader's role replace it,
   and a box that grew by a line pushed the whole page down. */
.switcher__names {
  display: flex;
  flex-direction: column;
  justify-content: center;
  flex: 1;
  min-width: 0;
  min-height: 2.375rem;
  line-height: 1.35;
}

.switcher__organization {
  font-size: var(--text-xs);
  color: var(--night-dim);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.switcher__project {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  min-width: 0;
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  white-space: nowrap;
}

/* The name gives way before the sandbox tag does: the tag is the one
   thing on the trigger that must never be cut. */
.switcher__project-name {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}

.switcher__sandbox {
  display: inline-flex;
  flex-shrink: 0;
  align-items: center;
  gap: var(--space-2xs);
  height: 1.125rem;
  padding: 0 var(--space-xs);
  border-radius: var(--radius-md);
  background: var(--sandbox-tape);
  color: var(--sandbox-ink);
  font-size: var(--text-2xs);
  font-weight: var(--weight-semibold);
}

.switcher__chevrons {
  display: inline-flex;
  color: var(--night-dim);
}

.switcher__menu {
  position: absolute;
  top: calc(100% + var(--space-sm));
  left: 0;
  z-index: 30;
  width: 18.75rem;
  max-width: calc(100vw - 2 * var(--space-lg));
  max-height: 70dvh;
  overflow: auto;
  padding: var(--space-sm);
  border-radius: var(--radius-2xl);
  border: 1px solid var(--pn-line);
  background: var(--pn-raise);
  color: var(--pn-text);
  box-shadow: var(--shadow-lg);
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

.switcher__section {
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
}

.switcher__section + .switcher__section {
  border-top: 1px solid var(--pn-line);
  padding-top: var(--space-sm);
}

.switcher__heading {
  padding: var(--space-sm) var(--space-md) var(--space-2xs);
  font-size: var(--text-2xs);
  font-weight: var(--weight-semibold);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--pn-dim);
}

.switcher__link {
  display: flex;
  align-items: center;
  gap: var(--space-lg);
  padding: var(--space-xs) var(--space-md);
  border-radius: var(--radius-xl);
  color: var(--pn-dim);
  font-size: var(--text-sm);
  text-decoration: none;
}

.switcher__link-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  /* A row's mark's width, so the words start where the names do. */
  width: 1.625rem;
  height: 1.625rem;
}

@media (hover: hover) {
  .switcher__link:hover {
    background: var(--pn-raise-2);
    color: var(--pn-text);
  }
}

.switcher__link:focus-visible {
  outline: 2px solid var(--brand-from);
  outline-offset: -2px;
}

.switcher__section .mode-switch {
  margin: var(--space-2xs) var(--space-md) var(--space-md);
}

/* A picker is a section's list, in flow inside the switcher's menu. */
.picker {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.picker__option {
  display: flex;
  align-items: center;
  gap: var(--space-lg);
  padding: var(--space-sm) var(--space-md);
  border-radius: var(--radius-xl);
  color: var(--pn-text);
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  text-decoration: none;
  white-space: nowrap;
}

@media (hover: hover) {
  .picker__option:hover {
    background: var(--pn-raise-2);
  }
}

.picker__option:focus-visible {
  outline: 2px solid var(--brand-from);
  outline-offset: -2px;
}

.picker__option[aria-current="true"] {
  background: var(--pn-raise-2);
}

/* An organization's initials on the same square the trigger draws, so it
   reads as the same thing in the menu as above it; a project's grid on
   the panel's quiet square. */
.picker__mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 1.625rem;
  height: 1.625rem;
  border-radius: var(--radius-lg);
  font-family: var(--font-heading);
  font-size: var(--text-2xs);
  font-weight: var(--weight-semibold);
}

.picker__mark[data-scope="organization"] {
  background: linear-gradient(135deg, var(--night-accent), var(--night-accent-2));
  color: var(--night-bg);
}

.picker__mark[data-scope="environment"],
.picker__mark[data-scope="location"],
.picker__mark[data-scope="charger"] {
  border: 1px solid var(--pn-line);
  background: var(--pn-raise-2);
  color: var(--pn-dim);
}

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

.picker__label {
  overflow: hidden;
  text-overflow: ellipsis;
}

.picker__detail {
  overflow: hidden;
  text-overflow: ellipsis;
  font-size: var(--text-xs);
  font-weight: var(--weight-regular);
  color: var(--pn-dim);
}

.picker__check {
  display: inline-flex;
  margin-left: auto;
  color: var(--primary);
}

.picker__empty {
  padding: var(--space-sm) var(--space-md);
  font-size: var(--text-xs);
  color: var(--pn-dim);
}

/* --- mode switch, toggle and band (spec 105, ADR-0125) ------------------- */
/* Sandbox is the marked mode; live is the unmarked normal. See mode.rs. */

/* Two equal halves in one well, the current one filled: a segmented
   control, the way every console draws a choice of two. */
.mode-switch {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-xs);
  padding: var(--space-xs);
  border-radius: var(--radius-xl);
  border: 1px solid var(--pn-line);
  background: var(--pn-raise-2);
}

.mode-switch__option {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-sm);
  height: 1.875rem;
  padding: 0 var(--space-md);
  border-radius: var(--radius-lg);
  color: var(--pn-dim);
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  text-decoration: none;
  white-space: nowrap;
}

@media (hover: hover) {
  .mode-switch__option:hover {
    color: var(--pn-text);
  }
}

/* A finger, not a cursor: 26px tall is legible but not tappable, and
   `touch.spec.ts` measured this option at 58x26 and failed it on every
   phone route. `--touch-target` is the same floor `.button`, `.field__input` and
   `.coverage-row__action` already take here -- switching mode is at least
   as consequential as any of them, so it gets the same target and not a
   smaller one for being chrome. */
@media (pointer: coarse) {
  .mode-switch__option {
    min-height: var(--touch-target);
  }
}

.mode-switch__option:focus-visible {
  outline: 2px solid var(--brand-from);
  outline-offset: -2px;
}

/* The selected option is filled; the other stays plain in the well --
   which is filled in is the one thing a glance needs (FR-018). Live, the
   unmarked normal (ADR-0125), fills with the panel's own raised surface;
   sandbox fills with the band's hazard tape. */
.mode-switch__option[aria-current="true"][data-mode="live"] {
  background: var(--pn-raise);
  box-shadow: var(--shadow-sm);
  color: var(--pn-text);
}

.mode-switch__option[aria-current="true"][data-mode="sandbox"] {
  /* Distinguishable with colour removed (FR-020): the stripes are a
     pattern a monochrome screen or a colour-vision difference still
     reads, and the flask and the word are two more channels. */
  background: repeating-linear-gradient(
    135deg,
    var(--sandbox-tape) 0 0.5rem,
    var(--sandbox-tape-2) 0.5rem 1rem
  );
  color: var(--sandbox-ink);
  font-weight: var(--weight-semibold);
}

.mode-switch__label {
  overflow: hidden;
  text-overflow: ellipsis;
}

/* The page header's one switch. Off, the track is the panel's dim ink so
   the control reads at 3:1 against the ground; on, it is sandbox amber. */
.mode-toggle {
  display: inline-flex;
  align-items: center;
  gap: var(--space-md);
  color: var(--pn-dim);
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  text-decoration: none;
  white-space: nowrap;
}

.mode-toggle:focus-visible {
  outline: 2px solid var(--brand-from);
  outline-offset: 2px;
  border-radius: var(--radius-md);
}

.mode-toggle__label {
  line-height: 1;
}

.mode-toggle__track {
  position: relative;
  display: inline-block;
  width: 1.875rem;
  height: 1.125rem;
  border-radius: var(--radius-pill);
  background: var(--pn-dim);
}

.mode-toggle__knob {
  position: absolute;
  top: 0.125rem;
  left: 0.125rem;
  width: 0.875rem;
  height: 0.875rem;
  border-radius: var(--radius-pill);
  background: var(--pn-raise);
  box-shadow: var(--shadow-sm);
  transition: transform var(--motion-fast) var(--ease-out);
}

.mode-toggle[aria-checked="true"] {
  color: var(--mode-sandbox-ink);
}

.mode-toggle[aria-checked="true"] .mode-toggle__track {
  background: var(--mode-sandbox);
}

.mode-toggle[aria-checked="true"] .mode-toggle__knob {
  transform: translateX(0.75rem);
}

@media (pointer: coarse) {
  .mode-toggle {
    min-height: var(--touch-target);
  }
}

/* Hazard tape across the top of every page while in sandbox: the stripes
   are one channel, the flask and the words the other two (FR-020). */
.sandbox-band {
  flex-shrink: 0;
  box-sizing: border-box;
  min-height: var(--band-height);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: calc(var(--space-xs) + env(safe-area-inset-top, 0px)) var(--space-lg) var(--space-xs);
  background: repeating-linear-gradient(
    135deg,
    var(--sandbox-tape) 0 0.875rem,
    var(--sandbox-tape-2) 0.875rem 1.75rem
  );
  color: var(--sandbox-ink);
  font-size: var(--text-sm);
}

.sandbox-band__label {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: var(--space-md);
  padding: var(--space-2xs) var(--space-lg);
  border-radius: var(--radius-pill);
  background: var(--sandbox-tape);
  text-align: center;
}

.sandbox-band__exit {
  color: var(--sandbox-ink);
  font-weight: var(--weight-semibold);
  text-decoration: underline;
}

/* --- trail -------------------------------------------------------------- */

.trail {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  min-width: 0;
}

.trail__step {
  display: contents;
}

.trail__sep {
  color: var(--pn-dim);
  opacity: 0.6;
  display: inline-flex;
  flex-shrink: 0;
}

.trail__crumb {
  color: var(--pn-dim);
  text-decoration: none;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}

@media (hover: hover) {
  .trail__crumb:hover {
    color: var(--pn-text);
  }
}

/* Plain text: a pill here read as a button (ADR-0125). */
/* A phone's name for the section on screen (see `trail.rs`): wider, the
   tab row says it. */
.trail__section {
  display: none;
}

.trail__current {
  display: inline-flex;
  align-items: center;
  gap: var(--space-sm);
  min-width: 0;
  color: var(--pn-text);
  font-weight: var(--weight-medium);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* --- chooser (ADR-0125) ------------------------------------------------- */

/* The sign-in sky, pinned to Night, with the choice in a centred column.
   Every colour is a --night-* token: this page is chrome, not panel. */
/* `width: 100%` is not decoration: `.scene` inside is an inline-size
   container, and a wrapper that sizes to its content collapses it to
   nothing (libs/ui/AGENTS.md, "`.scene` is a container"). */
.chooser {
  width: 100%;
  min-height: 100dvh;
  /* Outside the shell, which is where the console's face is set: without
     this the intro and any plain link fell back to the browser's serif. */
  font-family: var(--font-sans);
}

.chooser__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
}

.chooser__account {
  display: flex;
  align-items: center;
  gap: var(--space-lg);
}

.chooser__email {
  font-size: var(--text-sm);
  color: var(--night-dim);
}

/* A finger's worth on a touch screen: the logo is the way home and the
   avatar opens the account menu, and both sit in the corner a thumb
   reaches for. */
@media (pointer: coarse) {
  .chooser__header .logo,
  .chooser__account .avatar-row {
    min-height: var(--touch-target);
    min-width: var(--touch-target);
    justify-content: center;
  }
}

/* No room for the address beside the avatar on a phone: the menu it opens
   says it. */
@media (max-width: 40rem) {
  .chooser__email {
    display: none;
  }
}

.chooser__column {
  display: flex;
  flex-direction: column;
  gap: var(--space-2xl);
  width: 100%;
  max-width: 35rem;
  text-align: left;
}

.chooser__intro {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
}

.chooser__eyebrow {
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--night-accent);
}

.chooser__title {
  margin: 0;
  font-family: var(--font-heading);
  font-size: var(--text-5xl);
  font-weight: var(--weight-semibold);
  letter-spacing: -0.03em;
  line-height: 1.05;
  color: var(--night-text);
}

.chooser__lede {
  margin: 0;
  font-size: var(--text-base);
  color: var(--night-dim);
}

.chooser__options {
  display: flex;
  flex-direction: column;
  gap: var(--space-lg);
  width: 100%;
}

.chooser__option {
  display: flex;
  align-items: center;
  gap: var(--space-xl);
  min-height: var(--touch-target);
  padding: var(--space-xl);
  border-radius: var(--radius-2xl);
  border: 1px solid var(--night-line);
  background: var(--night-raise);
  color: var(--night-text);
  text-decoration: none;
}

@media (hover: hover) {
  .chooser__option:hover {
    border-color: var(--night-dim);
  }

  /* One night grey to the next is a quiet change on its own; the chevron
     leaning in says the row is the way on. */
  .chooser__option:hover .chooser__chevron {
    color: var(--night-text);
    transform: translateX(2px);
  }
}

.chooser__option:focus-visible {
  outline: 2px solid var(--night-accent);
  outline-offset: 2px;
}

.chooser__initials {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 2.75rem;
  height: 2.75rem;
  border-radius: var(--radius-xl);
  background: linear-gradient(135deg, var(--night-accent), var(--night-accent-2));
  color: var(--night-bg);
  font-family: var(--font-heading);
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
}

.chooser__names {
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
  flex: 1;
  min-width: 0;
}

.chooser__name {
  font-family: var(--font-heading);
  font-size: var(--text-lg);
  font-weight: var(--weight-semibold);
}

.chooser__detail {
  font-size: var(--text-sm);
  color: var(--night-dim);
}

.chooser__meta {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: var(--space-2xs);
  font-size: var(--text-xs);
  color: var(--night-dim);
  text-align: right;
}

.chooser__last {
  display: inline-flex;
  align-items: center;
  gap: var(--space-sm);
  height: 1.25rem;
  padding: 0 var(--space-md);
  border-radius: var(--radius-pill);
  background: color-mix(in oklab, var(--night-accent) 14%, transparent);
  color: var(--night-accent);
  font-weight: var(--weight-semibold);
}

.chooser__last::before {
  content: "";
  width: 0.3125rem;
  height: 0.3125rem;
  border-radius: var(--radius-pill);
  background: var(--night-accent);
  box-shadow: 0 0 6px var(--night-accent);
}

.chooser__chevron {
  display: inline-flex;
  color: var(--night-dim);
}

/* On a phone the figures drop under the name rather than squeezing it:
   at 390px they left "Depot / North AB" on two lines. */
@media (max-width: 40rem) {
  .chooser__option {
    flex-wrap: wrap;
    row-gap: var(--space-sm);
  }

  .chooser__meta {
    order: 1;
    flex-basis: 100%;
    flex-direction: row;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-md);
    padding-left: calc(2.75rem + var(--space-xl));
    text-align: left;
  }
}

/* Another organization, as the list's last row: dashed, because it is a
   place that does not exist yet. */
.chooser__create {
  display: flex;
  align-items: center;
  gap: var(--space-xl);
  min-height: var(--touch-target);
  padding: var(--space-lg) var(--space-xl);
  border-radius: var(--radius-2xl);
  border: 1px dashed var(--night-line);
  color: var(--night-dim);
  font-size: var(--text-sm);
  text-decoration: none;
}

@media (hover: hover) {
  .chooser__create:hover {
    border-color: var(--night-dim);
    color: var(--night-text);
  }
}

.chooser__create:focus-visible {
  outline: 2px solid var(--night-accent);
  outline-offset: 2px;
}

.chooser__create-mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 2.75rem;
  height: 2.75rem;
  border-radius: var(--radius-xl);
  border: 1px dashed var(--night-line);
  color: var(--night-accent);
}

/* --- sidebar (ADR-0125, ADR-0126) --------------------------------------- */

/* Night in both themes: the brand's sky, the constellation faint behind
   the destinations, and the current one wearing the ion. Every colour is
   a --night-* token, never a panel one, so a theme flip leaves the chrome
   where it is. The brand tokens are rebound here so the mark and the
   wordmark draw their Night build on it whatever the page's theme. */
.sidebar {
  --brand-from: var(--night-accent);
  --brand-to: var(--night-accent-2);
  --brand-star: var(--night-star);
  --brand-ion: var(--night-star);
  position: relative;
  isolation: isolate;
  width: var(--sidebar-width);
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-xl);
  padding: calc(var(--space-xl) + env(safe-area-inset-top, 0px)) var(--space-lg) var(--space-lg)
    calc(var(--space-lg) + env(safe-area-inset-left, 0px));
  background: var(--night-bg);
  color: var(--night-text);
  /* Above the page, and not clipped: the switcher's and the account's
     menus open out of it over the page, and their scrims have to reach
     the page to catch a click outside. The destinations scroll instead. */
  z-index: 20;
}

.sidebar__sky {
  position: absolute;
  inset: 0;
  z-index: -1;
  overflow: hidden;
  pointer-events: none;
}

.sidebar__constellation {
  position: absolute;
  right: -2.75rem;
  bottom: 4rem;
  width: 14rem;
  height: 14rem;
  fill: none;
  stroke: var(--night-accent);
  stroke-width: 0.6;
  stroke-linejoin: round;
  opacity: 0.16;
}

.sidebar__constellation circle {
  fill: var(--night-accent);
  stroke: none;
}

.sidebar__head {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
}

.sidebar__brand {
  display: inline-flex;
  align-items: center;
  gap: var(--space-md);
  padding: var(--space-2xs) var(--space-md) var(--space-md);
  text-decoration: none;
}

/* The switcher's slot: it takes the sidebar's width, and on a phone it
   is what gives way beside the mark and the account. */
.sidebar__scope {
  min-width: 0;
}

.sidebar__brand .wordmark {
  color: var(--night-text);
}

.sidebar__search .jump {
  width: 100%;
  border-color: var(--night-line);
  background: transparent;
  color: var(--night-dim);
}

.sidebar__search .jump__key {
  border-color: var(--night-line);
}

.sidebar__nav {
  display: flex;
  flex-direction: column;
  flex: 1;
  gap: var(--space-xl);
  min-height: 0;
  overflow-y: auto;
}

.sidebar__group {
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
}

.sidebar__group[data-group="foot"] {
  margin-top: auto;
}

.sidebar__item {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--space-lg);
  width: 100%;
  height: 2.25rem;
  padding: 0 var(--space-md);
  border: 0;
  border-radius: var(--radius-xl);
  background: none;
  color: var(--night-dim);
  font: inherit;
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  text-align: left;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
}

@media (hover: hover) {
  .sidebar__item:hover {
    background: var(--night-raise);
    color: var(--night-text);
  }
}

.sidebar__item:focus-visible {
  outline: 2px solid var(--night-accent);
  outline-offset: -2px;
}

/* The destination the reader is at: a glass pill on the sky (ADR-0212),
   lit along its top, with the ion beside its label. */
.sidebar__item[aria-current="page"] {
  background: color-mix(in oklab, var(--night-text) 9%, transparent);
  color: var(--night-text);
  box-shadow:
    inset 0 0 0 1px color-mix(in oklab, var(--night-text) 10%, transparent),
    inset 0 1px 0 color-mix(in oklab, var(--night-text) 16%, transparent);
  backdrop-filter: blur(var(--glass-blur));
}

.sidebar__icon {
  display: inline-flex;
  color: var(--night-dim);
}

.sidebar__item[aria-current="page"] .sidebar__icon {
  color: var(--night-accent);
}

/* An organization's projects, between its two destinations: a heading
   that is not a heading (the rows are what a reader tabs through), and a
   quiet note beside the default one. */
.sidebar__heading {
  padding: var(--space-md) var(--space-md) var(--space-xs);
  font-size: var(--text-2xs);
  font-weight: var(--weight-semibold);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--night-dim);
}

.sidebar__note {
  font-size: var(--text-xs);
  color: var(--night-dim);
}

/* Help sits just under Settings, outside the Destinations landmark: the
   negative margin takes back the gap between the nav and what follows. */
.sidebar__item[data-help="true"] {
  margin-top: calc(-1 * var(--space-lg));
}

.sidebar__label {
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
}

.sidebar__badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 1.25rem;
  height: 1.25rem;
  padding: 0 var(--space-sm);
  border-radius: var(--radius-pill);
  background: var(--night-accent);
  color: var(--night-bg);
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
}

/* The ion: the brand's moving light, on the one destination that is here. */
.sidebar__ion {
  width: 0.375rem;
  height: 0.375rem;
  flex-shrink: 0;
  border-radius: var(--radius-pill);
  background: linear-gradient(135deg, var(--night-accent), var(--night-accent-2));
  box-shadow:
    0 0 0 3px color-mix(in oklab, var(--night-accent) 16%, transparent),
    0 0 10px var(--night-accent);
}

.sidebar__item[data-new="true"] .sidebar__icon {
  color: var(--night-accent);
}

.sidebar__account {
  display: flex;
  align-items: center;
  padding: var(--space-lg) var(--space-md) 0;
  border-top: 1px solid var(--night-line);
}

.sidebar__account .menu-host {
  flex: 1;
  min-width: 0;
}

.sidebar__account .avatar-row {
  width: 100%;
  padding: var(--space-xs);
  border-radius: var(--radius-xl);
  color: var(--night-dim);
  font-size: var(--text-xs);
}

.sidebar__account .avatar-row[aria-expanded="true"] .avatar {
  box-shadow:
    0 0 0 2px var(--night-bg),
    0 0 0 4px var(--night-accent);
}

@media (hover: hover) {
  .sidebar__account .avatar-row:hover {
    background: var(--night-raise);
    color: var(--night-text);
  }
}

.sidebar__account .avatar-row__caption {
  display: block;
}

.sidebar .avatar {
  box-shadow:
    0 0 0 2px var(--night-bg),
    0 0 0 3px var(--night-line);
}

/* The account's menu opens upward from the foot of the sidebar. */
.sidebar__account .menu {
  top: auto;
  right: auto;
  bottom: calc(100% + var(--space-sm));
  left: 0;
}

/* --- bottom bar --------------------------------------------------------- */

.bottombar {
  display: none;
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 20;
  /* The home indicator's inset is added to the height as well as the
     padding, or `border-box` takes it out of the items instead. */
  height: calc(var(--bottombar-height) + env(safe-area-inset-bottom, 0px));
  padding: var(--space-sm) calc(var(--space-lg) + env(safe-area-inset-right, 0px))
    calc(var(--space-lg) + env(safe-area-inset-bottom, 0px))
    calc(var(--space-lg) + env(safe-area-inset-left, 0px));
  grid-template-columns: repeat(5, minmax(0, 1fr));
}

/* The bar is a glass pill floating above the page (ADR-0212), the page
   showing around and through it; the box above is only the place it
   holds and the room the home indicator needs. */
.bottombar::before {
  content: "";
  position: absolute;
  inset: var(--space-sm) calc(var(--space-lg) + env(safe-area-inset-right, 0px))
    calc(var(--space-lg) + env(safe-area-inset-bottom, 0px))
    calc(var(--space-lg) + env(safe-area-inset-left, 0px));
  border: 1px solid var(--glass-line);
  border-radius: var(--radius-pill);
  background: var(--glass);
  backdrop-filter: blur(var(--glass-blur));
  box-shadow:
    inset 0 1px 0 var(--glass-highlight),
    var(--shadow-lg);
}

.bottombar__item {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-2xs);
  min-height: 3rem;
  border-radius: var(--radius-pill);
  color: var(--pn-dim);
  font-size: 0.656rem;
  font-weight: var(--weight-medium);
  text-decoration: none;
  position: relative;
}

button.bottombar__item {
  appearance: none;
  border: 0;
  background: none;
  font-family: inherit;
  cursor: pointer;
}

/* The current tab, or More when the current destination is behind it. */
.bottombar__item:is([aria-current="page"], [data-current="true"]) {
  color: var(--pn-text);
  font-weight: var(--weight-semibold);
}

.bottombar__item:is([aria-current="page"], [data-current="true"]) > svg {
  color: var(--brand-from);
}

/* The ion under the current destination, as beside it in the sidebar. */
.bottombar__item:is([aria-current="page"], [data-current="true"])::after {
  content: "";
  position: absolute;
  bottom: var(--space-sm);
  left: 50%;
  width: 0.3125rem;
  height: 0.3125rem;
  margin-left: -0.15625rem;
  border-radius: var(--radius-pill);
  background: linear-gradient(135deg, var(--brand-from), var(--brand-to));
  box-shadow: 0 0 6px var(--brand-from);
}

/* An organization's bar is its two destinations and no More. */
.bottombar[data-count="2"] {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

.bottombar__more {
  position: relative;
  display: flex;
}

.bottombar__more > .bottombar__item {
  flex: 1;
}

/* More's menu opens upward, over the page, flush with the bar's end. */
.bottombar__more .menu {
  top: auto;
  bottom: calc(100% + var(--space-sm));
  right: var(--space-xs);
}

.bottombar__label {
  white-space: nowrap;
}

.bottombar__badge {
  position: absolute;
  top: var(--space-xs);
  right: 1.375rem;
  width: 0.4375rem;
  height: 0.4375rem;
  border-radius: var(--radius-pill);
  background: var(--status-warn);
  box-shadow: 0 0 0 2px var(--pn-raise);
}

/* --- tab frame ----------------------------------------------------------- */

/* A phone app's whole screen: what the tab shows, which scrolls, and the
   bar, which does not. The app's own layout, so it is always shown -- the
   console's `.bottombar` is the one that hides above 768px. */
.tabframe {
  display: flex;
  flex-direction: column;
  height: 100dvh;
  background: var(--pn-bg);
  color: var(--pn-text);
  /* Named here rather than inherited: the frame is the whole document in
     an app, and a document with no face set draws the bar's labels in the
     webview's default serif. */
  font-family: var(--font-sans);
}

/* The screen lays out what it is given, a stack with the page's gutter, so
   an app's screen is its content and nothing else: the frame is the only
   thing that knows where the notch and the home indicator are. */
.tabframe__screen {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  display: flex;
  flex-direction: column;
  gap: var(--gap-stack);
  padding: calc(var(--space-2xl) + env(safe-area-inset-top, 0px))
    calc(var(--space-xl) + env(safe-area-inset-right, 0px)) var(--space-2xl)
    calc(var(--space-xl) + env(safe-area-inset-left, 0px));
}

.tabframe__screen:focus-visible {
  outline: none;
}

/* As many equal columns as there are tabs. The home indicator's inset is
   padding, so the buttons keep their height. */
.tabbar {
  flex-shrink: 0;
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr);
  align-items: start;
  padding: var(--space-sm) calc(var(--space-xs) + env(safe-area-inset-right, 0px))
    calc(var(--space-sm) + env(safe-area-inset-bottom, 0px))
    calc(var(--space-xs) + env(safe-area-inset-left, 0px));
  background: var(--night-bg);
}

.tabbar__item {
  appearance: none;
  border: 0;
  background: none;
  font-family: inherit;
  cursor: pointer;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-xs);
  min-height: var(--touch-target);
  padding: var(--space-xs) 0;
  position: relative;
  border-radius: var(--radius-lg);
  font-size: var(--text-2xs);
  font-weight: var(--weight-medium);
  color: var(--night-dim);
}

.tabbar__item[aria-current="page"] {
  color: var(--night-text);
  font-weight: var(--weight-semibold);
}

.tabbar__item[aria-current="page"] > svg {
  color: var(--night-accent);
}

.tabbar__item:focus-visible {
  outline: 2px solid var(--night-accent);
  outline-offset: -2px;
}

.tabbar__label {
  white-space: nowrap;
}

.tabbar__badge {
  position: absolute;
  top: var(--space-xs);
  left: calc(50% + var(--space-md));
  width: 0.4375rem;
  height: 0.4375rem;
  border-radius: var(--radius-pill);
  background: var(--status-warn);
  box-shadow: 0 0 0 2px var(--night-bg);
}

/* --- jump to ------------------------------------------------------------ */

.jump {
  display: inline-flex;
  align-items: center;
  gap: var(--space-md);
  height: 2rem;
  padding: 0 var(--space-lg);
  width: 13.75rem;
  border-radius: var(--radius-xl);
  border: 1px solid var(--pn-line);
  background: var(--pn-raise);
  color: var(--pn-dim);
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  cursor: pointer;
}

.jump__label {
  flex: 1;
  text-align: left;
}

.jump__key {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  padding: 1px var(--space-xs);
  border-radius: var(--radius-md);
  border: 1px solid var(--pn-line);
}

/* In its place before the palette exists (ADR-0125): inert, and its
   title says why. */
.jump:disabled {
  cursor: not-allowed;
  opacity: 0.6;
}

/* --- notification bell --------------------------------------------------- */

/* An icon button, so its name is an aria-label that already carries the
   unread count (libs/ui/src/notification_bell.rs); the drawn number is for
   the eye. The box is the 44px floor itself rather than a smaller drawing
   with an invisible hit area, because the count is drawn on its corner. */
.bell {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: var(--touch-target);
  height: var(--touch-target);
  padding: 0;
  border: 0;
  border-radius: var(--radius-xl);
  background: none;
  color: var(--pn-dim);
  cursor: pointer;
  transition:
    background-color var(--motion-fast) var(--ease-out),
    color var(--motion-fast) var(--ease-out);
}

@media (hover: hover) {
  .bell:hover {
    color: var(--pn-text);
    background: var(--pn-raise-2);
  }
}

.bell:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px color-mix(in oklch, var(--ring), transparent 70%);
}

.bell__count {
  position: absolute;
  top: var(--space-xs);
  right: var(--space-xs);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 1.125rem;
  height: 1.125rem;
  padding: 0 var(--space-xs);
  border-radius: var(--radius-pill);
  background: var(--primary);
  color: var(--primary-foreground);
  font-family: var(--font-sans);
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  line-height: 1;
}

/* --- notification list --------------------------------------------------- */

/* The items of the inbox, in a Drawer's body. A placeholder while loading
   is the same box, so nothing moves when the real ones arrive. */
.notifications {
  display: flex;
  flex-direction: column;
  margin: 0;
  padding: 0;
  list-style: none;
}

.notification {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  padding: var(--space-lg) var(--space-lg);
  border-left: 3px solid transparent;
  border-bottom: 1px solid var(--pn-line);
}

.notification:last-child {
  border-bottom: 0;
}

/* Unread is a data attribute, never a class built in Rust. The bar is the
   marker for the eye; `.notification__unread` is the same fact in words. */
.notification[data-unread="true"] {
  border-left-color: var(--primary);
}

.notification__head {
  display: flex;
  align-items: baseline;
  gap: var(--space-md);
}

.notification__unread {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

.notification__meta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-md);
}

.notification__action {
  align-self: flex-start;
}

/* --- entity tabs -------------------------------------------------------- */

/* The tab strip is a segmented control on glass (ADR-0212): a pill that
   floats over the page with the current tab a porcelain pill inside it.
   It was an underlined row ruled across the page; with the rule gone the
   strip is as wide as its tabs and sits at the page's start edge, so the
   old `.page > .tabs` edge corrections went with it. */
.tabs {
  display: inline-flex;
  align-self: flex-start;
  max-width: 100%;
  gap: var(--space-2xs);
  padding: var(--space-2xs);
  flex-shrink: 0;
  overflow-x: auto;
  scrollbar-width: none;
  border: 1px solid var(--glass-line);
  border-radius: var(--radius-pill);
  background: var(--glass);
  backdrop-filter: blur(var(--glass-blur));
  box-shadow:
    inset 0 1px 0 var(--glass-highlight),
    var(--shadow-sm);
}

.tabs__tab {
  display: inline-flex;
  align-items: center;
  gap: var(--space-md);
  height: 2.125rem;
  padding: 0 var(--space-lg);
  border-radius: var(--radius-pill);
  white-space: nowrap;
  font-weight: var(--weight-medium);
  color: var(--pn-dim);
  text-decoration: none;
}

@media (hover: hover) {
  .tabs__tab:hover {
    color: var(--pn-text);
  }
}

.tabs__tab[aria-current="page"] {
  color: var(--pn-text);
  font-weight: var(--weight-semibold);
  background: var(--pn-raise);
  box-shadow: var(--shadow-sm);
}

/* Inside a page the strip stays put while the page scrolls under it, so
   the glass has something to blur and the sections stay a tap away. A
   step below the page's top edge, so it floats rather than sits. */
.page > .tabs {
  position: sticky;
  top: var(--space-md);
  z-index: 2;
}

.tabs__count {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--pn-dim);
}

/* --- page state --------------------------------------------------------- */

.page-state {
  display: flex;
  flex-direction: column;
  gap: var(--gap-stack);
}

/* --- settings: page title, layout, card, person row, checkbox -- */

/* The thing being edited is the title; its facts sit on the same line and
   wrap under it when the line is short. */
.page-title {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--space-lg) var(--space-2xl);
  flex-wrap: wrap;
}

.page-title__main {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  min-width: 0;
}

/* A picture before the name: the two are one group, held closer than the
   actions are held off it. */
.page-title__lead {
  display: flex;
  align-items: center;
  gap: var(--space-lg);
  min-width: 0;
}

.page-title__figure {
  display: flex;
}

.page-title__eyebrow {
  display: flex;
}

.page-title__line {
  display: flex;
  align-items: center;
  gap: var(--space-lg);
  flex-wrap: wrap;
}

.page-title__summary {
  margin: 0;
  font-size: var(--text-sm);
  color: var(--pn-dim);
}

.page-title__actions {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  flex-shrink: 0;
  /* It never shrinks beside the title, but on a narrow page it drops to
     its own line and must still fit it: a note beside a button (a charger
     that is offline says why Restart would queue) wraps rather than
     pushing the page sideways. */
  flex-wrap: wrap;
  max-width: 100%;
}

.page-title__name {
  margin: 0;
  font-family: var(--font-heading);
  font-size: var(--text-2xl);
  font-weight: var(--weight-semibold);
  letter-spacing: -0.015em;
  line-height: 1.15;
  color: var(--pn-text);
}

.page-title__name[data-mono="true"] {
  font-family: var(--font-mono);
  font-weight: var(--weight-medium);
  letter-spacing: -0.02em;
}

.page-title__facts {
  display: inline-flex;
  align-items: center;
  gap: var(--space-sm);
  flex-wrap: wrap;
}

/* The facts first, as one strip across the top, then the cards: with
   the settings column beside the page there is no room for a rail too. */
.settings-layout {
  display: flex;
  flex-direction: column;
  gap: var(--gap-block);
}

.settings-layout__cards {
  display: flex;
  flex-direction: column;
  gap: var(--gap-block);
  min-width: 0;
}

.settings-layout__facts {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xl) var(--space-2xl);
  padding: var(--space-xl) var(--space-2xl);
  border: 1px solid var(--pn-line);
  border-radius: var(--radius-2xl);
  background: var(--pn-raise);
}

.settings-layout__facts > .fact {
  flex: 1 1 12rem;
}

/* A long value takes about twice the room of the facts beside it, and
   wraps at its own slashes rather than pushing them to another row. */
.settings-layout__facts > .fact[data-wide="true"] {
  flex: 2.2 1 18rem;
}

/* In the strip a fact reads as a caption over a value: the lock and the
   small capitals are for a fact standing alone among fields. */
.settings-layout__facts .fact__label {
  font-size: var(--text-xs);
  font-weight: var(--weight-regular);
  letter-spacing: 0;
  text-transform: none;
}

.settings-layout__facts .fact__lock {
  display: none;
}

.settings-layout__facts .fact__reason {
  margin: 0;
  font-size: var(--text-xs);
  line-height: var(--text-xs-leading);
}

/* An identifier in the strip is a step smaller, and on a wide screen it
   keeps to one line: its fact grows to fit it, and the facts beside it
   wrap as whole facts instead. A phone is too narrow to promise that. */
.settings-layout__facts .fact__value[data-code="true"] {
  font-size: var(--text-sm);
}

@media (min-width: 48.0625rem) {
  .settings-layout__facts .fact:not([data-wide="true"]) .fact__value[data-code="true"] {
    white-space: nowrap;
    overflow-wrap: normal;
  }
}

/* The strip's facts share its box rather than each carrying one. */
.settings-layout__facts > .fact {
  background: transparent;
  border: 0;
  border-radius: 0;
  padding: 0;
  max-width: none;
}

/* Settings' own column beside the page (ADR-0127): the scope's pages,
   grouped under the scope's name. On a phone it folds into one row of
   links above the page, scrolling sideways. */
.settings-frame {
  flex: 1;
  display: grid;
  grid-template-columns: 14.5rem minmax(0, 1fr);
  gap: var(--space-2xl);
  align-items: stretch;
}

/* The page a settings frame is in grows to the visible height, so on a
   page shorter than the screen the column's line still reaches the
   bottom. Scoped by :has so no other page's container changes. */
.container:has(> .settings-frame) {
  flex: 1 0 auto;
  display: flex;
  flex-direction: column;
}

.settings-frame__page {
  min-width: 0;
}

/* A page's own overflow makes it the column's scroll box, one that never
   scrolls, and the column's sticky inside then never sticks. A settings
   frame's page lets the shell's main scroll it instead. */
.page:has(> .settings-frame) {
  overflow: visible;
}

.settings-nav {
  padding-right: var(--space-lg);
  border-right: 1px solid var(--pn-line);
}

.settings-nav__inner {
  position: sticky;
  top: var(--space-lg);
  display: flex;
  flex-direction: column;
  gap: var(--space-xl);
}

/* With a legend, as tall as the visible page below the chrome, so the
   legend sits at the foot of the screen while the links stay at the top
   -- and never shorter than what it holds, on a screen too low for both.
   Without one it is as tall as its links: a screen-tall box under a
   page's own heading is taller than its column, and at the foot of the
   page it would be pushed up past the top. */
.settings-nav__inner:has(> .settings-nav__legend) {
  height: calc(
    100dvh - var(--header-height) - var(--chrome-band) - var(--space-lg) * 2
  );
  min-height: min-content;
}

/* In sandbox the band takes its height off the top of the screen. */
.shell:has(.sandbox-band) {
  --chrome-band: var(--band-height);
}

.settings-nav__groups {
  display: flex;
  flex-direction: column;
  gap: var(--space-xl);
}

.settings-nav__group + .settings-nav__group {
  padding-top: var(--space-xl);
  border-top: 1px solid var(--pn-line);
}

.settings-nav__head {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  padding: 0 var(--space-md) var(--space-md);
}

.settings-nav__mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 1.5rem;
  height: 1.5rem;
  border-radius: var(--radius-lg);
  font-family: var(--font-heading);
  font-size: var(--text-2xs);
  font-weight: var(--weight-semibold);
}

.settings-nav__mark[data-scope="organization"] {
  background: linear-gradient(135deg, var(--night-accent), var(--night-accent-2));
  color: var(--night-bg);
}

.settings-nav__mark[data-scope="environment"],
.settings-nav__mark[data-scope="location"],
.settings-nav__mark[data-scope="charger"] {
  border: 1px solid var(--pn-line);
  background: var(--pn-raise);
  color: var(--pn-dim);
}

.settings-nav__names {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.settings-nav__name {
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  color: var(--pn-text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.settings-nav__caption {
  font-size: var(--text-xs);
  color: var(--pn-dim);
}

.settings-nav__links {
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
}

.settings-nav__link {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  height: 2.25rem;
  padding: 0 var(--space-md);
  border-radius: var(--radius-lg);
  color: var(--pn-dim);
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  text-decoration: none;
  white-space: nowrap;
}

@media (hover: hover) {
  .settings-nav__link:hover {
    background: var(--pn-raise-2);
    color: var(--pn-text);
  }
}

.settings-nav__link:focus-visible {
  outline: 2px solid var(--ring);
  outline-offset: -2px;
}

.settings-nav__link[aria-current="page"] {
  background: var(--pn-raise);
  color: var(--pn-text);
  font-weight: var(--weight-semibold);
  box-shadow:
    inset 0 1px 0 var(--pn-edge),
    var(--shadow-sm);
}

.settings-nav__icon {
  display: inline-flex;
  justify-content: center;
  flex-shrink: 0;
  /* As wide as the group's mark, so each link's icon stands centred
     under it and the labels start where the group's name does. */
  width: 1.5rem;
  color: var(--pn-dim);
}

.settings-nav__link[aria-current="page"] .settings-nav__icon {
  color: var(--primary);
}

.settings-nav__label {
  flex: 1;
}

/* A page of another scope: lighter than this scope's own, and an arrow
   after the words saying the link leaves. */
.settings-nav__away {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  height: 2rem;
  padding: 0 var(--space-md);
  border-radius: var(--radius-lg);
  color: var(--pn-dim);
  font-size: var(--text-sm);
  text-decoration: none;
  white-space: nowrap;
}

@media (hover: hover) {
  .settings-nav__away:hover {
    background: var(--pn-raise-2);
    color: var(--pn-text);
  }
}

.settings-nav__away:focus-visible {
  outline: 2px solid var(--ring);
  outline-offset: -2px;
}

.settings-nav__arrow {
  display: inline-flex;
  color: var(--muted-foreground);
}

.settings-nav__note {
  margin: 0;
  padding: var(--space-sm) var(--space-md) 0;
  font-size: var(--text-xs);
  line-height: var(--text-xs-leading);
  color: var(--pn-dim);
}

.settings-nav__per-mode {
  display: inline-flex;
  color: var(--pn-dim);
}

.settings-nav__per-mode-mark {
  width: 0.75rem;
  height: 0.75rem;
  flex-shrink: 0;
}

.settings-nav__per-mode-words {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

.settings-nav__legend {
  display: flex;
  align-items: flex-start;
  gap: var(--space-sm);
  margin: auto 0 0;
  padding: var(--space-lg) var(--space-md) 0;
  border-top: 1px solid var(--pn-line);
  font-size: var(--text-xs);
  line-height: var(--text-xs-leading);
  color: var(--pn-dim);
}

.settings-nav__legend .settings-nav__per-mode-mark {
  margin-top: 0.15rem;
}

@media (max-width: 47.9375rem) {
  /* One column: the nav is a row as tall as its links, and the page takes
     the rest. Without the rows named, a short page -- one still loading --
     left the grid to share the spare height out, and the nav stood 170px
     tall around 36px of links until the page grew and it snapped back. */
  .settings-frame {
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: auto 1fr;
    gap: var(--space-lg);
  }

  .settings-nav {
    padding: 0 0 var(--space-sm);
    border-right: 0;
    border-bottom: 1px solid var(--pn-line);
    overflow-x: auto;
  }

  .settings-nav__inner,
  .settings-nav__inner:has(> .settings-nav__legend) {
    position: static;
    height: auto;
    min-height: 0;
  }

  .settings-nav__groups {
    flex-direction: row;
    gap: var(--space-lg);
  }

  .settings-nav__group {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
  }

  .settings-nav__group + .settings-nav__group {
    padding: 0 0 0 var(--space-lg);
    border-top: 0;
    border-left: 1px solid var(--pn-line);
  }

  .settings-nav__head {
    padding: 0;
  }

  .settings-nav__caption,
  .settings-nav__note {
    display: none;
  }

  .settings-nav__links {
    flex-direction: row;
  }

  /* A finger's height: on a phone the column is a row of these. */
  .settings-nav__link,
  .settings-nav__away {
    height: var(--touch-target);
  }

  .settings-nav__legend {
    display: none;
  }
}

/* Whose settings page this is, above its title. */
.scope-line {
  display: inline-flex;
  align-items: center;
  gap: var(--space-sm);
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  color: var(--pn-dim);
}

.scope-line__mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 1.125rem;
  height: 1.125rem;
  border-radius: var(--radius-md);
  font-family: var(--font-heading);
  font-size: var(--text-2xs);
  letter-spacing: -0.02em;
  font-weight: var(--weight-semibold);
}

.scope-line__mark[data-scope="organization"] {
  background: linear-gradient(135deg, var(--night-accent), var(--night-accent-2));
  color: var(--night-bg);
}

.scope-line__mark[data-scope="environment"],
.scope-line__mark[data-scope="location"],
.scope-line__mark[data-scope="charger"] {
  border: 1px solid var(--pn-line);
  background: var(--pn-raise);
  color: var(--pn-dim);
}

/* The card: a title, the body, and a banded footer. One card
   depth and one accent (ADR-0126): only a card that ends something is
   coloured -- see libs/ui/src/setting_panel.rs. */
.card {
  /* Its own width, like `.field`: a card inside a `Form` sits in a column
     that aligns to the start, and without this it took its content width
     while the bare cards beside it stretched -- five cards, two widths. */
  width: 100%;
  /* A column: two cards side by side stretch to one height, and each
     keeps its footer at the bottom rather than floating mid-card. */
  display: flex;
  flex-direction: column;
  /* Porcelain (ADR-0212): opaque, no edge, a deep corner and a soft
     shadow instead of a hairline. */
  background: var(--pn-raise);
  border: 0;
  border-radius: var(--radius-3xl);
  box-shadow:
    inset 0 1px 0 var(--pn-edge),
    var(--shadow-md);
  overflow: hidden;
}

/* Danger keeps its one red: a ring where the edge used to be. */
.card[data-danger="true"] {
  box-shadow:
    inset 0 0 0 1px color-mix(in oklch, var(--destructive), transparent 70%),
    var(--shadow-md);
}

.card__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--space-md) var(--space-xl);
  padding: var(--space-xl) var(--space-2xl) 0;
}

/* Controls about the whole card, beside its title: a range, a line. */
.card__trailing {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-md);
}

/* A card's title is read as a label beside controls, so it is set in
   Inter (ADR-0212 rule 6); Space Grotesk keeps the page title and the
   figures. */
.card__head .heading {
  font-family: var(--font-sans);
  letter-spacing: -0.015em;
}

.card[data-danger="true"] .card__head .heading {
  color: var(--destructive);
}

.card__text {
  flex: 1 1 18rem;
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
  min-width: 0;
}

.card__body {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: var(--gap-stack);
  padding: var(--space-lg) var(--space-2xl) var(--space-xl);
}

/* A card that is only its title and its footer -- "Delete Riverside",
   with the refusal and the button below -- has no body to pad. */
.card__body:empty {
  display: none;
}

.card__head:has(+ .card__body:empty) {
  padding-bottom: var(--space-xl);
}

/* One part of a card that has two: a small heading and its own actions,
   parts divided by a hairline. */
.card-section {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
}

.card-section + .card-section {
  padding-top: var(--space-lg);
  border-top: 1px solid var(--pn-line);
}

.card-section__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-md);
  min-height: 2rem;
}

.card-section__label {
  margin: 0;
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--pn-dim);
}

.card-section__actions {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
}

.card-section__body {
  display: flex;
  flex-direction: column;
  gap: var(--gap-stack);
}

.card__foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-lg);
  flex-wrap: wrap;
  padding: var(--space-lg) var(--space-2xl);
  background: var(--pn-raise-2);
}

.card[data-danger="true"] .card__foot {
  background: color-mix(in oklch, var(--destructive), transparent 95%);
}

.card__state {
  flex: 1;
  min-width: 10rem;
}

.card__actions {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  margin-left: auto;
}

/* The page's actions gathered behind one button, for a phone only (see
   `page_title.rs`). Wider, the row of buttons beside the title is shown
   and these are not. */
/* Also phone-only: the title's links as chips under it (`links`), and the
   settings column's row that opens it as a sheet (`settings_nav.rs`). */
.page-title__more,
.page-title__links,
.settings-nav__toggle {
  display: none;
}

/* What the two phone sheets hold (`Sheet`, which draws the sheet itself):
   the page's actions as full-width rows, and every settings page grouped
   by scope. They only ever open on a phone. */
.page-title__sheet {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  padding: var(--space-sm) var(--space-lg) var(--space-xl);
}

.page-title__sheet > .button,
.page-title__sheet > * > .button {
  width: 100%;
  min-height: 3.25rem;
  justify-content: flex-start;
  padding-inline: var(--space-xl);
  border-radius: var(--radius-2xl);
  font-size: var(--text-base);
}

.settings-nav__sheet {
  display: flex;
  flex-direction: column;
  gap: var(--space-lg);
  padding: var(--space-sm) var(--space-lg) var(--space-xl);
}

.settings-nav__sheet .settings-nav__groups {
  display: flex;
  flex-direction: column;
  gap: var(--space-lg);
}

.settings-nav__sheet .settings-nav__group {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: var(--space-sm);
  padding: 0;
  border: 0;
}

.settings-nav__sheet .settings-nav__head {
  padding: 0 var(--space-xl);
}

/* The sheet is the raised surface, so its groups sit on the page's own
   ground: grouped rows, one inset block per scope. */
.settings-nav__sheet .settings-nav__links {
  display: flex;
  flex-direction: column;
  overflow: hidden;
  border-radius: var(--radius-2xl);
  background: var(--pn-bg);
}

.settings-nav__sheet :is(.settings-nav__link, .settings-nav__away) {
  height: auto;
  min-height: 3.25rem;
  padding: 0 var(--space-xl);
  border-radius: 0;
  font-size: var(--text-base);
}

/* --- back to (back_to.rs) -------------------------------------------------- */

.back-to {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2xs);
  min-height: var(--touch-target);
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  color: var(--pn-dim);
  text-decoration: none;
}

.back-to:focus-visible {
  outline: 2px solid var(--ring);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

/* --- page section (page_section.rs) --------------------------------------- */

.page-section {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

.page-section__rows {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

/* Each row decides for itself whether it belongs; a section none of them
   drew anything in is not there at all. */
.page-section[data-hides-when-empty="true"]:not(:has(.page-section__rows > *)) {
  display: none;
}

@container shell (max-width: 84.9375rem) {
  /* --- under 1360: the standing column has no room beside a table ------- */

  /* The rail is the one in the flow whatever the fold says, and the
     column lies over the page from the trailing edge while it is open,
     behind a scrim that closes it. `absolute` against `.shell__body`,
     the box every drawer is positioned against. */
.shell[data-aside] .shell__rail {
    display: block;
  }

.shell[data-aside] .shell__aside {
    display: none;
  }

.shell[data-aside-over="open"] .shell__aside {
    display: flex;
    position: absolute;
    top: 0;
    bottom: 0;
    right: 0;
    z-index: 30;
    box-shadow: var(--shadow-lg);
  }

.shell[data-aside-over="open"] .shell__aside-scrim {
    display: block;
    position: absolute;
    inset: 0;
    z-index: 29;
    border: 0;
    padding: 0;
    background: color-mix(in oklab, var(--pn-bg) 40%, transparent);
    backdrop-filter: blur(2px);
  }
}

/* Continuous corners (ADR-0212): the curve SwiftUI draws, where the browser
   can draw it. A guarded enhancement -- a browser without `corner-shape`
   keeps the plain radius above, and nothing else changes. */
@supports (corner-shape: squircle) {
  .card {
    corner-shape: var(--corner-continuous);
  }

  .index-preview__aside {
    corner-shape: var(--corner-continuous);
  }

  .index-preview__list .table {
    corner-shape: var(--corner-continuous);
  }

  .settings-nav__sheet .settings-nav__links {
    corner-shape: var(--corner-continuous);
  }

  .shell .tabs[data-sections="true"] {
    corner-shape: var(--corner-continuous);
  }
}

@container shell (max-width: 68.6875rem) {
  /* --- tablet: 768–1099 --------------------------------------------------- */

/* The sidebar becomes a drawer over the page (ADR-0125), opened from the
   page header's menu button. Hidden with `visibility` as well as moved,
   so its links are out of the tab order while it is off-screen; the
   visibility change waits for the slide to finish on the way out. */

.shell .sidebar {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    z-index: 30;
    transform: translateX(-100%);
    visibility: hidden;
    transition:
      transform var(--motion-slow) var(--ease-move),
      visibility var(--motion-slow) step-end;
  }

.shell[data-nav="open"] .sidebar {
    transform: none;
    visibility: visible;
    box-shadow: var(--shadow-lg);
    transition:
      transform var(--motion-slow) var(--ease-move),
      visibility var(--motion-slow) step-start;
  }

.shell[data-nav="open"] .shell__nav-scrim {
    display: block;
    position: absolute;
    inset: 0;
    z-index: 29;
    border: 0;
    padding: 0;
    background: color-mix(in oklab, var(--pn-bg) 40%, transparent);
    backdrop-filter: blur(2px);
  }

.page-header__menu {
    display: inline-flex;
  }

.trail__crumb[data-middle="true"],
  .trail__crumb[data-middle="true"] + .trail__sep {
    display: none;
  }

.shell[data-list="open"] .shell__scrim {
    display: block;
    position: absolute;
    inset: 0;
    z-index: 14;
    border: 0;
    padding: 0;
    background: color-mix(in oklab, var(--pn-bg) 40%, transparent);
    backdrop-filter: blur(2px);
  }
}

@container shell (max-width: 47.9375rem) {

  /* The sandbox band is one line on a phone -- the word and the way out --
     so its height is the --chrome-band everything fixed below it clears.
     The sentence stays for a screen reader. */
.shell .sandbox-band__why {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }

  /* --- phone: < 768 ------------------------------------------------------- */

/* The shell measures its own box, so a host that frames it has to hand
   it the full width it means: the storybook's 768 preset adds its own
   gutters on top for exactly this reason. */

  /* The sidebar is the bar across the top: the mark, the switcher and
     the account. Its destinations move to the bottom bar and search waits
     for a wider screen. The page header below it is the back link and the
     current page; the parent it would name is what the back link says. */
.shell__frame {
    flex-direction: column;
  }

  /* The tablet's drawer rules match here too (the phone is narrower than
     the tablet's ceiling), so the bar undoes them: in flow, on screen,
     never slid away. */
.shell .sidebar,
  .shell[data-nav="open"] .sidebar {
    position: relative;
    transform: none;
    visibility: visible;
    transition: none;
    box-shadow: none;
    width: auto;
    flex-direction: row;
    align-items: center;
    gap: var(--space-md);
    padding: calc(var(--space-sm) + env(safe-area-inset-top, 0px))
      calc(var(--space-lg) + env(safe-area-inset-right, 0px)) var(--space-sm)
      calc(var(--space-lg) + env(safe-area-inset-left, 0px));
    overflow: visible;
  }

.shell .sidebar__head {
    flex: 1;
    min-width: 0;
    flex-direction: row;
    align-items: center;
    gap: var(--space-sm);
  }

.shell .sidebar__brand {
    padding: 0;
  }

.shell .sidebar__brand .wordmark,
.shell .sidebar__search,
.shell .sidebar__nav,
.shell .sidebar__item[data-help="true"],
.shell .sidebar__sky,
.shell .switcher__initials,
.trail__step {
    display: none;
  }

.shell .switcher {
    min-width: 0;
  }

.shell .switcher__trigger {
    min-height: var(--touch-target);
    border: 0;
    background: none;
    padding: var(--space-xs) var(--space-sm);
  }

.shell .sidebar__account {
    padding: 0;
    border-top: 0;
  }

  /* The top bar has room for the face only. */
.shell .sidebar__account .avatar-row__caption {
    display: none;
  }

  /* The face alone is 36px; the button around it is a finger's width. */
.shell .sidebar__account .avatar-row {
    justify-content: center;
    width: var(--touch-target);
    height: var(--touch-target);
    padding: 0;
  }

.page-header {
    min-height: var(--touch-target);
    padding: 0 var(--space-xl);
    gap: var(--space-sm);
  }

.page-header__back {
    display: inline-flex;
  }

.page-header__menu,
  .shell[data-nav="open"] .shell__nav-scrim {
    display: none;
  }

  /* Room for the trail beside the stream and the switch: the switch keeps
     its flask and its track on screen and its word for a screen reader,
     which is still its accessible name. */
.mode-toggle__label {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }

  /* In flow, not fixed: `.shell`'s `contain: layout` makes the shell the
     containing block for anything fixed. The shell is the viewport and the
     bar is its last child, so it sits on the bottom edge and takes its own
     room while the page scrolls above it. `flex-shrink: 0` because a
     column flex item with an explicit height still gives it up when the
     body wants the room. `relative`, not `static`: the glass pill is an
     absolutely placed pseudo-element, and with no positioned bar it would
     take the shell for its box and cover the screen. */
.shell .bottombar {
    display: grid;
    position: relative;
    flex-shrink: 0;
  }

/* A phone has no edge to stand a column at: the caller's sheet is the
     assistant there (`AssistantDock`), opened from More. */
.shell[data-aside] .shell__rail,
  .shell[data-aside-over="open"] .shell__aside,
  .shell[data-aside-over="open"] .shell__aside-scrim {
    display: none;
  }

.shell__aside-sheet {
    display: contents;
  }

.shell__main {
    padding-bottom: var(--space-md);
  }

/* Tighter, so a station's four and a project's four fit a 390px
     screen without scrolling; and when they do not, the strip runs to
     the screen's edge with no padding after the last tab, so a tab cut
     at the edge is a tab to scroll to rather than a tab cut off. */
.tabs {
    gap: 0;
    scroll-padding-inline: var(--space-2xs);
  }

.tabs__tab {
    padding: 0 var(--space-md);
    scroll-snap-align: start;
  }

.page {
    padding: var(--space-xl);
  }
  /* --- phone: the native pass (canvas v14 "Mobile") ------------------------ */

/* Below 768px the console reads the way a phone app does: one light header
   whose title is the page's own, a 16px gutter, and grouped rows. Every
   rule here is inside the phone's container query, so the desktop and the
   tablet never match one of them. */

  /* The top bar is the page's own ground, not a Night slab: the tab bar
     and the anchor card carry the Night, and two dark bars stacked over a
     dark card read as three. The brand's colours go back to the ground's. */
.shell .sidebar,
  .shell[data-nav="open"] .sidebar {
    --brand-from: inherit;
    --brand-to: inherit;
    --brand-star: inherit;
    --brand-ion: inherit;
    /* The tablet's drawer lifts it to 30; here it is in flow, and the
       page's own bar button (`.page-title__more`), which lives inside the
       page's stacking context, has to paint over it. */
    z-index: auto;
    background: var(--pn-bg);
    color: var(--pn-text);
    padding-block: calc(var(--space-xs) + env(safe-area-inset-top, 0px)) var(--space-xs);
    padding-inline-start: calc(var(--space-md) + env(safe-area-inset-left, 0px));
    padding-inline-end: calc(var(--space-sm) + env(safe-area-inset-right, 0px));
  }

.shell .switcher__trigger {
    color: var(--pn-text);
    padding-inline: var(--space-xs) var(--space-sm);
  }

.shell .switcher__trigger:focus-visible {
    outline-color: var(--ring);
  }

  /* "Nordic Park / Production" on one line: the project is where the reader
     is, so it keeps its room and the organization gives way first. */
.shell .switcher__names {
    flex-direction: row;
    align-items: baseline;
    gap: var(--space-xs);
    min-height: 0;
  }

.shell .switcher__organization {
    flex-shrink: 1;
    font-size: var(--text-sm);
    color: var(--pn-dim);
  }

.shell .switcher__organization::after {
    content: " /";
  }

.shell .switcher__project {
    flex-shrink: 0;
    max-width: 70%;
  }

.shell .switcher__chevrons {
    color: var(--pn-dim);
  }

  /* A section's own page (no back link) needs no second bar: its title is
     the page's large title, and what the header still carries -- the
     stream's dot, a staleness chip -- moves up into the top bar, beside the
     account. The mode is in the switcher, so its toggle goes. */
.shell__frame {
    position: relative;
  }

.shell:not(:has(.page-header__back)) .page-header {
    position: absolute;
    top: env(safe-area-inset-top, 0px);
    right: calc(var(--touch-target) + var(--space-md) + env(safe-area-inset-right, 0px));
    /* Over the bar, which the tablet's drawer rule lifts to 30. */
    z-index: 31;
    min-height: calc(var(--touch-target) + 2 * var(--space-xs));
    padding: 0;
  }

.shell:not(:has(.page-header__back)) .page-header__trail {
    display: none;
  }

.shell:not(:has(.page-header__back)) .sidebar__head {
    padding-inline-end: var(--space-3xl);
  }

.page-header .mode-toggle {
    display: none;
  }

  /* A worded button in the header's actions (the console's "Assistant")
     has no room in the phone's top bar, beside the account and the page's
     own button: it covered that button. What it opens is reached from the
     More screen instead. */
.page-header__actions > .button {
    display: none;
  }

  /* A pushed page is a stack, not a place: the scope bar steps aside and
     the header is the stack's bar -- back, the page's name, the dot. */
.shell:has(.page-header__back) .sidebar {
    display: none;
  }

.shell:has(.page-header__back) .page-header {
    min-height: calc(var(--touch-target) + var(--space-md));
    padding-block-start: env(safe-area-inset-top, 0px);
    padding-inline: calc(var(--space-xs) + env(safe-area-inset-left, 0px)) var(--space-xl);
    border-bottom: 1px solid var(--hairline);
    gap: var(--space-2xs);
  }

.shell .page-header__back {
    width: var(--touch-target);
    height: var(--touch-target);
    margin: 0;
    color: var(--brand-from);
  }

.shell:has(.page-header__back) .page-header__trail {
    font-size: var(--text-base);
    font-weight: var(--weight-semibold);
  }

  /* The gutter is the container's, once: the page adds none of its own
     sideways, which is where the second 16px came from. */
.shell .container > .page {
    padding-inline: 0;
  }

.shell .page {
    padding-block: var(--space-md) var(--space-4xl);
  }

  /* The large title: the page's name is the first and biggest thing. */
.shell .page-title__name {
    font-size: var(--text-3xl);
    line-height: var(--text-3xl-leading);
    letter-spacing: -0.01em;
  }

  /* The large title pushes its facts to a line of their own, so a picture
     centred on the block floats between lines: it sits beside the name. */
.shell .page-title__lead {
    align-items: flex-start;
  }

  /* The tab bar: bigger marks, the current one lit and flagged from above. */
.shell .bottombar__item > svg {
    width: 1.5rem;
    height: 1.5rem;
  }

.shell .bottombar__item:is([aria-current="page"], [data-current="true"])::before {
    content: "";
    position: absolute;
    top: calc(-1 * var(--space-sm));
    left: 50%;
    width: 1.75rem;
    height: 3px;
    margin-left: -0.875rem;
    border-radius: 0 0 var(--radius-md) var(--radius-md);
    background: var(--night-accent);
  }

  /* The bar and the tab bar are flex items with a z-index, so each is its
     own stacking context and a sheet inside one could never rise over the
     other. Whichever holds an open menu goes on top for as long as it is
     open. */
.shell .sidebar:has(.menu__scrim),
  .shell .bottombar:has(.menu__scrim) {
    z-index: 45;
  }

.shell .switcher__section + .switcher__section {
    border-top: 0;
  }

.shell .switcher__heading {
    padding: var(--space-lg) var(--space-xl) var(--space-sm);
    font-size: var(--text-xs);
  }

  /* Cards are inset groups: the page's gutter is their edge, so their
     own padding is one step, not two, and they sit flat on the ground. */
.shell .card {
    border: 0;
    box-shadow: none;
  }

.shell .card__head {
    padding: var(--space-lg) var(--space-xl) 0;
  }

.shell .card__head:has(+ .card__body:empty) {
    padding-bottom: var(--space-lg);
  }

.shell .card__body {
    padding: var(--space-md) var(--space-xl) var(--space-lg);
  }

.shell .card__foot {
    padding: var(--space-md) var(--space-xl);
  }

  /* The page's actions: one button up in the bar -- beside the account on
     a section's page, at the bar's end on a pushed one -- and a sheet with
     each action as a full-width row. `fixed` measures the shell. */
.shell .page-title:not([data-keep="true"]) .page-title__actions,
  .shell .page-title[data-keep="true"] .page-title__more,
  .shell .page-title:not(:has(.page-title__actions > *)) .page-title__more {
    display: none;
  }

.shell .page-title__more {
    position: fixed;
    /* Below the sandbox band when there is one: it takes its height off
       the top of the screen (--chrome-band), one line on a phone. */
    top: calc(var(--chrome-band) + env(safe-area-inset-top, 0px) + var(--space-xs));
    right: calc(var(--touch-target) + var(--space-3xl) + env(safe-area-inset-right, 0px));
    z-index: 32;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: var(--touch-target);
    height: var(--touch-target);
    padding: 0;
    border: 0;
    border-radius: var(--radius-pill);
    background: none;
    color: var(--brand-from);
    cursor: pointer;
  }

.shell:has(.page-header__back) .page-title__more {
    top: calc(var(--chrome-band) + env(safe-area-inset-top, 0px) + var(--space-2xs));
    right: calc(var(--space-2xs) + env(safe-area-inset-right, 0px));
  }

.shell:has(.page-header__back):has(.page-title__more) .page-header {
    padding-inline-end: calc(var(--touch-target) + var(--space-md));
  }

.shell .page-title__more:focus-visible {
    outline: 2px solid var(--ring);
    outline-offset: -2px;
  }

  /* An entity's sections (`EntityTabs { sections: true }`): on its summary
     they are rows after the content, the summary's own row left out; on
     a section they are gone, and with them the entity's big title, which
     the bar already names -- its actions stay, behind the bar's button. */
.shell .tabs[data-sections="true"] {
    order: -1;
    /* Rows, not the glass strip: block-level and as wide as the page,
       where the strip is inline and only as wide as its tabs. */
    display: flex;
    align-self: stretch;
    flex-direction: column;
    gap: 0;
    margin: 0;
    padding: 0;
    overflow: hidden;
    border: 0;
    position: static;
    border-radius: var(--radius-3xl);
    background: var(--pn-raise);
    backdrop-filter: none;
    box-shadow:
      inset 0 1px 0 var(--pn-edge),
      var(--shadow-md);
  }

.shell .tabs[data-sections="true"] .tabs__tab {
    position: relative;
    justify-content: flex-start;
    height: auto;
    min-height: 3.25rem;
    margin: 0;
    padding: 0 var(--space-4xl) 0 var(--space-xl);
    border: 0;
    color: var(--pn-text);
    font-size: var(--text-base);
    font-weight: var(--weight-medium);
    box-shadow: none;
  }

.shell .tabs[data-sections="true"] .tabs__tab + .tabs__tab + .tabs__tab::before {
    content: "";
    position: absolute;
    top: 0;
    right: 0;
    left: var(--space-xl);
    height: 1px;
    background: var(--pn-line);
  }

.shell .tabs[data-sections="true"] .tabs__tab::after {
    content: "";
    position: absolute;
    top: 50%;
    right: var(--space-xl);
    width: 0.5rem;
    height: 0.5rem;
    margin-top: -0.25rem;
    border-top: 2px solid var(--pn-dim);
    border-right: 2px solid var(--pn-dim);
    opacity: 0.6;
    transform: rotate(45deg);
  }

.shell .tabs[data-sections="true"] .tabs__tab:active {
    background: var(--pn-raise-2);
  }

  /* The rows sit right under what the entity is doing now -- its title and
     its Right now -- and before everything else about it: a charger's
     history ran ~4,000px before its Sessions. */
.shell .page:has(> .tabs[data-sections="true"]) > .page-title {
    order: -3;
  }

.shell .page:has(> .tabs[data-sections="true"]) > .now-panel,
  .shell .page:has(> .tabs[data-sections="true"]) > * > .now-panel {
    order: -2;
  }

  /* A page body that is one stack of cards hands them to the page, so the
     ordering above can put its Right now first and its sections after it. */
.shell .page:has(> .tabs[data-sections="true"]) > .stack {
    display: contents;
  }

  /* The rows come after the summary, so while the summary is still
     arriving they would slide down with each answer: they wait, the way
     everything after a loading region waits (the motion section). */
.shell .page:has(.page-state[data-status="loading"]) .tabs[data-sections="true"] {
    visibility: hidden;
  }

.shell .tabs[data-sections="true"] .tabs__tab[aria-current="page"],
  .shell .tabs[data-sections="true"]:has(.tabs__tab:first-child:not([aria-current="page"])),
  .shell:has(.tabs[data-sections="true"] .tabs__tab:first-child:not([aria-current="page"])) .page-title__main,
  .shell:has(.tabs[data-sections="true"] .tabs__tab:first-child:not([aria-current="page"])) .page-title__figure {
    display: none;
  }

.shell .trail[data-section="true"] > :not(.trail__section) {
    display: none;
  }

.shell .trail__section {
    display: inline;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  /* On a phone the column scrolls (`.shell__main`), never the page inside
     it, so the page stops being a scroll container: a sticky footer, and
     a row of chips that runs to the screen's edge, both measure the
     column instead of a box that never moves. */
.shell .page {
    overflow: visible;
  }

  /* A card with something to save keeps its Save on screen: the footer
     sticks to the foot of the screen while any of the card is above it.
     `clip`, not `hidden`, because `hidden` makes the card a scroll
     container and a sticky child would measure that instead. */
.shell .card {
    overflow: clip;
  }

.shell .card__foot:has(button[type="submit"]:not(:disabled)) {
    position: sticky;
    bottom: 0;
    z-index: 5;
    background: var(--pn-raise);
    box-shadow: var(--shadow-md);
  }

  /* A filter that picks from a list (When, Location, Device) is a chip
     too, beside the others; its list opens as a sheet (the menu rule). */
.shell .index-toolbar {
    gap: var(--space-sm);
  }

  /* A title's links (`PageTitle { links }`): chips under it. */
.shell .page-title__links {
    display: flex;
    flex-basis: 100%;
    flex-wrap: wrap;
    gap: var(--space-sm);
  }

.shell .page-title__links > * {
    position: relative;
    min-height: 2.25rem;
    padding: 0 var(--space-lg);
    border-radius: var(--radius-pill);
  }

.shell .page-title__links > *::after {
    content: "";
    position: absolute;
    inset: calc((var(--touch-target) - 2.25rem) / -2 - 1px) 0;
  }

  /* Settings' column: one row naming the open page, and the whole column
     as a sheet when it is tapped. The row's button lies over it. */
.shell .settings-frame {
    gap: var(--gap-block);
  }

.shell .settings-nav {
    position: relative;
    padding: 0;
    border: 0;
    overflow: visible;
  }

.shell .settings-nav__toggle {
    display: block;
    position: absolute;
    inset: 0;
    z-index: 2;
    width: 100%;
    margin: 0;
    padding: 0;
    border: 0;
    border-radius: var(--radius-2xl);
    background: none;
    cursor: pointer;
  }

.shell .settings-nav__toggle:focus-visible {
    outline: 2px solid var(--ring);
    outline-offset: -2px;
  }

.shell .settings-nav :is(.settings-nav__head, .settings-nav__legend, .settings-nav__note, .settings-nav__away, .settings-nav__link:not([aria-current="page"])),
  .shell .settings-nav .settings-nav__group:not(:has([aria-current="page"])) {
    display: none;
  }

.shell .settings-nav .settings-nav__inner {
    position: relative;
    padding: 0 var(--space-4xl) 0 var(--space-sm);
    border-radius: var(--radius-2xl);
    background: var(--pn-raise);
  }

.shell .settings-nav .settings-nav__group + .settings-nav__group {
    padding: 0;
    border: 0;
  }

.shell .settings-nav .settings-nav__link[aria-current="page"] {
    flex: 1;
    min-height: 3.25rem;
    border: 0;
    background: none;
    box-shadow: none;
    font-size: var(--text-base);
  }

.shell .settings-nav .settings-nav__inner::after {
    content: "";
    position: absolute;
    top: 50%;
    right: var(--space-xl);
    width: 0.5rem;
    height: 0.5rem;
    margin-top: -0.375rem;
    border-right: 2px solid var(--pn-dim);
    border-bottom: 2px solid var(--pn-dim);
    opacity: 0.7;
    transform: rotate(45deg);
  }

.shell[data-screen="entity"] .list,
  .shell[data-screen="list"] .shell__main,
  .shell[data-list="open"] .shell__scrim {
    display: none;
  }
}

/* site.css -- The public site: its chrome, the marketing vocabulary, docs and the API reference. */

/* ------------------------------------------------------------------ */
/* The public site's chrome (site_chrome.rs). Static HTML, no script:  */
/* the phone menu is a <details> disclosure.                            */
/* ------------------------------------------------------------------ */

.site-header {
  position: sticky;
  top: 0;
  z-index: 20;
  background: color-mix(in oklab, var(--pn-bg) 85%, transparent);
  backdrop-filter: blur(12px);
}

/* The chrome sits on the same frame as the page under it.
 *
 * It used to hard-code `72rem` and a fixed `--space-2xl` inset. --gutter
 * opens from 1rem to 1.5rem at 40rem, so below that width the header was
 * inset 1.5rem while every page body was inset 1rem, and the logo did not
 * line up with the heading beneath it. Same numbers at desktop, different
 * numbers exactly where it shows. */
.site-header__row {
  position: relative;
  max-width: var(--width-page);
  margin: 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-xl);
  padding: var(--space-lg) var(--gutter);
}

.site-header__nav {
  display: flex;
  align-items: center;
  gap: var(--space-md);
}

.site-header__link {
  padding: var(--space-sm) var(--space-lg);
  border-radius: var(--radius-lg);
  color: var(--pn-text);
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  text-decoration: none;
}

.site-header__link[aria-current="page"] {
  background: var(--pn-raise);
}

@media (hover: hover) {
  .site-header__link:hover {
    background: var(--pn-raise);
  }
}

.site-header__link:focus-visible,
.site-header__menu-summary:focus-visible,
.site-footer__link:focus-visible {
  outline: 2px solid var(--brand-from);
  outline-offset: 2px;
}

.site-header__door {
  margin-left: var(--space-md);
  text-decoration: none;
}

.site-header__menu {
  display: none;
}

.site-header__menu-summary {
  cursor: pointer;
  list-style: none;
  padding: var(--space-sm) var(--space-lg);
  border: 1px solid var(--pn-line);
  border-radius: var(--radius-lg);
  color: var(--pn-text);
  font-size: var(--text-sm);
}

.site-header__menu-list {
  position: absolute;
  right: var(--space-2xl);
  top: 100%;
  display: flex;
  flex-direction: column;
  min-width: 12rem;
  padding: var(--space-md);
  border: 1px solid var(--pn-line);
  border-radius: var(--radius-xl);
  background: var(--pn-bg);
}

@media (max-width: 768px) {
  .site-header__nav {
    display: none;
  }

  .site-header__menu {
    display: block;
  }
}

/* --- the language picker ------------------------------------------------ */

/* A language is not a destination, so the picker stands after a hairline
   rather than among the links: "Svenska" in the row read as a sixth page
   and said nothing about the language already being read. */
.site-header__rule {
  width: 1px;
  height: 1.25rem;
  margin: 0 var(--space-xs);
  background: var(--pn-line);
}

.site-lang {
  position: relative;
}

/* The globe says "language" before the word does, so it carries the mark
   rather than the message: quieter than the label everywhere it appears,
   and never squashed by the flex row it sits in. */
.site-lang__globe {
  flex: none;
  color: var(--pn-dim);
}

.site-lang__summary {
  display: inline-flex;
  align-items: center;
  gap: var(--space-sm);
  min-height: var(--touch-target);
  padding: var(--space-sm) var(--space-md) var(--space-sm) var(--space-lg);
  border-radius: var(--radius-lg);
  color: var(--pn-text);
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  cursor: pointer;
  list-style: none;
  user-select: none;
}

.site-lang__summary::-webkit-details-marker {
  display: none;
}

.site-lang[open] .site-lang__summary {
  background: var(--pn-raise);
}

.site-lang__chevron {
  display: inline-flex;
  transition: rotate var(--motion-base) var(--ease-out);
}

.site-lang[open] .site-lang__chevron {
  rotate: 180deg;
}

.site-lang__menu {
  position: absolute;
  top: calc(100% + var(--space-sm));
  right: 0;
  z-index: 30;
  min-width: 13rem;
  max-width: 17rem;
  padding: var(--space-sm);
  border: 1px solid var(--pn-line);
  border-radius: var(--radius-2xl);
  background: var(--pn-raise);
  box-shadow: var(--shadow-lg);
}

.site-lang__option {
  display: flex;
  align-items: flex-start;
  gap: var(--space-md);
  min-height: var(--touch-target);
  padding: var(--space-md) var(--space-lg);
  border-radius: var(--radius-lg);
  color: var(--pn-text);
  font-size: var(--text-sm);
  line-height: var(--text-sm-leading);
  text-decoration: none;
}

.site-lang__option[aria-current] {
  font-weight: var(--weight-semibold);
}

.site-lang__tick {
  display: inline-flex;
  flex: none;
  width: 1rem;
  padding-top: 0.125rem;
  color: var(--brand-star);
}

.site-lang__words {
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
}

/* Said in the language it offers, so the reader it is for can read it. */
.site-lang__elsewhere {
  font-size: var(--text-xs);
  line-height: var(--text-xs-leading);
  font-weight: var(--weight-regular);
  color: var(--pn-dim);
}

@media (hover: hover) {
  .site-lang__summary:hover,
  .site-lang__option:hover {
    background: var(--pn-raise-2);
  }
}

.site-lang__summary:focus-visible,
.site-lang__option:focus-visible {
  outline: 2px solid var(--brand-from);
  outline-offset: -2px;
}

/* The phone menu's own group: the picker's list, opened out, under a
   heading -- there is no room for a menu inside a menu. */
.site-header__languages {
  margin-top: var(--space-md);
  padding-top: var(--space-md);
  border-top: 1px solid var(--pn-line);
}

.site-header__languages-label {
  margin: 0;
  padding: 0 var(--space-lg) var(--space-xs);
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--pn-dim);
}

.site-header__languages .site-lang__option {
  font-size: var(--text-base);
}

/* --- the footer's row --------------------------------------------------- */

.site-footer__languages {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-xs) var(--space-md);
  max-width: var(--width-page);
  margin: 0 auto;
  padding: var(--space-xl) var(--gutter) 0;
  color: var(--pn-dim);
}

.site-footer__languages-label {
  margin-right: var(--space-xs);
}

.site-footer__language {
  display: inline-flex;
  align-items: center;
  min-height: var(--touch-target);
  padding: 0 var(--space-lg);
  border-radius: var(--radius-pill);
  color: var(--pn-text);
  text-decoration: none;
}

.site-footer__language[aria-current] {
  background: var(--pn-raise);
  font-weight: var(--weight-semibold);
}

@media (hover: hover) {
  .site-footer__language:hover {
    background: var(--pn-raise);
  }
}

.site-footer {
  margin-top: var(--space-6xl);
  background: var(--pn-bg);
  color: var(--pn-dim);
  font-size: var(--text-sm);
}

/* A grid, not a wrapping flex row.
 *
 * Flex packed the groups against the left and left two thirds of a 1440px
 * page empty, which read as a column that had failed to load rather than as
 * a deliberately short footer. auto-fit distributes whatever groups exist
 * across the width, so the footer stays balanced whether there are two or
 * five, and collapses to one column on a phone without a breakpoint. */
.site-footer__row {
  max-width: var(--width-page);
  margin: 0 auto;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(10rem, 100%), 1fr));
  gap: var(--space-3xl);
  padding: var(--space-5xl) var(--gutter) var(--space-2xl);
}

.site-footer__group {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  min-width: 0;
}

.site-footer__heading {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.site-footer__list {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  list-style: none;
  margin: 0;
  padding: 0;
}

/* WCAG 2.2's 2.5.8: a target at least 24px tall. The bare text line was
   17px, and every page on the site failed on it, because every page has
   this footer. */
.site-footer__link {
  display: inline-flex;
  align-items: center;
  min-height: 1.5rem;
  color: var(--pn-text);
  text-decoration: none;
}

@media (hover: hover) {
  .site-footer__link:hover {
    text-decoration: underline;
  }
}

.site-footer__legal {
  max-width: var(--width-page);
  margin: 0 auto;
  padding: var(--space-xl) var(--gutter) var(--space-3xl);
}

.site-footer__company {
  color: var(--pn-text);
  font-weight: var(--weight-medium);
}

/* ------------------------------------------------------------------ */
/* The public site's page vocabulary (marketing.rs).                    */
/* ------------------------------------------------------------------ */

.mk-eyebrow {
  margin: 0;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  letter-spacing: 0.2em;
  text-transform: uppercase;
  /* The brand green alone is 3.76:1 on white. Pulled toward the text
     colour, it clears 4.5:1 on either ground and still reads as brand. */
  color: color-mix(in oklab, var(--brand-from) 65%, var(--pn-text));
}

.mk-accent {
  background: linear-gradient(90deg, var(--brand-from), var(--brand-to));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.mk-section {
  max-width: var(--width-page);
  margin: 0 auto;
  padding: var(--space-3xl) var(--gutter);
  scroll-margin-top: var(--topbar-height);
}

.mk-section__header {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  max-width: var(--width-content);
  margin: 0 auto var(--space-3xl);
  text-align: center;
}

.mk-section__title {
  margin: 0;
  font-family: var(--font-heading);
  font-size: var(--text-3xl);
  line-height: var(--text-3xl-leading);
  font-weight: var(--weight-semibold);
  letter-spacing: -0.01em;
  text-wrap: balance;
  color: var(--pn-text);
}

.mk-section__lede {
  margin: 0;
  /* Centred text needs a shorter measure than left-aligned text: every line
     starts somewhere new. The header is 48rem, which ran to ~95 characters. */
  max-width: 40rem;
  margin-inline: auto;
  color: var(--pn-dim);
  text-wrap: balance;
}

/* A lede outside `.mk-section__header` (SectionNote). The header centres
 * its own text; a note in a section's body does not inherit that, so it is
 * a centred 40rem box full of left-aligned text -- invisible on a paragraph,
 * obvious on a two-word link, which then looks randomly indented. */
.mk-section__lede[data-align="center"] {
  text-align: center;
}

.mk-section__body {
  display: flex;
  flex-direction: column;
  gap: var(--gap-section);
}

.mk-pillars {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(18rem, 100%), 1fr));
  gap: var(--space-xl);
}

.mk-pillar {
  display: flex;
  flex-direction: column;
  gap: var(--space-lg);
  padding: var(--space-3xl);
  border: 1px solid var(--pn-line);
  border-radius: var(--radius-xl);
  background: var(--pn-raise);
}

.mk-pillar__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.5rem;
  height: 2.5rem;
  border-radius: var(--radius-lg);
  color: var(--brand-from);
  background: color-mix(in oklab, var(--brand-from) 15%, transparent);
}

.mk-pillar__title,
.mk-spotlight__title {
  margin: 0;
  font-family: var(--font-heading);
  font-size: var(--text-xl);
  line-height: var(--text-xl-leading);
  font-weight: var(--weight-semibold);
  text-wrap: balance;
  color: var(--pn-text);
}

.mk-pillar__description,
.mk-spotlight__paragraph {
  margin: 0;
  font-size: var(--text-sm);
  line-height: var(--text-sm-leading);
  color: var(--pn-dim);
}

.mk-checklist {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  margin: 0;
  padding: var(--space-lg) 0 0;
  border-top: 1px solid var(--pn-line);
  list-style: none;
}

.mk-checklist__item {
  display: flex;
  align-items: flex-start;
  gap: var(--space-md);
  font-size: var(--text-sm);
  color: var(--pn-text);
}

/* A FactRow borrows the checklist item's type and spacing, but that item is
 * a row -- a mark beside one line -- and this holds a heading, paragraphs
 * and a list, which must stack. As a row they sat side by side and pushed a
 * phone's page sideways. */
.mk-fact-row {
  flex-direction: column;
  gap: var(--space-xs);
  min-width: 0;
}

.mk-checklist__mark {
  display: inline-flex;
  margin-top: var(--space-2xs);
  color: var(--brand-from);
}

.mk-spotlight {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(20rem, 100%), 1fr));
  align-items: center;
  gap: var(--space-3xl);
}

/* Picture first only when they stand side by side: in one column the
   copy leads, or a picture lands under the section above it as if it
   were that section's. */
@media (min-width: 44rem) {
  .mk-spotlight[data-reverse="true"] .mk-spotlight__visual {
    order: -1;
  }
}

.mk-spotlight__copy {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-lg);
}

.mk-spotlight__visual {
  min-width: 0;
}

.mk-cta {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-xl);
  max-width: var(--width-content);
  margin: 0 auto;
  padding: var(--space-3xl) var(--gutter);
  text-align: center;
}

.mk-cta__title {
  margin: 0;
  font-family: var(--font-heading);
  font-size: var(--text-4xl);
  line-height: var(--text-4xl-leading);
  font-weight: var(--weight-semibold);
  text-wrap: balance;
  color: var(--pn-text);
}

.mk-cta__description,
.mk-cta__small-print {
  margin: 0;
  color: var(--pn-dim);
  text-wrap: balance;
}

.mk-cta__small-print {
  font-size: var(--text-sm);
}

.mk-cta__actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-lg);
}

.mk-faq {
  display: flex;
  flex-direction: column;
  max-width: var(--width-content);
  margin: 0 auto;
  width: 100%;
}

.mk-faq__item {
  border-bottom: 1px solid var(--pn-line);
}

.mk-faq__question {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-lg);
  padding: var(--space-xl) 0;
  cursor: pointer;
  list-style: none;
  font-weight: var(--weight-medium);
  color: var(--pn-text);
}

.mk-faq__question:focus-visible,
.mk-breadcrumbs__link:focus-visible,
.data-table__scroll:focus-visible {
  outline: 2px solid var(--brand-from);
  outline-offset: 2px;
}

.mk-faq__chevron {
  display: inline-flex;
  transition: transform var(--motion-fast) var(--ease-out);
}

.mk-faq__item[open] .mk-faq__chevron {
  transform: rotate(180deg);
}

.mk-faq__answer {
  margin: 0;
  padding: 0 0 var(--space-xl);
  color: var(--pn-dim);
}

.data-table__scroll {
  overflow-x: auto;
  border: 1px solid var(--pn-line);
  border-radius: var(--radius-xl);
}

.data-table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--text-sm);
}

.data-table__caption {
  padding: var(--space-lg) var(--space-xl);
  text-align: left;
  font-weight: var(--weight-medium);
  color: var(--pn-text);
}

.data-table th,
.data-table td {
  padding: var(--space-md) var(--space-xl);
  border-top: 1px solid var(--pn-line);
  text-align: left;
  vertical-align: top;
  color: var(--pn-text);
}

.data-table thead th {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--pn-dim);
}

.mk-breadcrumbs__list {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-md);
  margin: 0;
  padding: 0;
  list-style: none;
  font-size: var(--text-sm);
  color: var(--pn-dim);
}

.mk-breadcrumbs__item + .mk-breadcrumbs__item::before {
  content: "/";
  margin-right: var(--space-md);
}

.mk-breadcrumbs__link {
  display: inline-flex;
  align-items: center;
  /* A 17px line of text otherwise; 24px is WCAG 2.2's minimum target. */
  min-height: 1.5rem;
  color: var(--pn-dim);
}

.mk-window {
  margin: 0;
  overflow: hidden;
  border: 1px solid var(--pn-line);
  border-radius: var(--radius-xl);
  background: var(--pn-bg);
}

.mk-window__bar {
  display: flex;
  align-items: center;
  gap: var(--space-lg);
  padding: var(--space-md) var(--space-lg);
  border-bottom: 1px solid var(--pn-line);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--pn-dim);
}

.mk-window__dots {
  display: inline-flex;
  gap: var(--space-sm);
}

.mk-window__dot {
  width: 0.625rem;
  height: 0.625rem;
  border-radius: var(--radius-pill);
  background: var(--pn-line);
}

/* The transcript wraps rather than scrolling sideways.
 *
 * `overflow-x: auto` put a scrollbar under the homepage's showcase terminal
 * and cut its third step -- the `curl` that is the whole point of the
 * section -- off at the right edge. A hero sample nobody scrolls is a hero
 * sample nobody reads, and a real terminal wraps too. */
.mk-window__body {
  margin: 0;
  padding: var(--space-xl);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  line-height: var(--text-sm-leading);
  color: var(--pn-text);
}

.mk-breadcrumbs {
  margin-bottom: var(--space-xl);
}

.mk-window__title {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.mk-intro {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-xl);
  max-width: var(--width-content);
  margin: 0 auto;
  padding: var(--space-3xl) var(--gutter);
  text-align: center;
}

.mk-intro[data-align="start"] {
  align-items: flex-start;
  text-align: left;
}

.mk-intro__title {
  margin: 0;
  font-family: var(--font-heading);
  font-size: var(--text-4xl);
  line-height: var(--text-4xl-leading);
  font-weight: var(--weight-semibold);
  letter-spacing: -0.02em;
  text-wrap: balance;
  color: var(--pn-text);
}

/* The one h1 on a marketing page has to outrank every section title under
   it and the closing call to action, both of which are --text-3xl/4xl. At
   --text-4xl it tied the CTA and the page had no headline. Not larger:
   a Scene is --width-content wide, and a --text-6xl title broke to three
   lines inside it. */
@media (min-width: 40rem) {
  .mk-intro__title {
    font-size: var(--text-5xl);
    line-height: var(--text-5xl-leading);
  }
}

.mk-intro__lede {
  margin: 0;
  font-size: var(--text-lg);
  line-height: var(--text-lg-leading);
  color: var(--pn-dim);
  text-wrap: balance;
}

.mk-intro__small-print {
  margin: 0;
  font-size: var(--text-sm);
  color: var(--pn-dim);
}

.mk-intro__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-lg);
}

.mk-facts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(16rem, 100%), 1fr));
  gap: var(--space-lg);
}

@media (min-width: 768px) {
  .mk-facts[data-columns="2"] {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  /* `FactCards`' default. Without it three fell through to auto-fit,
     which packs four 16rem columns into a page -- so six cards laid out
     four and two, with half a row empty. */
  .mk-facts[data-columns="3"] {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }

  .mk-facts[data-columns="4"] {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}

.mk-fact {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  padding: var(--space-2xl);
  border: 1px solid var(--pn-line);
  border-radius: var(--radius-xl);
  background: var(--pn-raise);
}

.mk-fact[data-dashed="true"] {
  border-style: dashed;
  background: transparent;
}

.mk-fact__head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-md);
}

.mk-fact__marker {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.75rem;
  height: 1.75rem;
  border-radius: var(--radius-md);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--pn-text);
  background: var(--pn-raise-2);
}

.mk-fact__title {
  margin: 0;
  flex: 1;
  font-family: var(--font-heading);
  font-size: var(--text-base);
  font-weight: var(--weight-semibold);
  color: var(--pn-text);
}

.mk-fact__link {
  color: inherit;
}

/* The title link covers the whole card. On its own it was a 20px line of
   text, under WCAG 2.2's 24px target on a phone; the card is the thing a
   finger aims at anyway. Safe because a card's body is plain text -- there
   is no second link underneath for this to swallow. */
.mk-fact:has(.mk-fact__link) {
  position: relative;
}

.mk-fact__link::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
}

.mk-fact__link:focus-visible {
  outline: 2px solid var(--brand-from);
  outline-offset: 2px;
}

.mk-fact__badge {
  padding: var(--space-2xs) var(--space-md);
  border-radius: var(--radius-pill);
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  color: var(--pn-text);
  background: var(--pn-raise-2);
}

.mk-fact__badge[data-tone="good"] {
  color: color-mix(in oklab, var(--status-ok) 55%, var(--pn-text));
  background: color-mix(in oklab, var(--status-ok) 15%, transparent);
}

.mk-fact__badge[data-tone="partial"] {
  color: color-mix(in oklab, var(--status-warn) 55%, var(--pn-text));
  background: color-mix(in oklab, var(--status-warn) 15%, transparent);
}

.mk-fact__badge[data-tone="progress"] {
  color: color-mix(in oklab, var(--brand-to) 55%, var(--pn-text));
  background: color-mix(in oklab, var(--brand-to) 15%, transparent);
}

.mk-fact__body {
  margin: 0;
  font-size: var(--text-sm);
  line-height: var(--text-sm-leading);
  color: var(--pn-dim);
}

/* The numbers share a row, and so do the labels.
 *
 * The item was a `column-reverse` flex in a stretched grid cell, which
 * packs its content at the bottom: a stat whose label wrapped to one line
 * sat a whole line lower than its neighbours whose labels wrapped to two,
 * so "3" hung ~23px below "15", "73" and "0". Subgrid puts every value in
 * the strip's first row and every label in its second, so the alignment no
 * longer depends on how the labels happen to wrap.
 *
 * The rows are assigned explicitly because the markup is a <dl>: <dt> (the
 * label) has to precede its <dd> (the value), and the value is what reads
 * first. Where subgrid is unsupported the item is still a two-row grid in
 * the right order -- it just stops aligning across the strip. */
.mk-stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(10rem, 100%), 1fr));
  grid-template-rows: auto auto;
  column-gap: var(--space-xl);
  row-gap: var(--space-xs);
  margin: 0;
}

.mk-stats__item {
  display: grid;
  grid-template-rows: subgrid;
  grid-row: span 2;
  row-gap: var(--space-xs);
}

.mk-stats__value {
  grid-row: 1;
}

.mk-stats__label {
  grid-row: 2;
}

.mk-stats__value {
  margin: 0;
  font-family: var(--font-heading);
  font-size: var(--text-4xl);
  line-height: var(--text-4xl-leading);
  font-weight: var(--weight-semibold);
  color: var(--pn-text);
}

.mk-stats__label {
  font-size: var(--text-sm);
  color: var(--pn-dim);
}

/* The console as it is now (ADR-0125, ADR-0126): the Night sidebar with the
   ion on the current destination, then the panel ground with readouts over
   the list. The sidebar is Night in both themes, as the console's own is;
   everything right of it follows the theme. */
.mk-console {
  display: grid;
  grid-template-columns: 13rem minmax(0, 1fr);
  overflow: hidden;
  border: 1px solid var(--pn-line);
  border-radius: var(--radius-2xl);
  background: var(--pn-bg);
  box-shadow: var(--shadow-lg);
  font-size: var(--text-sm);
  line-height: var(--text-sm-leading);
  text-align: left;
}

.mk-console__sidebar {
  display: flex;
  flex-direction: column;
  gap: var(--gap-stack);
  padding: var(--space-xl) var(--space-lg);
  background: var(--night-bg);
  color: var(--night-dim);
}

.mk-console__org {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  padding: 0 var(--space-sm);
}

.mk-console__monogram {
  display: grid;
  flex: none;
  place-items: center;
  width: 1.75rem;
  height: 1.75rem;
  border-radius: var(--radius-lg);
  background: linear-gradient(135deg, var(--night-accent), var(--night-accent-2));
  font-family: var(--font-heading);
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  color: var(--night-bg);
}

.mk-console__org-name {
  display: flex;
  flex-direction: column;
  font-weight: var(--weight-semibold);
  color: var(--night-text);
}

.mk-console__mode {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  line-height: var(--text-xs-leading);
  font-weight: var(--weight-regular);
  color: var(--night-dim);
}

/* Settings sits at the foot, as it does in the console: the list fills the
   column and its last item takes the slack above it. */
.mk-console__nav {
  display: flex;
  flex: 1;
  flex-direction: column;
  gap: var(--space-2xs);
  margin: 0;
  padding: 0;
  list-style: none;
}

.mk-console__destination {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  min-height: 2.25rem;
  padding: 0 var(--space-md);
  border-radius: var(--radius-xl);
  font-weight: var(--weight-medium);
}

.mk-console__destination:last-child {
  margin-top: auto;
}

.mk-console__destination[data-current="true"] {
  background: var(--night-raise);
  color: var(--night-text);
}

.mk-console__glyph {
  display: inline-flex;
}

.mk-console__destination[data-current="true"] .mk-console__glyph {
  color: var(--night-accent);
}

.mk-console__ion {
  width: var(--space-sm);
  height: var(--space-sm);
  margin-left: auto;
  border-radius: var(--radius-pill);
  background: linear-gradient(135deg, var(--night-accent), var(--night-accent-2));
  box-shadow:
    0 0 0 3px color-mix(in oklab, var(--night-accent) 16%, transparent),
    0 0 10px var(--night-accent);
}

.mk-console__main {
  display: flex;
  flex-direction: column;
  gap: var(--gap-stack);
  min-width: 0;
  padding: var(--space-xl) var(--space-2xl) var(--space-2xl);
}

.mk-console__head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-lg);
}

.mk-console__heading {
  margin: 0;
  font-family: var(--font-heading);
  font-size: var(--text-2xl);
  line-height: var(--text-2xl-leading);
  font-weight: var(--weight-semibold);
  letter-spacing: -0.015em;
  color: var(--pn-text);
}

.mk-console__summary {
  margin: 0;
  color: var(--pn-dim);
}

.mk-console__readouts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(8rem, 100%), 1fr));
  gap: var(--space-lg);
  margin: 0;
}

.mk-console__readout {
  padding: var(--space-lg) var(--space-xl);
  border: 1px solid var(--pn-line);
  border-radius: var(--radius-2xl);
  background: var(--pn-raise);
  box-shadow: var(--shadow-sm);
}

/* The page's one anchor, on ink (ADR-0126, point 2). */
.mk-console__readout:first-child {
  border-color: var(--ink-line);
  background: linear-gradient(162deg, var(--ink-lift) 0%, var(--ink-bg) 60%);
}

.mk-console__readout-label {
  font-size: var(--text-2xs);
  font-weight: var(--weight-semibold);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--pn-dim);
}

.mk-console__readout-value {
  margin: var(--space-xs) 0 0;
  font-family: var(--font-heading);
  font-size: var(--text-2xl);
  line-height: var(--text-2xl-leading);
  font-weight: var(--weight-semibold);
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.02em;
  color: var(--pn-text);
}

.mk-console__readout:first-child .mk-console__readout-label {
  color: var(--ink-dim);
}

.mk-console__readout:first-child .mk-console__readout-value {
  color: var(--ink-text);
}

.mk-console__rows {
  margin: 0;
  padding: 0;
  overflow: hidden;
  border: 1px solid var(--pn-line);
  border-radius: var(--radius-2xl);
  background: var(--pn-raise);
  box-shadow: var(--shadow-sm);
  list-style: none;
}

.mk-console__row {
  display: grid;
  grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr) 3rem auto 4.5rem;
  align-items: center;
  gap: var(--space-lg);
  padding: var(--space-md) var(--space-xl);
}

.mk-console__row + .mk-console__row {
  border-top: 1px solid var(--pn-line);
}

.mk-console__id {
  overflow: hidden;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--pn-text);
}

.mk-console__site,
.mk-console__protocol {
  color: var(--pn-dim);
}

.mk-console__detail {
  font-variant-numeric: tabular-nums;
  text-align: right;
  color: var(--pn-text);
}

@media (max-width: 48rem) {
  .mk-console {
    grid-template-columns: minmax(0, 1fr);
  }

  .mk-console__sidebar {
    display: none;
  }
}

@media (max-width: 640px) {
  .mk-console__row {
    grid-template-columns: minmax(0, 1fr) auto 4rem;
  }

  .mk-console__site,
  .mk-console__protocol {
    display: none;
  }
}

/* --- hero card ---------------------------------------------------------- */

/* Ink in both themes: the component carries `data-theme="dark"`, so the
   drawings inside it resolve the dark panel tokens on its ground and the
   card's own colours come from `--ink-*`. */
.mk-hero-card {
  display: flex;
  flex-direction: column;
  margin: 0;
  overflow: hidden;
  border: 1px solid var(--ink-line);
  border-radius: var(--radius-2xl);
  background: linear-gradient(162deg, var(--ink-lift) 0%, var(--ink-bg) 46%);
  box-shadow:
    var(--shadow-lg),
    inset 0 1px 0 color-mix(in oklab, var(--ink-text) 8%, transparent);
  color: var(--ink-text);
  text-align: left;
}

.mk-hero-card__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--gap-stack);
  padding: var(--space-xl) var(--space-2xl) 0;
}

.mk-hero-card__title {
  margin: 0;
  font-family: var(--font-heading);
  font-size: var(--text-base);
  line-height: var(--text-base-leading);
  font-weight: var(--weight-semibold);
  color: var(--ink-text);
}

.mk-hero-card__subtitle {
  margin: 0;
  font-size: var(--text-xs);
  line-height: var(--text-xs-leading);
  color: var(--ink-dim);
}

/* Each drawing is its own band, ruled off from the one above it. */
.mk-hero-card__body {
  display: flex;
  flex-direction: column;
  gap: var(--gap-stack);
  padding: var(--space-lg) var(--space-2xl) var(--space-xl);
}

.mk-hero-card__body > * + * {
  padding-top: var(--gap-stack);
  border-top: 1px solid var(--ink-line);
}

.mk-hero-card__caption {
  padding: var(--space-md) var(--space-2xl);
  border-top: 1px solid var(--ink-line);
  font-size: var(--text-xs);
  line-height: var(--text-xs-leading);
  color: var(--ink-dim);
}

/* --- the fuse demo: the home page's hero, answering without a script ---- */

/* Every state is in the markup already; these rules show the one the
   checked inputs name. An element listing car counts in data-cars is
   hidden unless the checked count is one of them, and one marked data-on
   is hidden on the other side of the switch. What varies continuously --
   a ring's arc, the speed current runs, the fault colour -- is a custom
   property set per state on the figure. See src/fuse_demo.rs. */
.fuse-demo {
  --fuse-demo-live: var(--night-accent);
  --fuse-demo-reading: var(--ink-text);
  --fuse-demo-share: 0;
  --fuse-demo-load-on: 0;
  --fuse-demo-load-off: 0;
  --fuse-demo-period: 2s;
  /* An inline-size container takes no width from its contents, and the
     scene centres what it holds: without a width of its own it is a
     sliver. */
  container-type: inline-size;
  position: relative;
  box-sizing: border-box;
  width: 100%;
  max-width: var(--width-page);
  margin: var(--space-4xl) auto 0;
  padding: var(--space-2xl) var(--space-2xl) var(--space-xl);
  border: 1px solid var(--ink-line);
  border-radius: var(--radius-2xl);
  background:
    radial-gradient(ellipse 60% 45% at 50% 0%, color-mix(in oklab, var(--night-accent) 9%, transparent), transparent),
    var(--ink-bg);
  color: var(--ink-text);
  box-shadow: 0 2.5rem 6rem -3rem color-mix(in oklab, var(--night-accent) 35%, transparent);
}

.fuse-demo:has(.fuse-demo__count[value="0"]:checked) [data-cars]:not([data-cars~="0"]),
.fuse-demo:has(.fuse-demo__count[value="1"]:checked) [data-cars]:not([data-cars~="1"]),
.fuse-demo:has(.fuse-demo__count[value="2"]:checked) [data-cars]:not([data-cars~="2"]),
.fuse-demo:has(.fuse-demo__count[value="3"]:checked) [data-cars]:not([data-cars~="3"]),
.fuse-demo:has(.fuse-demo__count[value="4"]:checked) [data-cars]:not([data-cars~="4"]),
.fuse-demo:has(.fuse-demo__count[value="5"]:checked) [data-cars]:not([data-cars~="5"]),
.fuse-demo:has(.fuse-demo__count[value="6"]:checked) [data-cars]:not([data-cars~="6"]),
.fuse-demo:has(.fuse-demo__switch-input:checked) [data-on="false"],
.fuse-demo:has(.fuse-demo__switch-input:not(:checked)) [data-on="true"] {
  display: none;
}

/* Per count: each car's share of its ring, the fuse's load with Flowion on
   and off (of 100), and how long current takes to run down a wire, longer
   as each car gets less. */
.fuse-demo:has(.fuse-demo__count[value="1"]:checked) {
  --fuse-demo-share: 100;
  --fuse-demo-load-on: 50.8;
  --fuse-demo-load-off: 50.8;
  --fuse-demo-period: 2.2s;
}

.fuse-demo:has(.fuse-demo__count[value="2"]:checked) {
  --fuse-demo-share: 93.75;
  --fuse-demo-load-on: 95.2;
  --fuse-demo-load-off: 100;
  --fuse-demo-period: 2.35s;
}

.fuse-demo:has(.fuse-demo__count[value="3"]:checked) {
  --fuse-demo-share: 62.5;
  --fuse-demo-load-on: 95.2;
  --fuse-demo-load-off: 100;
  --fuse-demo-period: 3.5s;
}

.fuse-demo:has(.fuse-demo__count[value="4"]:checked) {
  --fuse-demo-share: 46.9;
  --fuse-demo-load-on: 95.2;
  --fuse-demo-load-off: 100;
  --fuse-demo-period: 4.7s;
}

.fuse-demo:has(.fuse-demo__count[value="5"]:checked) {
  --fuse-demo-share: 37.5;
  --fuse-demo-load-on: 95.2;
  --fuse-demo-load-off: 100;
  --fuse-demo-period: 5.9s;
}

.fuse-demo:has(.fuse-demo__count[value="6"]:checked) {
  --fuse-demo-share: 31.3;
  --fuse-demo-load-on: 95.2;
  --fuse-demo-load-off: 100;
  --fuse-demo-period: 7s;
}

/* Flowion off with more than one car: the fuse trips. */
.fuse-demo:has(.fuse-demo__switch-input:not(:checked)):has(.fuse-demo__count:is([value="2"], [value="3"], [value="4"], [value="5"], [value="6"]):checked) {
  --fuse-demo-live: var(--ink-fault);
  --fuse-demo-reading: var(--ink-fault);
}

/* The controls are real inputs, out of sight; the switch and the steps
   below are their labels. */
.fuse-demo__switch-input,
.fuse-demo__count,
.fuse-demo__counts legend,
.fuse-demo__hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

.fuse-demo__counts {
  min-width: 0;
  margin: 0;
  padding: 0;
  border: 0;
}

/* The wide and the narrow drawing: the sheet shows the one that fits. */
.fuse-demo__drawings {
  min-width: 0;
}

.fuse-demo__art {
  display: block;
  width: 100%;
  height: auto;
  overflow: visible;
}

.fuse-demo__art[data-layout="narrow"] {
  display: none;
}

@container (max-width: 40rem) {
  .fuse-demo__art[data-layout="wide"] {
    display: none;
  }

  .fuse-demo__art[data-layout="narrow"] {
    display: block;
  }
}

.fuse-demo__lot {
  fill: none;
  stroke: var(--ink-text);
  stroke-opacity: 0.12;
  stroke-width: 1.5;
}

.fuse-demo__wiring,
.fuse-demo__drop {
  fill: none;
  stroke: color-mix(in oklab, var(--fuse-demo-live) 50%, transparent);
  stroke-width: 1.4;
  transition: stroke var(--motion-slow) var(--ease-out);
}

.fuse-demo__drop[data-free],
.fuse-demo__node[data-free] {
  stroke: var(--ink-line);
  stroke-dasharray: 3 6;
}

.fuse-demo__joint {
  fill: var(--fuse-demo-live);
}

.fuse-demo__joint[data-free] {
  fill: var(--ink-line);
}

.fuse-demo__current {
  fill: none;
  stroke-linecap: round;
  stroke-dasharray: 8 200;
  animation: fuse-demo-flow var(--fuse-demo-period) linear infinite;
}

.fuse-demo__current[data-part="glow"] {
  stroke: var(--fuse-demo-live);
  stroke-width: 6;
  opacity: 0.28;
}

.fuse-demo__current[data-part="core"] {
  stroke: var(--night-star);
  stroke-width: 1.6;
}

.fuse-demo__node {
  fill: var(--ink-bg);
  stroke: color-mix(in oklab, var(--fuse-demo-live) 35%, transparent);
  stroke-width: 1.2;
}

.fuse-demo__node[data-part="fuse"] {
  stroke: var(--ink-line);
}

.fuse-demo__ring,
.fuse-demo__load {
  fill: none;
  stroke: var(--fuse-demo-live);
  stroke-width: 3;
  stroke-linecap: round;
  transition:
    stroke-dasharray var(--motion-slow) var(--ease-move),
    stroke var(--motion-slow) var(--ease-out);
}

.fuse-demo__ring {
  stroke-dasharray: var(--fuse-demo-share) 100;
}

.fuse-demo__load {
  stroke-width: 4;
}

.fuse-demo__load[data-on="true"] {
  stroke-dasharray: var(--fuse-demo-load-on) 100;
}

.fuse-demo__load[data-on="false"] {
  stroke-dasharray: var(--fuse-demo-load-off) 100;
}

.fuse-demo__halo {
  animation: fuse-demo-breathe 3.2s ease-in-out infinite;
}

.fuse-demo__halo-stop {
  stop-color: var(--fuse-demo-live);
  stop-opacity: 0.3;
}

.fuse-demo__halo-stop[data-edge] {
  stop-opacity: 0;
}

.fuse-demo__amps,
.fuse-demo__reading,
.fuse-demo__bay-name,
.fuse-demo__rating {
  text-anchor: middle;
}

.fuse-demo__amps {
  font-family: var(--font-heading);
  font-weight: var(--weight-semibold);
  fill: var(--ink-text);
}

.fuse-demo__amps[data-free] {
  font-weight: var(--weight-medium);
  fill: var(--ink-dim);
}

.fuse-demo__reading {
  font-family: var(--font-heading);
  font-weight: var(--weight-semibold);
  letter-spacing: -0.03em;
  fill: var(--fuse-demo-reading);
}

.fuse-demo__rating {
  fill: var(--ink-dim);
}

.fuse-demo__bay-name {
  font-family: var(--font-mono);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  fill: var(--ink-dim);
}

/* A car from above, drawn in the line the wiring is drawn in. */
.fuse-demo__car {
  transform-box: fill-box;
  animation: fuse-demo-arrive 0.9s var(--ease-move) both;
}

.fuse-demo__bay[data-bay="2"] .fuse-demo__car {
  animation-delay: 0.1s;
}

.fuse-demo__bay[data-bay="3"] .fuse-demo__car {
  animation-delay: 0.2s;
}

.fuse-demo__bay[data-bay="4"] .fuse-demo__car {
  animation-delay: 0.3s;
}

.fuse-demo__bay[data-bay="5"] .fuse-demo__car {
  animation-delay: 0.4s;
}

.fuse-demo__bay[data-bay="6"] .fuse-demo__car {
  animation-delay: 0.5s;
}

.fuse-demo__body {
  fill: color-mix(in oklab, var(--fuse-demo-live) 7%, transparent);
  stroke: color-mix(in oklab, var(--fuse-demo-live) 65%, var(--ink-text));
  stroke-width: 1.1;
  transition: stroke var(--motion-slow) var(--ease-out);
}

.fuse-demo__glass {
  fill: none;
  stroke: color-mix(in oklab, var(--fuse-demo-live) 65%, var(--ink-text));
  stroke-width: 0.8;
  opacity: 0.55;
}

.fuse-demo__lamp {
  fill: none;
  stroke-width: 1;
  stroke-linecap: round;
}

.fuse-demo__lamp[data-end="front"] {
  stroke: var(--night-star);
  opacity: 0.8;
}

.fuse-demo__lamp[data-end="rear"] {
  stroke: var(--ink-fault);
  opacity: 0.75;
}

.fuse-demo__battery {
  fill: var(--ink-raise);
}

.fuse-demo__charge {
  fill: var(--fuse-demo-live);
  transform-box: fill-box;
  transform-origin: left center;
  animation: fuse-demo-charge 50s linear forwards;
}

.fuse-demo:has(.fuse-demo__switch-input:not(:checked)):has(.fuse-demo__count:is([value="2"], [value="3"], [value="4"], [value="5"], [value="6"]):checked) .fuse-demo__charge {
  animation-play-state: paused;
}

@keyframes fuse-demo-flow {
  from {
    stroke-dashoffset: 8;
  }

  to {
    stroke-dashoffset: -100;
  }
}

@keyframes fuse-demo-arrive {
  from {
    opacity: 0;
    transform: translateY(1.5rem);
  }
}

@keyframes fuse-demo-charge {
  to {
    transform: scale(1, 1);
  }
}

@keyframes fuse-demo-breathe {
  50% {
    opacity: 0.55;
  }
}

.fuse-demo__controls {
  position: relative;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-lg);
  margin-top: var(--space-xl);
}

.fuse-demo__switch,
.fuse-demo__stepper {
  display: inline-flex;
  align-items: center;
  min-height: var(--touch-target);
  border: 1px solid var(--ink-line);
  border-radius: var(--radius-pill);
  background: var(--ink-lift);
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  color: var(--ink-text);
}

.fuse-demo__switch {
  gap: var(--space-lg);
  padding: 0 var(--space-sm) 0 var(--space-xl);
  cursor: pointer;
}

.fuse-demo__lamp-dot {
  width: 0.5rem;
  height: 0.5rem;
  border-radius: var(--radius-pill);
  background: var(--ink-dim);
  transition: background var(--motion-base) var(--ease-out);
}

.fuse-demo__knob {
  position: relative;
  width: 2.625rem;
  height: 1.5rem;
  border-radius: var(--radius-pill);
  background: var(--ink-raise);
  transition: background var(--motion-base) var(--ease-out);
}

.fuse-demo__knob::after {
  content: "";
  position: absolute;
  top: 0.1875rem;
  left: 0.1875rem;
  width: 1.125rem;
  height: 1.125rem;
  border-radius: var(--radius-pill);
  background: var(--night-star);
  transition: transform var(--motion-base) var(--ease-move);
}

.fuse-demo:has(.fuse-demo__switch-input:checked) .fuse-demo__lamp-dot,
.fuse-demo:has(.fuse-demo__switch-input:checked) .fuse-demo__knob {
  background: var(--night-accent);
}

.fuse-demo:has(.fuse-demo__switch-input:checked) .fuse-demo__knob::after {
  transform: translateX(1.125rem);
}

.fuse-demo__stepper {
  padding: 0;
}

.fuse-demo__step {
  display: grid;
  place-items: center;
  width: var(--touch-target);
  height: var(--touch-target);
  border-radius: var(--radius-pill);
  font-size: var(--text-lg);
  line-height: 1;
  cursor: pointer;
  user-select: none;
  transition: background var(--motion-fast) var(--ease-out);
}

.fuse-demo__step[data-disabled] {
  color: var(--ink-dim);
  cursor: default;
}

.fuse-demo__count-words {
  min-width: 4.5rem;
  text-align: center;
}

.fuse-demo:has(.fuse-demo__switch-input:focus-visible) .fuse-demo__switch,
.fuse-demo:has(.fuse-demo__count:focus-visible) .fuse-demo__stepper {
  outline: 2px solid var(--night-accent);
  outline-offset: 2px;
}

.fuse-demo__status {
  min-height: 3.25rem;
  margin-top: var(--space-lg);
  text-align: center;
}

.fuse-demo__state {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  margin: 0;
}

.fuse-demo__say {
  font-weight: var(--weight-semibold);
  color: var(--night-accent);
}

.fuse-demo__state[data-tripped] .fuse-demo__say {
  color: var(--ink-fault);
}

.fuse-demo__tip {
  font-size: var(--text-sm);
  line-height: var(--text-sm-leading);
  color: var(--ink-dim);
}

@media (hover: hover) {
  .fuse-demo__switch:hover,
  .fuse-demo__step:not([data-disabled]):hover {
    background: var(--ink-raise);
  }
}

@container (max-width: 40rem) {
  .fuse-demo__controls {
    gap: var(--space-sm);
  }

  .fuse-demo__switch {
    padding-left: var(--space-lg);
  }

  .fuse-demo__count-words {
    min-width: 3.75rem;
  }
}

@media (max-width: 40rem) {
  .fuse-demo {
    margin-top: var(--space-2xl);
    padding: var(--space-xl) var(--space-md) var(--space-lg);
  }
}

@media (prefers-reduced-motion: reduce) {
  .fuse-demo__current[data-part="glow"] {
    display: none;
  }

  .fuse-demo__current,
  .fuse-demo__car,
  .fuse-demo__charge,
  .fuse-demo__halo {
    animation: none;
  }

  .fuse-demo__current {
    stroke-dasharray: none;
    opacity: 0.6;
  }
}

/* --- a section in daylight ---------------------------------------------- */

/* A light band: on a night page it reads as day, on a light page as a
   quiet panel. It carries data-theme="light", so everything inside
   resolves the light tokens. */
.mk-section[data-ground="day"] {
  max-width: calc(var(--width-page) + 2 * var(--space-3xl));
  padding: var(--space-5xl) var(--space-3xl);
  border-radius: calc(var(--radius-2xl) * 2);
  background: var(--pn-bg);
  color: var(--pn-text);
}

/* --- what a product connects to ----------------------------------------- */

.mk-connections {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(15rem, 100%), 1fr));
  gap: var(--space-xl);
}

.mk-connections__group {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  padding: var(--space-xl);
  border: 1px solid var(--pn-line);
  border-radius: calc(var(--radius-2xl) * 1.25);
  background: var(--pn-raise);
}

.mk-connections__title {
  margin: 0 0 var(--space-xs);
  font-size: var(--text-sm);
  line-height: var(--text-sm-leading);
  font-weight: var(--weight-semibold);
  color: var(--pn-dim);
}

.mk-connections__list {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  margin: 0;
  padding: 0;
  list-style: none;
}

.mk-connection {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  column-gap: var(--space-md);
  padding: var(--space-lg) var(--space-lg);
  border: 1px solid var(--pn-line);
  border-radius: var(--radius-2xl);
  background: var(--pn-raise-2);
}

.mk-connection__name {
  font-family: var(--font-heading);
  font-size: var(--text-base);
  line-height: var(--text-base-leading);
  font-weight: var(--weight-semibold);
  color: var(--pn-text);
}

.mk-connection__detail {
  grid-column: 1;
  font-size: var(--text-xs);
  line-height: var(--text-xs-leading);
  color: var(--pn-dim);
}

.mk-connection .mk-fact__badge {
  grid-column: 2;
  grid-row: 1 / span 2;
}

/* --- an invoice, as paper ------------------------------------------------ */

.mk-invoice {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-xl);
}

.mk-invoice__paper {
  width: min(100%, 25rem);
  padding: var(--space-xl) var(--space-2xl);
  border-radius: var(--radius-2xl);
  background: var(--pn-raise);
  color: var(--pn-text);
  font-size: var(--text-sm);
  line-height: var(--text-sm-leading);
  box-shadow: 0 1.75rem 3.5rem -1.75rem color-mix(in oklab, var(--foreground) 60%, transparent);
  rotate: -1.2deg;
}

.mk-invoice__head {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--space-xs) var(--space-md);
  padding-bottom: var(--space-md);
  margin-bottom: var(--space-sm);
  border-bottom: 1px solid var(--pn-line);
}

.mk-invoice__title {
  font-weight: var(--weight-semibold);
}

.mk-invoice__detail {
  font-size: var(--text-xs);
  line-height: var(--text-xs-leading);
  color: var(--pn-dim);
}

.mk-invoice__line,
.mk-invoice__total {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--space-lg);
  padding: var(--space-sm) 0;
}

.mk-invoice__line[data-highlighted] {
  margin: var(--space-2xs) calc(-1 * var(--space-md));
  padding: var(--space-sm) var(--space-md);
  border-radius: var(--radius-xl);
  background: color-mix(in oklab, var(--brand-from) 14%, var(--pn-raise));
  color: var(--brand-star);
  font-weight: var(--weight-semibold);
}

.mk-invoice__label {
  display: inline-flex;
  align-items: center;
  gap: var(--space-sm);
}

.mk-invoice__amount {
  font-family: var(--font-mono);
  white-space: nowrap;
}

.mk-invoice__total {
  margin-top: var(--space-sm);
  padding-top: var(--space-md);
  border-top: 1px solid var(--pn-line);
  font-weight: var(--weight-semibold);
}

.mk-invoice__note {
  margin: 0;
  font-size: var(--text-xs);
  line-height: var(--text-xs-leading);
  color: var(--pn-dim);
}

/* --- where money goes ---------------------------------------------------- */

.mk-flow {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-xl);
}

.mk-flow__aside {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: var(--space-md);
  margin: 0 0 var(--space-md);
  padding: var(--space-sm) var(--space-lg);
  border: 1px dashed var(--pn-line);
  border-radius: var(--radius-pill);
  font-size: var(--text-xs);
  line-height: var(--text-xs-leading);
  color: var(--pn-dim);
}

.mk-flow__aside::after {
  content: "";
  position: absolute;
  top: 100%;
  left: 50%;
  height: var(--space-xl);
  border-left: 1px dashed var(--pn-line);
}

.mk-flow__steps {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  width: 100%;
  margin: 0;
  padding: 0;
  list-style: none;
}

.mk-flow__step {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-sm);
  text-align: center;
}

/* The line to the next stop, with a pulse running along it. */
.mk-flow__step:not([data-end])::after {
  content: "";
  position: absolute;
  top: 1.75rem;
  left: calc(50% + 2.25rem);
  right: calc(-50% + 2.25rem);
  height: 2px;
  background:
    linear-gradient(90deg, transparent, var(--brand-from) 45%, transparent 60%) 0 0 / 40% 100% no-repeat,
    color-mix(in oklab, var(--brand-from) 35%, transparent);
  animation: mk-flow-pulse 2.4s linear infinite;
}

.mk-flow__orb {
  display: grid;
  place-items: center;
  width: 3.5rem;
  height: 3.5rem;
  border: 1.5px solid color-mix(in oklab, var(--brand-from) 45%, transparent);
  border-radius: var(--radius-pill);
  background: var(--pn-raise);
  color: var(--brand-star);
}

.mk-flow__step[data-end] .mk-flow__orb {
  border-color: var(--brand-from);
  box-shadow: 0 0 1.75rem color-mix(in oklab, var(--brand-from) 30%, transparent);
}

.mk-flow__title {
  font-size: var(--text-sm);
  line-height: var(--text-sm-leading);
  font-weight: var(--weight-semibold);
  color: var(--pn-text);
}

.mk-flow__detail {
  max-width: 10rem;
  font-size: var(--text-xs);
  line-height: var(--text-xs-leading);
  color: var(--pn-dim);
}

@keyframes mk-flow-pulse {
  from {
    background-position: -60% 0, 0 0;
  }

  to {
    background-position: 160% 0, 0 0;
  }
}

/* --- who it is for -------------------------------------------------------- */

.mk-audiences {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(15rem, 100%), 1fr));
  gap: var(--space-xl);
  margin: 0;
  padding: 0;
  list-style: none;
}

.mk-audience {
  display: flex;
  flex-direction: column;
  overflow: hidden;
  border: 1px solid var(--pn-line);
  border-radius: calc(var(--radius-2xl) * 1.25);
  background: var(--pn-raise);
}

.mk-audience__art {
  display: block;
  width: 100%;
  height: auto;
}

.mk-audience__text {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  padding: var(--space-xl) var(--space-xl) var(--space-2xl);
}

.mk-audience__title {
  margin: 0;
  font-family: var(--font-heading);
  font-size: var(--text-lg);
  line-height: var(--text-lg-leading);
  font-weight: var(--weight-semibold);
  color: var(--pn-text);
}

.mk-audience__description {
  margin: 0;
  font-size: var(--text-sm);
  line-height: var(--text-sm-leading);
  color: var(--pn-dim);
}

/* The scenes are night in both themes: the sky is the brand's. */
.mk-art__sky {
  fill: var(--ink-bg);
}

.mk-art__building,
.mk-art__roof,
.mk-art__window-frame {
  fill: var(--ink-raise);
}

.mk-art__building[data-far] {
  fill: var(--ink-lift);
}

.mk-art__window {
  fill: var(--ink-lift);
}

.mk-art__window[data-lit] {
  fill: color-mix(in oklab, var(--scene-accent-warm) 70%, transparent);
}

.mk-art__garage {
  fill: var(--ink-bg);
  stroke: color-mix(in oklab, var(--night-accent) 50%, transparent);
}

.mk-art__glow {
  fill: var(--night-accent);
  opacity: 0.25;
}

.mk-art__moon {
  fill: var(--night-star);
  opacity: 0.85;
}

.mk-art__ground {
  fill: var(--ink-lift);
}

.mk-art__grid,
.mk-art__road {
  fill: none;
  stroke: var(--ink-line);
}

.mk-art__grid {
  stroke-width: 2;
}

.mk-art__road {
  stroke-width: 10;
  stroke-linecap: round;
}

.mk-art__lines {
  fill: none;
  stroke: var(--ink-text);
  stroke-opacity: 0.3;
  stroke-width: 2;
}

.mk-art__car {
  fill: var(--ink-dim);
}

.mk-art__car[data-charging] {
  fill: var(--night-accent-2);
}

.mk-art__link {
  fill: none;
  stroke: var(--night-accent);
  stroke-opacity: 0.45;
  stroke-width: 1.5;
  stroke-dasharray: 3 5;
}

.mk-art__site {
  fill: var(--night-accent);
}

.mk-art__window-frame {
  stroke: var(--ink-line);
}

.mk-art__code {
  fill: var(--ink-dim);
  opacity: 0.6;
}

.mk-art__code[data-kind="verb"] {
  fill: var(--night-accent-2);
  opacity: 1;
}

.mk-art__code[data-kind="ok"] {
  fill: var(--night-accent);
  opacity: 0.9;
}

.mk-art__code[data-kind="quiet"] {
  opacity: 0.35;
}

@media (max-width: 40rem) {
  .mk-section[data-ground="day"] {
    padding: var(--space-4xl) var(--gutter);
    border-radius: 0;
  }

  .mk-flow__steps {
    grid-template-columns: 1fr;
    row-gap: var(--space-4xl);
  }

  .mk-flow__step:not([data-end])::after {
    top: calc(100% + var(--space-md));
    left: 50%;
    right: auto;
    width: 2px;
    height: calc(var(--space-4xl) - 2 * var(--space-md));
    background: color-mix(in oklab, var(--brand-from) 35%, transparent);
    animation: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  .mk-flow__step:not([data-end])::after {
    animation: none;
  }
}

/* --- outcomes ----------------------------------------------------------- */

.mk-outcomes {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(18rem, 100%), 1fr));
  gap: var(--space-xl);
}

/* Two rows of the grid, shared across the row of cards: every picture band
   in a row is as tall as the tallest, so the titles beneath them start on
   one line whatever each drawing's height. Where subgrid is unsupported
   the card is still picture-then-words, it just stops aligning. */
.mk-outcome {
  display: grid;
  grid-row: span 2;
  grid-template-rows: subgrid;
  row-gap: 0;
  overflow: hidden;
  border: 1px solid var(--pn-line);
  border-radius: var(--radius-2xl);
  background: var(--pn-raise);
  box-shadow:
    var(--shadow-md),
    inset 0 1px 0 color-mix(in oklab, var(--pn-text) 6%, transparent);
  text-align: left;
}

/* The pictures are pinned to the foot of their band, so three of
   different heights still meet the text on one line.

   On the card's own `--pn-raise`, not a raised strip: the drawings in it
   are the console's, and their inks are measured against the tile ground
   they sit on there. `PriceDay`'s low-price figure over a lit hour is
   4.37:1 on `--pn-raise-2` -- under AA -- and clears it on `--pn-raise`. */
.mk-outcome__visual {
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  gap: var(--gap-stack);
  min-height: 11rem;
  padding: var(--space-xl);
  border-bottom: 1px solid var(--pn-line);
  background: var(--pn-raise);
}

.mk-outcome__figure {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

.mk-outcome__figure-head {
  display: flex;
  justify-content: space-between;
  gap: var(--space-md);
  font-size: var(--text-xs);
  line-height: var(--text-xs-leading);
  color: var(--pn-dim);
}

.mk-outcome__figure-value {
  font-weight: var(--weight-medium);
  font-variant-numeric: tabular-nums;
  color: var(--pn-text);
}

.mk-outcome__body {
  display: flex;
  flex: 1;
  flex-direction: column;
  gap: var(--space-md);
  padding: var(--space-xl) var(--space-2xl) var(--space-2xl);
}

.mk-outcome__title {
  margin: 0;
  font-family: var(--font-heading);
  font-size: var(--text-xl);
  line-height: var(--text-xl-leading);
  font-weight: var(--weight-semibold);
  letter-spacing: -0.015em;
  color: var(--pn-text);
}

.mk-outcome__text {
  margin: 0;
  font-size: var(--text-sm);
  line-height: var(--text-sm-leading);
  color: var(--pn-dim);
}

/* Pushed to the foot, so the proofs of a row line up with each other. */
.mk-outcome__proof {
  margin: auto 0 0;
  padding-top: var(--space-md);
  border-top: 1px solid var(--pn-line);
  font-size: var(--text-sm);
  line-height: var(--text-sm-leading);
  color: var(--pn-text);
}

/* --- name wall ---------------------------------------------------------- */

.mk-name-wall {
  display: flex;
  flex-direction: column;
  gap: var(--gap-block);
  padding: var(--space-3xl);
  border: 1px solid var(--pn-line);
  border-radius: var(--radius-2xl);
  background: var(--pn-raise);
  box-shadow: var(--shadow-md);
  text-align: left;
}

.mk-name-wall__names {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-md) var(--space-2xl);
  margin: 0;
  padding: 0;
  list-style: none;
}

.mk-name-wall__name {
  font-family: var(--font-heading);
  font-size: var(--text-2xl);
  line-height: var(--text-2xl-leading);
  font-weight: var(--weight-semibold);
  letter-spacing: -0.02em;
  color: var(--pn-text);
}

/* Every third name a step back, so the wall has a rhythm rather than
   reading as one heavy block. */
.mk-name-wall__name:nth-child(3n + 2) {
  color: var(--pn-dim);
}

.mk-name-wall__name[data-more="true"] {
  color: color-mix(in oklab, var(--brand-from) 65%, var(--pn-text));
}

.mk-name-wall__note {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  padding-top: var(--gap-stack);
  border-top: 1px solid var(--pn-line);
}

/* --- swatches ----------------------------------------------------------- */

.mk-swatches {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(18rem, 100%), 1fr));
  gap: var(--space-lg);
  margin: 0;
  padding: 0;
  list-style: none;
  text-align: left;
}

.mk-swatch {
  display: flex;
  align-items: center;
  gap: var(--space-xl);
  padding: var(--space-lg);
  border: 1px solid var(--pn-line);
  border-radius: var(--radius-xl);
  background: var(--pn-raise);
}

/* Ringed, so Night stays visible on a night ground and Starlight on a
   light one. */
.mk-swatch__chip {
  flex: none;
  width: 3.5rem;
  height: 3.5rem;
  border-radius: var(--radius-lg);
  box-shadow:
    inset 0 0 0 1px color-mix(in oklab, var(--pn-text) 14%, transparent),
    var(--shadow-sm);
}

.mk-swatch__text {
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
  min-width: 0;
}

.mk-swatch__name {
  font-family: var(--font-heading);
  font-size: var(--text-base);
  line-height: var(--text-base-leading);
  font-weight: var(--weight-semibold);
  color: var(--pn-text);
}

.mk-swatch__hex {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  line-height: var(--text-xs-leading);
  color: var(--pn-dim);
}

.mk-swatch__role {
  font-size: var(--text-sm);
  line-height: var(--text-sm-leading);
  color: var(--pn-dim);
}

/* --- directory ---------------------------------------------------------- */

/* `width: 100%` because a centred scene sizes its items to their content,
   which folded the grid to a single tile's width. */
.mk-directory {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(15rem, 100%), 1fr));
  gap: var(--space-lg);
  width: 100%;
  margin: 0;
  padding: 0;
  list-style: none;
  text-align: left;
}

.mk-directory[data-pairs="true"] {
  grid-template-columns: minmax(0, 1fr);
}

@media (min-width: 40rem) {
  .mk-directory[data-pairs="true"] {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

.mk-directory__entry {
  display: flex;
}

/* The whole tile is the link: a reader scanning for one name aims at the
   tile, and a word-sized target inside it would miss them. */
.mk-directory__link {
  display: flex;
  flex: 1;
  flex-direction: column;
  gap: var(--space-xs);
  min-height: var(--touch-target);
  padding: var(--space-lg) var(--space-xl);
  border: 1px solid var(--pn-line);
  border-radius: var(--radius-xl);
  background: var(--pn-raise);
  box-shadow: var(--shadow-sm);
  color: var(--pn-text);
  text-decoration: none;
  transition:
    border-color var(--motion-base) var(--ease-out),
    box-shadow var(--motion-base) var(--ease-out);
}

@media (hover: hover) {
  .mk-directory__link:hover {
    border-color: var(--brand-to);
    box-shadow: var(--shadow-md);
  }
}

.mk-directory__link:focus-visible {
  outline: 2px solid var(--brand-from);
  outline-offset: 2px;
}

.mk-directory__head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-sm);
}

.mk-directory__name {
  font-family: var(--font-heading);
  font-size: var(--text-lg);
  line-height: var(--text-lg-leading);
  font-weight: var(--weight-semibold);
  letter-spacing: -0.01em;
}

.mk-directory__detail {
  font-size: var(--text-sm);
  line-height: var(--text-sm-leading);
  color: var(--pn-dim);
}

@keyframes flow-dash {
  to {
    stroke-dashoffset: -12;
  }
}

.mk-specimen {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  margin: 0;
}

.mk-specimen__ground {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 10rem;
  padding: var(--space-2xl);
  border: 1px solid var(--pn-line);
  border-radius: var(--radius-xl);
  background: var(--pn-bg);
}

.mk-specimen__image {
  max-width: 100%;
  height: auto;
}

.mk-specimen__caption {
  font-size: var(--text-sm);
  color: var(--pn-dim);
}

.mk-specimen[data-treatment="recolor"] .mk-specimen__image {
  filter: hue-rotate(120deg);
}

.mk-specimen[data-treatment="rotate"] .mk-specimen__image {
  transform: rotate(45deg);
}

.mk-specimen[data-treatment="stretch"] .mk-specimen__image {
  transform: scaleX(1.5);
}

.mk-specimen[data-treatment="glow"] .mk-specimen__image {
  filter: drop-shadow(0 0 12px var(--brand-from));
}

.mk-specimen[data-treatment="light-ground"] .mk-specimen__ground {
  background: var(--pn-text);
}

/* The docs page: the sidebar, the outline, the callout, the code block.
 * Spec 100 US2. The two navigations are landmarks with distinct names, and
 * neither needs a script to work. */

/* A tinted panel with a full hairline in the kind's colour, the icon in a
   column of its own. Not a left stripe: a 3px bar on a grey slab read as
   a quotation, which is what the old block quotes looked like too, and the
   one paragraph a reader must not skip looked like the one they could. */
.callout {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--space-md);
  padding: var(--space-lg) var(--space-xl);
  border: 1px solid color-mix(in oklch, var(--status-busy) 35%, transparent);
  border-radius: var(--radius-lg);
  background: color-mix(in oklch, var(--status-busy) 7%, var(--background));
  color: var(--foreground);
}

.callout[data-kind="warning"] {
  border-color: color-mix(in oklch, var(--status-warn) 40%, transparent);
  background: color-mix(in oklch, var(--status-warn) 8%, var(--background));
}

.callout[data-kind="tip"] {
  border-color: color-mix(in oklch, var(--status-ok) 35%, transparent);
  background: color-mix(in oklch, var(--status-ok) 7%, var(--background));
}

.callout__icon {
  display: flex;
  padding-top: 0.15em;
  color: var(--status-busy);
}

.callout[data-kind="warning"] .callout__icon {
  color: var(--status-warn);
}

.callout[data-kind="tip"] .callout__icon {
  color: var(--status-ok);
}

.callout__content {
  display: grid;
  gap: var(--space-xs);
  min-width: 0;
}

.callout__body {
  display: grid;
  gap: var(--space-sm);
  font-size: var(--text-sm);
  line-height: var(--text-sm-leading);
}

/* A paragraph or list item inside the body takes the body's size, even
   where a surrounding sheet sets one on every `p`/`li` (the site's
   `.prose p` did, and every callout rendered a step larger than drawn).
   Two classes so it outranks a `.container p` rule loaded later. */
.callout .callout__body p,
.callout .callout__body li {
  font-size: inherit;
}

.code-block {
  position: relative;
  display: grid;
  gap: var(--space-2xs);
}

.code-block__language {
  justify-self: start;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  line-height: var(--text-xs-leading);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted-foreground);
}

.code-block__pre {
  overflow-x: auto;
  padding: var(--space-md) var(--space-lg);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-md);
  background: var(--raised);
  /* A long line still scrolls -- a shell command broken by the browser
     is not a command -- but the bar is a hairline, not a gutter. */
  scrollbar-width: thin;
  scrollbar-color: var(--hairline) transparent;
}

.code-block__code {
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  line-height: var(--text-sm-leading);
  white-space: pre;
}

/* A request with an address: one framed figure, the bar on top, the
   response below it. The frame moves from each `pre` to the whole. */
.code-block[data-headed="true"] {
  gap: 0;
  overflow: hidden;
  border: 1px solid var(--hairline);
  border-radius: var(--radius-lg);
  background: var(--raised);
}

.code-block[data-headed="true"] .code-block__pre {
  border: 0;
  border-radius: 0;
  background: transparent;
}

.code-block__bar {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-md);
  padding: var(--space-sm) var(--space-lg);
  border-bottom: 1px solid var(--hairline);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  line-height: var(--text-xs-leading);
}

.code-block__bar .code-block__language {
  margin-left: auto;
}

.code-block__method {
  font-weight: var(--weight-semibold);
  color: var(--primary);
}

.code-block__method[data-method="get"] {
  color: var(--status-busy);
}

.code-block__method[data-method="put"],
.code-block__method[data-method="patch"] {
  color: var(--status-warn);
}

.code-block__method[data-method="delete"] {
  color: var(--status-fault);
}

.code-block__path {
  min-width: 0;
  overflow-wrap: anywhere;
  color: var(--muted-foreground);
}

.code-block__response {
  border-top: 1px dashed var(--hairline);
}

.code-block__status {
  display: flex;
  align-items: baseline;
  gap: var(--space-md);
  padding: var(--space-sm) var(--space-lg);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  line-height: var(--text-xs-leading);
  color: var(--muted-foreground);
  cursor: pointer;
}

.code-block__status:focus-visible {
  outline: 2px solid var(--ring);
  outline-offset: -2px;
}

.code-block__status-label {
  color: var(--muted-foreground);
}

.code-block__status-code {
  font-weight: var(--weight-semibold);
  color: var(--status-ok);
}

.docs-nav {
  font-size: var(--text-sm);
  line-height: var(--text-sm-leading);
}

.docs-nav__drawer {
  display: grid;
  gap: var(--space-lg);
}

.docs-nav__summary {
  padding: var(--space-sm) 0;
  font-weight: var(--weight-semibold);
  color: var(--foreground);
  cursor: pointer;
  border-bottom: 1px solid var(--hairline);
}

.docs-nav__tabs,
.docs-nav__pages {
  display: grid;
  gap: var(--space-2xs);
  margin: 0;
  padding: 0;
  list-style: none;
}

.docs-nav__tabs {
  grid-auto-flow: column;
  justify-content: start;
  gap: var(--space-md);
}

.docs-nav__tab-link {
  display: inline-block;
  padding: var(--space-2xs) 0;
  color: var(--muted-foreground);
  text-decoration: none;
  border-bottom: 2px solid transparent;
}

.docs-nav__tab-link[aria-current] {
  color: var(--foreground);
  border-bottom-color: var(--primary);
}

.docs-nav__section {
  display: grid;
  gap: var(--space-xs);
}

.docs-nav__section-title {
  margin: 0;
  font-size: var(--text-xs);
  line-height: var(--text-xs-leading);
  font-weight: var(--weight-semibold);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted-foreground);
}

/* The drawer's own `gap` does not reach through a <details>: Chrome lays
   out its content in an internal slot, so the tabs and every section sat
   flush against the one before. Margins do reach. */
.docs-nav__drawer > * + * {
  margin-top: var(--space-xl);
}

.docs-nav__drawer > .docs-nav__section + .docs-nav__section {
  margin-top: var(--space-2xl);
}

/* A docs section folds: its title is the summary, with a chevron that
   turns when it opens and the number of pages inside. */
.docs-nav__fold {
  display: grid;
}

.docs-nav__fold + .docs-nav__fold {
  margin-top: var(--space-2xs);
}

.docs-nav__fold-title {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  min-height: var(--space-3xl);
  padding: var(--space-2xs) var(--space-xs);
  border-radius: var(--radius-sm);
  font-weight: var(--weight-semibold);
  color: var(--foreground);
  cursor: pointer;
  list-style: none;
}

.docs-nav__fold-title::-webkit-details-marker {
  display: none;
}

@media (hover: hover) {
  .docs-nav__fold-title:hover {
    background: var(--muted);
  }
}

.docs-nav__fold-title:focus-visible {
  outline: 2px solid var(--ring);
  outline-offset: 2px;
}

.docs-nav__fold-chevron {
  display: flex;
  color: var(--muted-foreground);
  transition: transform var(--motion-fast) var(--ease-out);
}

.docs-nav__fold[open] > .docs-nav__fold-title .docs-nav__fold-chevron {
  transform: rotate(90deg);
}

.docs-nav__fold-label {
  flex: 1;
}

.docs-nav__count {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  line-height: var(--text-xs-leading);
  font-weight: var(--weight-regular);
  color: var(--muted-foreground);
}

.docs-nav__fold > .docs-nav__pages {
  padding: var(--space-2xs) 0 var(--space-sm) var(--space-xl);
}

.docs-nav__fold[data-aside="true"] > .docs-nav__fold-title {
  font-weight: var(--weight-medium);
  color: var(--muted-foreground);
}

.docs-nav__fold[data-break="true"] {
  margin-top: var(--space-md);
  padding-top: var(--space-md);
  border-top: 1px solid var(--hairline);
}

.docs-nav__tab,
.docs-nav__page {
  margin: 0;
}

.docs-nav__link {
  display: block;
  padding: var(--space-2xs) var(--space-xs);
  border-radius: var(--radius-sm);
  color: var(--muted-foreground);
  text-decoration: none;
}

@media (hover: hover) {
  .docs-nav__link:hover {
    color: var(--foreground);
    background: var(--muted);
  }
}

.docs-nav__link[aria-current="page"] {
  color: var(--foreground);
  font-weight: var(--weight-medium);
  background: var(--muted);
}

.docs-nav__planned {
  display: flex;
  gap: var(--space-xs);
  align-items: baseline;
  padding: var(--space-2xs) var(--space-xs);
  color: var(--muted-foreground);
}

.docs-nav__planned-mark {
  font-size: var(--text-xs);
  line-height: var(--text-xs-leading);
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

.toc {
  font-size: var(--text-sm);
  line-height: var(--text-sm-leading);
}

.toc__title {
  margin: 0 0 var(--space-xs);
  font-size: var(--text-xs);
  line-height: var(--text-xs-leading);
  font-weight: var(--weight-semibold);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted-foreground);
}

.toc__list {
  display: grid;
  gap: var(--space-2xs);
  margin: 0;
  padding: 0;
  list-style: none;
}

.toc__item[data-level="3"] {
  padding-left: var(--space-md);
}

.toc__item[data-level="4"] {
  padding-left: var(--space-lg);
}

/* 2.5.8's 24px target, as the footer's links have. */
.toc__link {
  display: inline-flex;
  align-items: center;
  min-height: 1.5rem;
  color: var(--muted-foreground);
  text-decoration: none;
}

@media (hover: hover) {
  .toc__link:hover {
    color: var(--foreground);
  }
}

@media (min-width: 64rem) {
  /* Wide enough for a sidebar: the drawer is always open and its summary
   * has nothing left to say.
   *
   * A closed <details> hides its own children through a UA rule
   * (`details:not([open]) > *:not(summary) { display: none }`), which
   * author CSS can override -- and must, here. Hiding only the summary
   * leaves the whole navigation invisible on a desktop, which is exactly
   * what it did until this rule existed. */
  @supports selector(::details-content) {
    .docs-nav__drawer::details-content {
      content-visibility: visible;
      block-size: auto;
    }

    /* Hidden only where the rule above actually opened the drawer. A
     * browser without ::details-content keeps the summary and the reader
     * clicks it: a collapsed sidebar is a small annoyance, an invisible
     * one is a site with no navigation. */
    .docs-nav__summary {
      display: none;
    }
  }
}

/* The blog: a post on the index, and the top of a post. */

.post-card {
  display: grid;
  gap: var(--space-2xs);
  padding-block: var(--space-md);
  border-bottom: 1px solid var(--hairline);
}

.post-card__title {
  margin: 0;
  font-family: var(--font-heading);
  font-size: var(--text-lg);
  line-height: var(--text-lg-leading);
  font-weight: var(--weight-semibold);
}

/* The card is the target. The title alone is a 23px line of text -- one
 * pixel under WCAG 2.2's 24px minimum, which is the least useful way to
 * fail it. The stretched link grows the hit area and not the accessible
 * name: `::after` carries no text, so the link is still announced as its
 * title, which `the_title_is_the_link_and_the_summary_is_not_inside_it`
 * exists to hold. Safe because a card has no second link under it. */
.post-card:has(.post-card__link) {
  position: relative;
}

.post-card__link {
  color: var(--foreground);
  text-decoration: none;
}

.post-card__link::after {
  content: "";
  position: absolute;
  inset: 0;
}

@media (hover: hover) {
  .post-card__link:hover {
    color: var(--primary);
  }
}

.post-card__meta,
.post-header__meta {
  margin: 0;
  font-size: var(--text-xs);
  line-height: var(--text-xs-leading);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted-foreground);
}

.post-card__byline,
.post-header__byline {
  color: var(--muted-foreground);
}

.post-header {
  display: grid;
  gap: var(--space-xs);
  padding-bottom: var(--space-lg);
}

.post-header__title {
  margin: 0;
  font-family: var(--font-heading);
  font-size: var(--text-3xl);
  line-height: var(--text-3xl-leading);
  font-weight: var(--weight-semibold);
}

.post-header__role {
  color: var(--muted-foreground);
}

/* --- api reference ------------------------------------------------------ */

/* A method as a filled tint: GET reads, POST/PUT/PATCH change, DELETE
   removes. The word is the signal; the tone is the second one. */
.method-badge {
  display: inline-flex;
  flex: none;
  align-items: center;
  justify-content: center;
  /* Wide enough for DELETE, so a column of badges lines its titles up. */
  min-width: 5em;
  padding: 0.2em 0.45em;
  border-radius: var(--radius-sm);
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  font-weight: var(--weight-semibold);
  line-height: 1;
  letter-spacing: 0.04em;
  background: color-mix(in oklab, var(--status-unknown) 14%, transparent);
  color: color-mix(in oklab, var(--status-unknown) 60%, var(--foreground));
}

.method-badge[data-method="get"] {
  background: color-mix(in oklab, var(--status-busy) 14%, transparent);
  color: color-mix(in oklab, var(--status-busy) 60%, var(--foreground));
}

.method-badge[data-method="post"] {
  background: color-mix(in oklab, var(--status-ok) 14%, transparent);
  color: color-mix(in oklab, var(--status-ok) 60%, var(--foreground));
}

.method-badge[data-method="put"],
.method-badge[data-method="patch"] {
  background: color-mix(in oklab, var(--status-warn) 16%, transparent);
  color: color-mix(in oklab, var(--status-warn) 55%, var(--foreground));
}

.method-badge[data-method="delete"] {
  background: color-mix(in oklab, var(--status-fault) 14%, transparent);
  color: color-mix(in oklab, var(--status-fault) 60%, var(--foreground));
}

/* The sidebar's resources and, under the open one, its endpoints. */
.reference-nav__resource {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-sm);
  padding: var(--space-2xs) var(--space-xs);
  border-radius: var(--radius-sm);
  color: var(--muted-foreground);
  text-decoration: none;
}

.reference-nav__count {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  line-height: var(--text-xs-leading);
  color: var(--muted-foreground);
}

.reference-nav__resource[aria-current="page"] {
  color: var(--foreground);
  font-weight: var(--weight-medium);
  background: var(--muted);
}

.reference-nav__operations {
  display: grid;
  gap: var(--space-2xs);
  margin: var(--space-2xs) 0 var(--space-xs) var(--space-xs);
  padding: 0 0 0 var(--space-sm);
  border-left: 1px solid var(--hairline);
  list-style: none;
}

.reference-nav__operation {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  min-height: 1.75rem;
  padding: var(--space-2xs) var(--space-xs);
  border-radius: var(--radius-sm);
  font-size: var(--text-sm);
  line-height: var(--text-sm-leading);
  color: var(--muted-foreground);
  text-decoration: none;
}

.reference-nav__operation[aria-current="page"] {
  color: var(--foreground);
  font-weight: var(--weight-medium);
  background: color-mix(in oklab, var(--brand-from) 12%, transparent);
}

@media (hover: hover) {
  .reference-nav__resource:hover,
  .reference-nav__operation:hover {
    color: var(--foreground);
    background: var(--muted);
  }
}

/* The top of an endpoint's page. */
.endpoint-heading {
  display: grid;
  gap: var(--gap-stack);
}

.endpoint-heading__title {
  margin: 0;
  font-family: var(--font-heading);
  font-size: var(--text-4xl);
  line-height: var(--text-4xl-leading);
  font-weight: var(--weight-semibold);
  letter-spacing: -0.02em;
  text-wrap: balance;
  color: var(--foreground);
}

.endpoint-heading__signature {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-md);
  padding: var(--space-md) var(--space-lg);
  border: 1px solid var(--pn-line);
  border-radius: var(--radius-xl);
  background: var(--pn-raise-2);
}

.endpoint-heading__path {
  min-width: 0;
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  line-height: var(--text-sm-leading);
  overflow-wrap: anywhere;
  color: var(--foreground);
}

.endpoint-heading__slot {
  color: color-mix(in oklab, var(--brand-to) 70%, var(--foreground));
}

.endpoint-heading__marks {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-sm);
  margin: 0;
  padding: 0;
  list-style: none;
}

.endpoint-heading__mark {
  display: inline-flex;
  align-items: center;
  min-height: 1.5rem;
  padding: 0 var(--space-sm);
  border-radius: var(--radius-lg);
  font-size: var(--text-xs);
  line-height: var(--text-xs-leading);
  font-weight: var(--weight-medium);
  background: color-mix(in oklab, var(--status-unknown) 14%, transparent);
  color: color-mix(in oklab, var(--status-unknown) 55%, var(--foreground));
}

.endpoint-heading__mark[data-tone="status-busy"] {
  background: color-mix(in oklab, var(--status-busy) 14%, transparent);
  color: color-mix(in oklab, var(--status-busy) 55%, var(--foreground));
}

.endpoint-heading__mark[data-tone="status-warn"] {
  background: color-mix(in oklab, var(--status-warn) 16%, transparent);
  color: color-mix(in oklab, var(--status-warn) 50%, var(--foreground));
}

.endpoint-heading__mark[data-tone="status-ok"] {
  background: color-mix(in oklab, var(--status-ok) 14%, transparent);
  color: color-mix(in oklab, var(--status-ok) 55%, var(--foreground));
}

.endpoint-heading__mark[data-tone="status-fault"] {
  background: color-mix(in oklab, var(--status-fault) 14%, transparent);
  color: color-mix(in oklab, var(--status-fault) 55%, var(--foreground));
}

.endpoint-heading__description {
  display: grid;
  gap: var(--space-md);
  max-width: var(--width-prose);
  font-size: var(--text-base);
  line-height: var(--text-base-leading);
  color: var(--foreground);
}

.endpoint-heading__description p {
  margin: 0;
}

.endpoint-heading__try-it {
  display: grid;
  gap: var(--space-md);
}

/* A titled part of a reference page. */
.reference-section {
  display: grid;
  gap: var(--space-md);
  min-width: 0;
}

.reference-section__head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-md);
}

.reference-section__title {
  margin: 0;
  font-family: var(--font-heading);
  font-size: var(--text-xl);
  line-height: var(--text-xl-leading);
  font-weight: var(--weight-semibold);
  letter-spacing: -0.01em;
  color: var(--foreground);
}

.reference-section__meta {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  line-height: var(--text-xs-leading);
  color: var(--muted-foreground);
}

/* Fields as stacked rows. */
.api-field-list {
  display: grid;
  /* Declared, not implicit: an implicit track is `auto`, which grows to the
     longest unbreakable word inside it and scrolled a phone sideways. */
  grid-template-columns: minmax(0, 1fr);
  margin: 0;
  padding: 0;
  list-style: none;
}

.api-field {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-xs);
  min-width: 0;
  padding: var(--space-md) 0;
  border-top: 1px solid var(--hairline);
}

.api-field__head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-xs) var(--space-md);
}

.api-field__name {
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  line-height: var(--text-sm-leading);
  font-weight: var(--weight-medium);
  overflow-wrap: anywhere;
  color: var(--foreground);
}

.api-field__type {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  line-height: var(--text-xs-leading);
  color: var(--muted-foreground);
}

.api-field__mark {
  font-size: var(--text-xs);
  line-height: var(--text-xs-leading);
  font-weight: var(--weight-semibold);
  color: var(--muted-foreground);
}

.api-field__mark[data-mark="required"] {
  color: color-mix(in oklab, var(--status-warn) 65%, var(--foreground));
}

.api-field__note {
  padding: 0 var(--space-xs);
  border: 1px solid var(--pn-line);
  border-radius: var(--radius-sm);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  line-height: var(--text-xs-leading);
  background: var(--pn-raise-2);
  color: var(--foreground);
}

.api-field__description {
  max-width: var(--width-prose);
  font-size: var(--text-sm);
  line-height: var(--text-sm-leading);
  color: var(--muted-foreground);
}

.api-field__description p {
  margin: 0;
}

/* A type name or a path in a description is one unbreakable word, and a
   phone is narrower than GetStationTariffsResponse.tariff_assignments_json. */
.api-field__description code {
  overflow-wrap: anywhere;
}

.api-field__values {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-xs);
}

.api-field__values-label {
  font-size: var(--text-xs);
  line-height: var(--text-xs-leading);
  color: var(--muted-foreground);
}

.api-field__value {
  padding: 0 var(--space-xs);
  border: 1px solid var(--pn-line);
  border-radius: var(--radius-sm);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  line-height: var(--text-xs-leading);
  background: var(--pn-raise-2);
  color: var(--foreground);
}

.api-field__children {
  margin-top: var(--space-xs);
  padding: 0 var(--space-md);
  border: 1px solid var(--pn-line);
  border-radius: var(--radius-lg);
  background: var(--pn-raise-2);
}

.api-field__children > .api-field-list > .api-field:first-child {
  border-top: 0;
}

.api-field__toggle {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  min-height: var(--touch-target);
  font-size: var(--text-sm);
  line-height: var(--text-sm-leading);
  font-weight: var(--weight-medium);
  color: var(--foreground);
  cursor: pointer;
}

.api-field__toggle:focus-visible {
  outline: 2px solid var(--brand-from);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

.api-field__variants {
  display: grid;
  gap: var(--space-sm);
  padding-bottom: var(--space-md);
}

/* Tabs with no script: a hidden radio, its label as the tab, its panel
   after it. The panels wrap to their own full-width row below the labels,
   which is what lets one flex container hold both in document order. */
.tab-set {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xs);
  min-width: 0;
}

.tab-set__input {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: 0;
  opacity: 0;
  pointer-events: none;
}

.tab-set__tab {
  display: inline-flex;
  align-items: center;
  gap: var(--space-sm);
  min-height: 2rem;
  padding: 0 var(--space-md);
  border: 1px solid var(--pn-line);
  border-radius: var(--radius-lg);
  background: var(--pn-raise);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  line-height: var(--text-xs-leading);
  font-weight: var(--weight-medium);
  color: var(--foreground);
  cursor: pointer;
}

.tab-set__input:checked + .tab-set__tab {
  border-color: var(--foreground);
  background: var(--foreground);
  color: var(--background);
}

.tab-set__input:focus-visible + .tab-set__tab {
  outline: 2px solid var(--brand-from);
  outline-offset: 2px;
}

.tab-set__dot {
  width: 0.45rem;
  height: 0.45rem;
  border-radius: var(--radius-pill);
  background: var(--status-unknown);
}

.tab-set__dot:is([data-tone="status-ok"], [data-tone="status-warn"], [data-tone="status-fault"], [data-tone="status-busy"]) {
  background: var(--tone);
}

.tab-set__panel {
  display: none;
  order: 1;
  flex-basis: 100%;
  min-width: 0;
  padding-top: var(--space-md);
}

.tab-set__input:checked + .tab-set__tab + .tab-set__panel {
  display: block;
}

/* On a code panel the tabs sit on ink, quieter. */
/* The ink ground is painted here, not assumed from a parent: the dim tab
   labels are only legible on ink, and a set that ever sits outside a
   CodePanel would otherwise put them on a light page at 2.3:1. Inside a
   CodePanel it is the same colour, so nothing changes there. */
.tab-set[data-ground="ink"] {
  gap: var(--space-2xs);
  padding: var(--space-sm) var(--space-sm) 0;
  background: var(--ink-bg);
  color: var(--ink-text);
}

.tab-set[data-ground="ink"] .tab-set__tab {
  min-height: 1.75rem;
  border-color: transparent;
  background: transparent;
  font-family: var(--font-sans);
  color: var(--ink-dim);
}

.tab-set[data-ground="ink"] .tab-set__input:checked + .tab-set__tab {
  border-color: transparent;
  background: var(--ink-raise);
  color: var(--ink-text);
}

.tab-set[data-ground="ink"] .tab-set__panel {
  padding-top: 0;
}

/* A dark panel for code. */
.code-panel {
  display: grid;
  min-width: 0;
  overflow: hidden;
  border: 1px solid var(--ink-line);
  border-radius: var(--radius-2xl);
  background: var(--ink-bg);
  color: var(--ink-text);
}

.code-panel__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-md);
  padding: var(--space-sm) var(--space-lg);
  border-bottom: 1px solid var(--ink-line);
}

.code-panel__title {
  font-size: var(--text-2xs);
  font-weight: var(--weight-semibold);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-dim);
}

.code-panel__badge {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  line-height: var(--text-xs-leading);
  color: var(--ink-text);
}

.code-panel__body {
  min-width: 0;
}

/* A code block inside a panel is the panel's body: the panel already has
   the frame, the ground and (in its badge) the language. */
.code-panel__body .code-block {
  gap: 0;
}

.code-panel__body .code-block__language {
  display: none;
}

.code-panel__body .code-block__pre {
  border: 0;
  border-radius: 0;
  background: transparent;
  scrollbar-width: none;
}

/* The examples column is narrow, and a scrollbar under every request and
   response read as clutter: a long line wraps instead. A wrapped JSON
   string or curl line is still copy-paste exact -- the break is only
   drawn. */
.code-panel__body .code-block__code {
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* A resource's endpoints. */
.endpoint-table {
  display: grid;
  margin: 0;
  padding: 0;
  overflow: hidden;
  border: 1px solid var(--pn-line);
  border-radius: var(--radius-xl);
  list-style: none;
}

.endpoint-table__row + .endpoint-table__row {
  border-top: 1px solid var(--pn-line);
}

.endpoint-table__link {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: center;
  gap: var(--space-xs) var(--space-md);
  padding: var(--space-md) var(--space-lg);
  color: var(--foreground);
  text-decoration: none;
}

.endpoint-table__title {
  font-weight: var(--weight-medium);
}

.endpoint-table__path {
  grid-column: 2;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  line-height: var(--text-xs-leading);
  overflow-wrap: anywhere;
  color: var(--muted-foreground);
}

@media (min-width: 64rem) {
  .endpoint-table__link {
    grid-template-columns: auto minmax(12rem, 16rem) minmax(0, 1fr);
  }

  .endpoint-table__path {
    grid-column: auto;
  }
}

@media (hover: hover) {
  .endpoint-table__link:hover {
    background: var(--pn-raise-2);
  }
}

.endpoint-table__link:focus-visible {
  outline: 2px solid var(--brand-from);
  outline-offset: -2px;
}

/* motion.css -- Motion: how things move, as one policy -- every duration a --motion-* token, all of it off under reduced motion. */

/* --- motion ------------------------------------------------------------- */

/* Everything a finger lands on answers the finger, at once, and lets go
   gently: the press is instant (`transition-duration: 0s` while
   `:active`), the release fades over `--motion-fast`. Without this the
   first thing that happened after a tap was the next page, which is what
   made the console feel like it was not listening. */
:is(
    .button,
    .row,
    .sidebar__item,
    .bottombar__item,
    .tabs__tab,
    .picker__option,
    .trail__crumb,
    .coverage-row,
    .jump,
    .chooser__option,
    .chooser__create,
    .chooser__chevron
  ) {
  transition:
    background-color var(--motion-fast) var(--ease-out),
    border-color var(--motion-fast) var(--ease-out),
    box-shadow var(--motion-fast) var(--ease-out),
    color var(--motion-fast) var(--ease-out),
    transform var(--motion-fast) var(--ease-out),
    opacity var(--motion-base) var(--ease-out);
  -webkit-touch-callout: none;
}

/* Controls give; rows and tabs tint, because a whole row shrinking reads
   as the list moving. */
:is(
    .button,
    .bottombar__item,
    .jump
  ):not(
    :disabled
  ):active {
  transform: scale(var(--press-scale));
  transition-duration: 0s;
}

:is(.row, .tabs__tab, .picker__option, .coverage-row):active {
  background-color: var(--pn-raise-2);
  transition-duration: 0s;
}

/* The chooser is on the night sky, where the paper's raise would flash. */
.chooser__option:active {
  background-color: var(--night-line);
  transition-duration: 0s;
}

/* A page settles from the top down. Everything after the first region
   still loading -- at every level, so in document order -- is laid out
   but not shown until that region has its answer; then the next region
   is the one waiting, and so on. So exactly one placeholder is visible at
   a time, and nothing visible ever moves: a region that arrives taller
   than its skeleton only pushes content nobody can see yet. The page's
   own heading and tabs sit above every region and are there at once.
   Measured by `clients/console/e2e/layout.spec.ts`.

   Except a sheet. It is the reader's, opened over the page, and a control
   in it that goes hidden while focused drops focus to the document body,
   where no dialog can catch it: Tab then reaches the page behind and
   Escape stops arriving. A page's own column reloading beneath an open
   sheet is not a reason to blank it.

   And except what a page marks `data-settles-alone`: an element that sits
   outside the page's flow (fixed, or in its own grid column), so showing it
   moves nothing, and that says its own loading state. The console's docked
   "At the charger" panel and the pin that opens its sheet are the two
   today. An attribute, not a class, so a client opts out without naming
   (and so restyling) a class this stylesheet owns. */
.page :is(.page-state[data-status="loading"], :has(.page-state[data-status="loading"])) ~ *:not(.sheet, [data-settles-alone]) {
  visibility: hidden;
  opacity: 0;
}

/* A scroll area that gains a scrollbar must not push its content left.
   Only where scrollbars take room -- a mouse and a classic scrollbar; a
   touch screen's overlay scrollbar takes none, and a reserved gutter
   there is width a phone does not have (it tipped the showcase's phone
   frame into scrolling sideways). The outer scroller only: `.page`
   inside it is not the one that scrolls. */
@media (hover: hover) and (pointer: fine) {
  .shell__main,
  .list__rows {
    scrollbar-gutter: stable;
  }
}

/* Page transitions. The console runs a navigation inside
   `document.startViewTransition` and says which way it moves on the
   document element (`data-nav`: push, pop, swap). The header and the list
   are part of the root snapshot, and the root does not animate, so they
   stay crisp while the screen moves; the sidebar, the ion, the current tab
   and the sandbox band are named below and move on their own. On a phone
   the list is a screen too, so it takes the same name -- only one of the
   two is ever displayed. */
:root::view-transition-old(root),
:root::view-transition-new(root) {
  animation: none;
}

.shell__main {
  view-transition-name: screen;
}

@container shell (max-width: 47.9375rem) {
  .shell[data-screen="list"] .list {
    view-transition-name: screen;
  }
}

::view-transition-group(screen) {
  animation-duration: var(--motion-slow);
  animation-timing-function: var(--ease-move);
}

/* A push and a pop slide whole screens, so the group must not also morph
   from the list's box to the page's. */
:root:is([data-nav="push"], [data-nav="pop"])::view-transition-group(screen) {
  animation: none;
}

::view-transition-old(screen) {
  animation: screen-out var(--motion-fast) var(--ease-out) both;
}

::view-transition-new(screen) {
  animation: screen-in var(--motion-base) var(--ease-out) both;
}

@media (max-width: 47.9375rem) {
  /* A screen is captured without the shell's ground behind it, so two of
     them sliding past each other would show through one another. Each
     carries the shell's own ground, which is the flat panel colour. */
  :root:is([data-nav="push"], [data-nav="pop"])::view-transition-old(screen),
  :root:is([data-nav="push"], [data-nav="pop"])::view-transition-new(screen) {
    background-color: var(--pn-bg);
  }

  :root[data-nav="push"]::view-transition-new(screen),
  :root[data-nav="pop"]::view-transition-old(screen) {
    box-shadow: var(--shadow-lg);
  }

  :root[data-nav="push"]::view-transition-old(screen) {
    animation: screen-behind var(--motion-slow) var(--ease-move) both;
  }

  :root[data-nav="push"]::view-transition-new(screen) {
    animation: screen-over var(--motion-slow) var(--ease-move) both;
  }

  :root[data-nav="pop"]::view-transition-old(screen) {
    animation: screen-over var(--motion-slow) var(--ease-move) reverse both;
    z-index: 1;
  }

  :root[data-nav="pop"]::view-transition-new(screen) {
    animation: screen-behind var(--motion-slow) var(--ease-move) reverse both;
  }
}

@keyframes screen-out {
  to {
    opacity: 0;
  }
}

@keyframes screen-in {
  from {
    opacity: 0;
    transform: translateY(0.25rem);
  }
}

@keyframes screen-over {
  from {
    transform: translateX(100%);
  }
}

@keyframes screen-behind {
  to {
    transform: translateX(-25%);
    opacity: 0.6;
  }
}

/* --- motion around a navigation (ADR-0126) ------------------------------ */

/* Motion says where something came from and gets out of the way: every
   duration is a --motion-* token, so reduced motion (which zeroes them)
   stops all of it without a query of its own.

   1. A menu comes from where it opens: down from a trigger above it, up
      from one below it. */
@keyframes pop-down {
  from {
    opacity: 0;
    transform: translateY(-0.25rem) scale(0.98);
  }
}

@keyframes pop-up {
  from {
    opacity: 0;
    transform: translateY(0.25rem) scale(0.98);
  }
}

.menu,
.switcher__menu {
  animation: pop-down var(--motion-fast) var(--ease-out) both;
}

.menu {
  transform-origin: top right;
}

.switcher__menu {
  transform-origin: top left;
}

.sidebar__account .menu,
.bottombar__more .menu {
  animation-name: pop-up;
  transform-origin: bottom left;
}

.bottombar__more .menu {
  transform-origin: bottom right;
}


/* 2. What arrives after the page has settled -- an answer replacing its
   placeholder, a row the list gained -- fades in rather than appearing in
   one frame. Only then: during a navigation the view transition is
   already fading the whole screen, and content that mounted with it must
   not fade a second time inside the captured image (it would be captured
   half-transparent). `:active-view-transition` is what tells the two
   apart; a browser without it drops the rule, and content simply appears,
   which is what it did before. */
.panel {
  transition: opacity var(--motion-base) var(--ease-out);
}

/* A turn in the assistant's conversation, and the line saying what it is
   doing, arrive the same way -- and rise a little as they come, the way a
   message does in every chat the reader knows. */
.assistant-message,
.assistant-working {
  transition:
    opacity var(--motion-base) var(--ease-out),
    translate var(--motion-base) var(--ease-out);
}

@starting-style {
  :root:not(:active-view-transition)
    :is(.page .panel, .list__rows .row, .assistant-message, .assistant-working) {
    opacity: 0;
  }

  :root:not(:active-view-transition) :is(.assistant-message, .assistant-working) {
    translate: 0 var(--space-xs);
  }
}

/* 3. The ion -- the brand's one moving light -- glides from the
   destination the reader left to the one they chose, and the current
   tab's highlight slides along the tab row the same way. Tabs only on a
   cross-fade: on a phone's push the whole screen slides, and a tab
   gliding separately inside it would come apart from its page. */
.sidebar__ion {
  view-transition-name: ion;
}

:root[data-nav="swap"] .tabs__tab[aria-current="page"] {
  view-transition-name: tab-current;
}

/* Between one thing's own tabs only the page under the tabs changes. Its
   heading and its tab row are captured on their own, so they are not
   faded and lifted with the screen: the same before and after, they hold
   still while the content cross-fades beneath them, and the current
   tab's highlight slides along a row that is not moving. */
:root[data-nav="swap"] .page:has(> .tabs) > .page-title {
  view-transition-name: page-head;
}

:root[data-nav="swap"] .page:has(> .tabs) > .tabs {
  view-transition-name: page-tabs;
}

::view-transition-group(ion),
::view-transition-group(tab-current) {
  animation-duration: var(--motion-slow);
  animation-timing-function: var(--ease-move);
}

/* 4. Scope. The sidebar and the chooser are one sky: entering an
   organization from the chooser contracts the page-sized sky into the
   sidebar, and a change of scope inside the console cross-fades the
   sidebar's contents. Both images fill the group and are cropped from the
   top left rather than scaled, so the sky narrows instead of shrinking
   into a thumbnail. */
.sidebar,
.chooser {
  view-transition-name: sky;
}

::view-transition-group(sky) {
  animation-duration: var(--motion-slow);
  animation-timing-function: var(--ease-move);
}

::view-transition-old(sky),
::view-transition-new(sky) {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: left top;
}

/* The sandbox band drops in from above on entering sandbox and rises away
   on leaving it; the sidebar and the screen, both named, glide down and up
   to make room rather than jumping. */
.sandbox-band {
  view-transition-name: band;
}

@keyframes band-in {
  from {
    transform: translateY(-100%);
  }
}

@keyframes band-out {
  to {
    transform: translateY(-100%);
  }
}

::view-transition-new(band):only-child {
  animation: band-in var(--motion-slow) var(--ease-move) both;
}

::view-transition-old(band):only-child {
  animation: band-out var(--motion-base) var(--ease-out) both;
}

/* 5. Live values move rather than jump: a bar eases to its new width, a
   status dot to its new colour. */
.meter__fill {
  transition: width var(--motion-slow) var(--ease-out);
}

.status__dot {
  transition: background-color var(--motion-base) var(--ease-out);
}

/* touch.css -- Touch: the 44px floor for anything a finger presses, as one policy. */

/* --- touch -------------------------------------------------------------- */

/* On a touch screen everything a finger lands on takes one: 44px each way
   (FR-026), held by `clients/console/e2e/touch.spec.ts`. Two ways to get
   there, chosen per thing:

   - **Visibly larger** where the thing is what a phone is for. The list
     column's rows are how a phone gets anywhere, so they grow to 56px
     with a larger name; the avatar is the one control in a corner that
     is reached for on every page, so it grows too.
   - **An invisible hit area** where the drawing is the right size and
     only the target is not: the home mark, the back link, a link in a
     line of text. A centred `::after` of at least 44px takes the touch
     and draws nothing, so nothing around it moves. */
@media (pointer: coarse) {
  .list .row {
    min-height: 3.5rem;
    gap: var(--space-lg);
  }

  .list .row__name {
    font-size: var(--text-base);
  }

  .avatar {
    width: 2.25rem;
    height: 2.25rem;
    font-size: var(--text-sm);
  }

  /* It sits beside a title that can be long, and the title is what
     gives way. */
  .page-header__back,
  .page-header__menu {
    width: var(--touch-target);
    height: var(--touch-target);
    flex-shrink: 0;
  }

  .tabs__tab {
    height: var(--touch-target);
  }

  .sidebar__brand,
  .avatar,
  .details__link {
    position: relative;
  }

  .sidebar__brand::after,
  .avatar::after,
  .details__link::after {
    content: "";
    position: absolute;
    top: 50%;
    left: 50%;
    width: max(100%, var(--touch-target));
    height: max(100%, var(--touch-target));
    transform: translate(-50%, -50%);
  }
}

/* The night sky.
 *
 * THIS SHEET IS THE ONE EXEMPTION TO "NO LITERAL COLOUR", and the reason
 * is narrow: what follows is **artwork**, not component styling. The sky is
 * twenty-seven stars, each with its own size, position and alpha, in the
 * canvas sky's own palette — mostly white, a scattering of cyan and
 * emerald, the odd warm giant. Putting twenty-seven one-off alphas through
 * the token layer would not make them reusable, it would make the token
 * layer a list of star colours. Everything that *does* change with the
 * theme -- the ground, the bolt, the comet, the ion, the nebulae, the
 * horizon -- goes through the `--sky-*` tokens, so the sky follows the
 * light/dark pair the way every other component does.
 *
 * `components_carry_no_literal_colour` names this file explicitly rather
 * than skipping anything that looks like art, and
 * `the_literal_colour_exemption_has_exactly_one_member` stops the list
 * growing. Component styles still belong under assets/components/, where the rule
 * bites.
 *
 * Carried across from `services/web/src/app/globals.css` rather than
 * redrawn, so the two consoles show the same sky while both exist.
 *
 * WHAT IS HERE AND WHAT IS NOT. An earlier pass shipped a static sky on the
 * grounds that the existing console's `<canvas>` needs `web-sys`, which
 * this crate cannot reach. That was the right constraint applied to the
 * wrong thing: the *canvas* is unreachable, the *effects* are not. Drift,
 * twinkle, the constellation and the pointer parallax are all here, as CSS
 * animations, inline SVG and one custom property the component writes.
 *
 * What is genuinely lost is per-star simulation — 420 independently seeded
 * stars against three tiled layers. On a phone the existing console shows
 * its own tiled fallback for the same reason, so this is the treatment most
 * of its traffic already sees.
 *
 * EVERYTHING THAT MOVES IS BEHIND `prefers-reduced-motion`, the parallax
 * included. A drifting sky behind an error message is decoration; for a
 * reader who asked for stillness it is an obstacle.
 */

/* The scene follows the theme, like every other component: the palette it
   paints text and controls with is the ordinary token layer, and the sky's
   own colours -- bolt, comet, ion, nebulae, horizon -- come through the
   `--sky-*` tokens, which the token layer defines once per ground. So a
   scene on a light document is the same composition by day, in deep
   emerald, with no stars; on a dark one it is the night sky the brand is
   drawn on. Nothing here decides which. */
.scene {
  position: relative;
  isolation: isolate;

  /* So the sky can size itself against the scene rather than the window.
     That is not a refinement, it is what the canvas measures: its
     `CONSTELLATION_MIN_WIDTH = 1024` and its `min(height * 0.58, width *
     0.2)` are both the *canvas's* box. A viewport query gets the same
     answer whenever a scene is the whole page and the wrong one every other
     time -- a scene in a storybook card at 290px was drawing a 275px
     bolt.

     `width: 100%` above is not decoration either, and it is the trap in
     this line: an inline-size container's width no longer depends on its
     contents, so a scene inside anything that sizes to its content
     collapses to zero. It rendered as a blank card and nothing else looked
     wrong. */
  container-type: inline-size;
  display: flex;
  flex-direction: column;
  width: 100%;
  min-height: 100svh;
  overflow: clip;

  background: var(--sky-ground);
  color: var(--foreground);
}

.scene__layer {
  position: absolute;
  inset: 0;
  pointer-events: none;

  /* The sky comes up rather than popping in: a fade, not a movement, so it
     is not inside the reduced-motion block below -- and it is one-shot, so
     it costs nothing after the first two seconds. */
  animation: scene-rise 1.8s ease-out both;
}

@keyframes scene-rise {
  from {
    opacity: 0;
  }
}

/* --- nebulae ------------------------------------------------------------ */

/* A decorative nebula: one blurred colour field. Two ways of drawing the
   same soft disc, chosen by device -- everywhere, the element's own colour
   faded out by a radial *mask*, so no layer is inflated by a blur radius
   and nothing is convolved; on a pointer-fine screen wide enough to be a
   desktop, the real `filter: blur()`, whose light spills past the box the
   way a mask cannot. A phone pays for a gradient, not a convolution. */
.scene__nebula {
  position: absolute;
  border-radius: var(--radius-pill);
  mask-image: radial-gradient(closest-side, #000 25%, transparent 100%);
}

.scene__nebula[data-nebula="one"] {
  top: -8rem;
  left: 8%;
  width: 36rem;
  height: 26rem;
  background: var(--sky-nebula-one);
}

.scene__nebula[data-nebula="two"] {
  top: 15%;
  right: -8%;
  width: 32rem;
  height: 24rem;
  background: var(--sky-nebula-two);
}

.scene__nebula[data-nebula="three"] {
  bottom: -40%;
  left: 30%;
  width: 44rem;
  height: 26rem;
  background: var(--sky-nebula-three);
}

@media (min-width: 48rem) and (pointer: fine) {
  .scene__nebula {
    mask-image: none;
    filter: blur(7.5rem);
  }
}

/* --- the stars ---------------------------------------------------------- */

/* Three tiled layers -- far, mid and near -- each with its own tile size so
   the repeat never lines up, its own drift rate and its own twinkle period.
   The parallax *between* them is what gives the sky depth; one layer would
   be wallpaper.
 *
 * The oversize is in the direction of travel only. Each layer drifts by
 * exactly one of its own tiles per cycle, which is what makes the loop
 * seamless, and the element has to extend far enough left-and-up that the
 * edge it is moving away from never enters the viewport. Extending it in
 * all four directions instead would double the rasterised area for nothing.
 */
.scene__stars {
  /* `none` by day: there are no stars in a daytime sky, and pale specks on
     a pale ground read as dust on the screen. */
  display: var(--sky-stars);
  position: absolute;
  top: -325px;
  right: -790px;
  bottom: 0;
  left: 0;
  opacity: 0.85;
}

.scene__stars[data-depth="far"] {
  background-image:
    radial-gradient(circle 0.9px at 8.5% 14.2%, #ffffff71 0 55%, #0000),
    radial-gradient(circle 0.9px at 40.7% 7.2%, #ffffff67 0 55%, #0000),
    radial-gradient(circle 0.9px at 56.2% 21.7%, #fef0c764 0 55%, #0000),
    radial-gradient(circle 0.9px at 91% 21%, #a7f3d04f 0 55%, #0000),
    radial-gradient(circle 0.9px at 11.2% 56.2%, #ffffff5f 0 55%, #0000),
    radial-gradient(circle 0.9px at 36.3% 59.3%, #ffffff52 0 55%, #0000),
    radial-gradient(circle 0.9px at 54.7% 51.6%, #ffffff59 0 55%, #0000),
    radial-gradient(circle 0.9px at 91.4% 50.1%, #bae6fd67 0 55%, #0000),
    radial-gradient(circle 0.9px at 18.4% 81%, #ffffff6a 0 55%, #0000),
    radial-gradient(circle 0.9px at 44.5% 80.8%, #fef0c762 0 55%, #0000),
    radial-gradient(circle 0.9px at 62.3% 74.7%, #fef0c75b 0 55%, #0000),
    radial-gradient(circle 0.9px at 94.2% 82.2%, #fef0c755 0 55%, #0000);
  background-size: 340px 140px;
}

.scene__stars[data-depth="mid"] {
  background-image:
    radial-gradient(circle 1.3px at 12.7% 15.1%, #ffffff73 0 55%, #0000),
    radial-gradient(circle 1.3px at 48.3% 11.8%, #ffffff82 0 55%, #0000),
    radial-gradient(circle 1.3px at 74.7% 23.3%, #ffffff9f 0 55%, #0000),
    radial-gradient(circle 1.3px at 6.2% 41.4%, #ffffff86 0 55%, #0000),
    radial-gradient(circle 1.3px at 54% 43.6%, #bae6fd92 0 55%, #0000),
    radial-gradient(circle 1.3px at 93.4% 50.4%, #ffffff9f 0 55%, #0000),
    radial-gradient(circle 1.3px at 17.2% 77.5%, #ffffffa2 0 55%, #0000),
    radial-gradient(circle 1.3px at 45.3% 89%, #ffffff83 0 55%, #0000),
    radial-gradient(circle 1.3px at 89.2% 92.7%, #a7f3d08d 0 55%, #0000);
  background-size: 520px 215px;
}

.scene__stars[data-depth="near"] {
  background-image:
    radial-gradient(circle 1.9px at 10% 37.1%, #ffffffd2 0 55%, #0000),
    radial-gradient(circle 1.9px at 44.7% 32.8%, #fef0c7ca 0 55%, #0000),
    radial-gradient(circle 1.9px at 82.9% 19.1%, #ffffffac 0 55%, #0000),
    radial-gradient(circle 1.9px at 25.2% 81.6%, #ffffffaf 0 55%, #0000),
    radial-gradient(circle 1.9px at 52.2% 76.8%, #ffffffd2 0 55%, #0000),
    radial-gradient(circle 1.9px at 73.9% 79.8%, #ffffffc8 0 55%, #0000);
  background-size: 790px 325px;
}

/* The bolt, in the open sky. The same six coordinates as the mark -- see
   `logo::CONSTELLATION` and `the_sky_and_the_mark_are_the_same_shape`.
 *
 * Hidden below 64rem, where it would sit behind the copy instead of beside
 * it. The canvas applies the same rule at the same width
 * (`CONSTELLATION_MIN_WIDTH = 1024`).
 */
.scene__constellation {
  display: none;
  position: absolute;

  /* Every number here is the canvas's own. It sizes the bolt
     `min(height * 0.58, width * 0.2)` and centres it at a fraction of the
     canvas the caller chooses, because only the caller knows where its copy
     is -- 0.8 across for the marketing hero, whose copy is hard left. A
     scene's copy is centred, so the open sky is the outer fifth on either
     side and this takes the left one.

     `translate` does the centring and `transform` the parallax, which is
     why they are two properties rather than one: they compose, and the
     parallax below can then be added and removed on its own. */
  top: 44%;
  left: 18%;
  width: min(58cqh, 20cqw);
  aspect-ratio: 1;
  translate: -50% -50%;
  overflow: visible;
}

@container (min-width: 64rem) {
  .scene__constellation {
    display: block;
  }
}

/* A screen that carries the mark itself asks for a quiet sky: the sign-in
   screen plays the animated Flyby, and a second bolt behind it competes
   with the one that moves. `!important`-free because this rule comes after
   the container query above and has the higher specificity. */
.scene[data-constellation="false"] .scene__constellation {
  display: none;
}

/* On a split scene the left column is copy, so the bolt goes up and over --
   the same place the canvas puts it for this screen (`constellationX: 0.3,
   constellationY: 0.26`). The canvas takes those as props for the reason
   its own comment gives: the caller knows where its copy is and the sky
   does not. Here the `aside` is that knowledge. */
.scene[data-split="true"] .scene__constellation {
  top: 26%;
  left: 30%;
}

/* The canvas strokes this at alpha 0.16 in `rgb(167, 243, 208)`, one
   device pixel wide. Both are baked in here: the alpha into the colour, and
   the width into the viewBox's own units -- 0.35 of a hundred across a box
   that is about 280px wide is the same hairline. */
.scene__constellation-line {
  fill: none;
  stroke: var(--sky-line);
  stroke-width: 0.35;
  stroke-linejoin: round;
}

/* Radii in viewBox units, matched to the canvas's 1.8px against its own
   ~280px box. Bigger than that and the bolt stops being a constellation and
   starts being a diagram -- which is exactly what the first pass looked
   like. The glow is the canvas's 7px halo, as a drop shadow rather than a
   second circle per star. */
.scene__constellation-star {
  fill: var(--sky-star);
  opacity: 0.85;
  filter: drop-shadow(0 0 1.5px var(--sky-star-glow));
}

/* The strike point, as in the mark. The canvas does not crown one -- it is
   a hair's difference here, and it is what ties the sky to the logo. */
.scene__constellation-star[data-crown="true"] {
  fill: var(--sky-crown);
}

/* --- the light that moves ---------------------------------------------- */

/* The mark's comet, in the sky. `brand.md`: the sky holds two kinds of
   light, the constellation's fixed stars and one that moves. The group is
   the mark's own 100-unit frame laid over the sky's (see `mark_frame()` in
   `scene.rs`), so the two legs are the mark's `WAKE_FAR` and `WAKE_NEAR`
   verbatim, they pass behind the bolt through a mask that is `bolt_path()`,
   and they resolve into the mark's `ION`.

   At rest -- and under reduced motion -- the whole wake is drawn, faintly,
   as it is on the mark: a still sky is the mark, large. The flyby below
   turns each leg into a dash that sweeps along it. */
.scene__wake {
  fill: none;
  stroke-linecap: round;
}

/* The glow: wide, soft, a long tail. The near leg is thicker and brighter
   than the far one, the way a real tail grows toward its nucleus -- that is
   what makes the over-under read as depth rather than a line with a gap. */
.scene__wake[data-part="glow"] {
  --comet-tail: 22;
  stroke: var(--sky-wake-glow);
  stroke-width: 0.9;
  opacity: 0.2;
}

.scene__wake[data-part="glow"][data-leg="near"] {
  stroke-width: 1.25;
  opacity: 0.28;
}

/* The core: a hair of starlight with a short tail, over the glow. */
.scene__wake[data-part="core"] {
  --comet-tail: 8;
  stroke: var(--sky-wake-core);
  stroke-width: 0.4;
  opacity: 0.75;
  filter: drop-shadow(0 0 1px var(--sky-star-glow));
}

.scene__wake[data-part="core"][data-leg="near"] {
  stroke-width: 0.55;
  opacity: 0.9;
}

/* Where the wake resolves. It holds its place between flybys, faintly,
   and flares as each one arrives. Not a constellation star: it burns
   outside the bolt, and it is the one light here that is not white. */
.scene__ion {
  fill: var(--sky-ion);
  opacity: 0.45;
  filter: drop-shadow(0 0 3px var(--sky-wake-glow));
}

/* --- what moves --------------------------------------------------------- */

/* One block, so that reduced motion removes all of it at once -- the drift,
   the twinkle and the pointer parallax together. A sky that stops drifting
   but still lurches toward the pointer is the worst of both. */
@media (prefers-reduced-motion: no-preference) {
  .scene__nebula[data-nebula="one"] {
    animation: nebula-drift 32s ease-in-out infinite;
  }

  .scene__nebula[data-nebula="two"] {
    animation: nebula-drift 44s ease-in-out -12s infinite reverse;
  }

  .scene__nebula[data-nebula="three"] {
    animation: nebula-drift 38s ease-in-out -24s infinite;
  }

  /* `translate` rather than `transform`, on every layer below: the drift
     animations own `transform`, and the two properties compose instead of
     one overwriting the other. Each layer takes a fraction of the pointer
     offset -- the far sky barely moves, the near sky moves the full 3px --
     which is the whole of the depth effect. */
  .scene__nebula {
    translate: calc(var(--parallax-x) * 0.5) calc(var(--parallax-y) * 0.5);
    transition: translate 3200ms cubic-bezier(0.55, 0, 0.25, 1);
  }

  /* Long, and deliberately longer than a hand moves: the sky lags the
     pointer like something far away. The curve is slow at *both* ends --
     the sky takes a moment to notice the pointer, drifts, and settles --
     so a pointer entering at the edge is not jumped at, and a pointer
     that stops is not chased to a halt. `scene.rs` cuts the canvas's
     range to a fifth for the same reason. */
  .scene__stars {
    transition: translate 2500ms cubic-bezier(0.55, 0, 0.25, 1);
  }

  .scene__stars[data-depth="far"] {
    translate: calc(var(--parallax-x) * 0.35) calc(var(--parallax-y) * 0.35);
    animation:
      star-drift-far 150s linear infinite,
      star-twinkle 9s ease-in-out infinite;
  }

  .scene__stars[data-depth="mid"] {
    translate: calc(var(--parallax-x) * 0.65) calc(var(--parallax-y) * 0.65);
    animation:
      star-drift-mid 130s linear infinite,
      star-twinkle 7s ease-in-out -2s infinite;
  }

  .scene__stars[data-depth="near"] {
    translate: var(--parallax-x) var(--parallax-y);
    animation:
      star-drift-near 110s linear infinite,
      star-twinkle 5.5s ease-in-out -4s infinite;
  }

  /* The constellation sits at the mid depth, as it does on the canvas
     (`CONSTELLATION_DEPTH = 0.65`), and breathes on a period of its own so
     it never pulses in step with the layer behind it. */
  .scene__constellation {
    transform: translate(
      calc(var(--parallax-x) * 0.65),
      calc(var(--parallax-y) * 0.65)
    );
    transition: transform 2500ms cubic-bezier(0.55, 0, 0.25, 1);
    animation: constellation-breathe 16s ease-in-out infinite;
  }

  /* Each fixed star twinkles on its own phase, as on the canvas, where
     every star carries a `twinklePhase` of its own. Six delays for six
     stars: with one shared phase the bolt blinks as a unit, which is a
     signal, not a sky. Counted by type, so the polygon before them does
     not shift the sequence. */
  .scene__constellation-star {
    animation: constellation-twinkle 4.6s ease-in-out infinite;
  }

  .scene__constellation-star:nth-of-type(2) {
    animation-delay: -0.9s;
  }

  .scene__constellation-star:nth-of-type(3) {
    animation-delay: -2.7s;
  }

  .scene__constellation-star:nth-of-type(4) {
    animation-delay: -1.6s;
  }

  .scene__constellation-star:nth-of-type(5) {
    animation-delay: -3.8s;
  }

  .scene__constellation-star:nth-of-type(6) {
    animation-delay: -0.4s;
  }

  /* The flyby. Each leg declares its length as `pathLength` (47 and 42,
     the arcs' real lengths, so one offset crosses both at one speed), and
     the dash is the comet: `--comet-tail` long, then a gap longer than the whole
     journey so it can never wrap. The offset sweeps the dash's head from
     the far leg's start (0), across the hand-off at 47, to the ion at 89,
     then 22 further so the tail collapses into the ion instead of hanging
     there -- that is the wake *resolving*, which is the brand's word for
     it.

     The head is one registered custom property, `--comet-head`, and the
     keyframes animate *that*; each leg and each part derives its own
     offset from it. Not `calc(var(--comet-tail) - n)` in the keyframes
     themselves: Chrome treats a calc() with a var() inside as
     non-interpolable and snaps it at 50%, so the comet never moved. A
     registered `<number>` interpolates, which is what `@property` below
     is for.

     One flyby every 18s, taking about 3.6s, with the first one arriving
     five seconds after the sky does rather than eighteen. */
  .scene__wake {
    stroke-dasharray: var(--comet-tail) 200;
    stroke-dashoffset: calc(var(--comet-tail) - var(--comet-head));
    animation: comet-flyby 18s cubic-bezier(0.4, 0, 0.6, 1) -13s infinite;
  }

  .scene__wake[data-leg="near"] {
    stroke-dashoffset: calc(var(--comet-tail) + 47 - var(--comet-head));
  }

  .scene__ion {
    animation: ion-burn 18s ease-out -13s infinite;
  }
}

/* Each layer travels exactly one of its own tiles, so the frame it lands on
   is identical to the one it started from and the loop is invisible.
   Leftwards and down, at the canvas's ratio (DRIFT_X -3.2, DRIFT_Y 1.1). */
@keyframes star-drift-far {
  to {
    transform: translate3d(-340px, 140px, 0);
  }
}

@keyframes star-drift-mid {
  to {
    transform: translate3d(-520px, 215px, 0);
  }
}

@keyframes star-drift-near {
  to {
    transform: translate3d(-790px, 325px, 0);
  }
}

@keyframes star-twinkle {
  0%,
  100% {
    opacity: 0.62;
  }

  50% {
    opacity: 1;
  }
}

@keyframes constellation-breathe {
  0%,
  100% {
    opacity: 0.75;
  }

  50% {
    opacity: 1;
  }
}

/* Carried across verbatim from the existing console's globals.css, so the
   two skies drift the same way while both exist. */
@keyframes nebula-drift {
  0%,
  100% {
    transform: translate3d(0, 0, 0) scale(1);
  }

  50% {
    transform: translate3d(3rem, -1.5rem, 0) scale(1.08);
  }
}

@keyframes constellation-twinkle {
  0%,
  100% {
    opacity: 0.55;
  }

  50% {
    opacity: 1;
  }
}

/* The head runs 0 -> 111 over the first fifth of the period: 47 along the
   far leg, 42 along the near one, 22 more for the tail to catch up. */
@property --comet-head {
  syntax: "<number>";
  inherits: false;
  initial-value: 0;
}

@keyframes comet-flyby {
  0% {
    --comet-head: 0;
  }

  20%,
  100% {
    --comet-head: 111;
  }
}

/* The head reaches the ion at h = 89, which is 16% of the period; the
   flare peaks a beat after and settles back over the next few seconds. */
@keyframes ion-burn {
  0%,
  14% {
    opacity: 0.45;
  }

  18% {
    opacity: 1;
  }

  34%,
  100% {
    opacity: 0.45;
  }
}

/* --- the horizon -------------------------------------------------------- */

/* The aurora that closes a scene: an emerald glow rising from the bottom
   edge, with a hairline over it. */
.scene__aurora {
  position: absolute;
  inset-inline: 0;
  bottom: 0;
  height: 12rem;
  pointer-events: none;
  background-image: radial-gradient(
    70% 120% at 50% 100%,
    var(--sky-horizon),
    transparent 70%
  );
}

.scene__hairline {
  position: absolute;
  inset-inline: 2.5rem;
  bottom: 0;
  height: 1px;
  pointer-events: none;
  background-image: linear-gradient(
    to right,
    transparent,
    var(--sky-hairline),
    transparent
  );
}

/* --- the frame ---------------------------------------------------------- */

/* Both of these are the page frame `container.rs` names, spelled with the
   same tokens rather than with numbers of their own -- a scene is a page,
   and it should not be a page of a different width. */
.scene__header {
  position: relative;
  display: flex;
  align-items: center;
  width: 100%;
  max-width: var(--width-page);
  margin: 0 auto;
  /* Clear of the status bar and the notch under `viewport-fit=cover`, as
     the top bar in assets/components/chrome.css already is. `env()` is 0 wherever there
     is nothing to clear, so this is the old padding everywhere else. */
  padding: calc(var(--space-xl) + env(safe-area-inset-top, 0px)) var(--gutter)
    var(--space-xl);
}

.scene__body {
  position: relative;
  display: flex;
  flex: 1;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--gap-block);
  width: 100%;
  max-width: var(--width-content);
  margin: 0 auto;
  /* And clear of the home indicator at the bottom, for the same reason. */
  padding: 0 var(--gutter)
    calc(var(--space-3xl) + env(safe-area-inset-bottom, 0px));
  text-align: center;
}

/* A column that opens with a way back is a page the reader came to from
   somewhere -- the profile, one of their cars -- and it reads top down. A
   short one held in the middle of the sky left its back link floating
   halfway down the screen. Centring stays for the choices themselves. */
.scene__body:has(> .scene__content > .chooser__column > .back-to:first-child) {
  justify-content: flex-start;
}

/* Two columns once there is room for them: what the screen is for on the
   left, the card it renders into on the right. Below that width they stack
   in document order, which is why the aside comes first in the markup --
   a reader who meets the form before the pitch has to go back for it.

   The card's column is bounded and the copy's is not, so the copy takes the
   slack as the viewport grows rather than the form stretching into a shape
   no form wants to be. */
.scene[data-width="page"] .scene__body {
  max-width: var(--width-page);
}

.scene[data-split="true"] .scene__body {
  max-width: var(--width-page);
  align-items: stretch;
  text-align: left;
}

.scene__aside {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--gap-stack);
}

.scene__content {
  display: flex;
  flex-direction: column;
  gap: var(--gap-block);
  width: 100%;
}

/* One column: everything is centred, as an item and not only as text.
   A flex column's default is `stretch`, and it is silent in the worst way
   -- a paragraph with a `max-width` sits against the left edge of a
   centred page, and a pill-shaped reference stretches to the full width
   of the column. A split scene keeps `stretch`, because the card in its
   second column is meant to fill it. */
.scene:not([data-split="true"]) .scene__content {
  align-items: center;
}

/* A card on a single-column scene -- the sign-in card -- is a fixed
   width rather than the width of its widest field: 28rem, the card in
   the design, and the fields inside it fill it rather than stopping at
   `.field`'s own 22rem, which would leave a margin inside the card. */
.scene:not([data-split="true"]) .scene__content > .panel {
  width: 100%;
  max-width: 28rem;
}

.scene:not([data-split="true"]) .scene__content > .panel .field {
  max-width: none;
}

@media (min-width: 64rem) {
  .scene[data-split="true"] .scene__body {
    display: grid;
    grid-template-columns: 1fr minmax(0, 26rem);
    align-items: center;
    gap: var(--gap-section);
  }
}

/* A link in the scene's copy -- the support address. Not the browser's
   default blue-then-purple, which on this ground reads as a mistake, and
   not the button treatment either: a reader who has not tried the buttons
   above should try those first, so this stays a line of text. The lockup is
   excluded because it is a link that is not copy, and so is a chooser row,
   which is a card the whole of which is the link -- and a directory tile,
   for the same reason: underlining it underlined its whole contents. A car
   in "Your cars" is the same kind of link. */
.scene__body a:not(.button):not(.logo):not(.chooser__option):not(.chooser__create):not(.mk-directory__link):not(.vehicle-row) {
  color: var(--foreground);
  text-decoration: underline;
  text-underline-offset: 4px;
}

/* --- eyebrow ------------------------------------------------------------ */

/* Wraps, and centres when it does. 0.3em of tracking on uppercase mono is
   roughly twice the width of the same words set normally, so a status in
   words -- "SOMETHING WENT WRONG" -- is close to 300px before any padding.
   Without `flex-wrap` it would push the scene wider than the phone it is
   on. The trailing tracking on the last letter is removed with a negative
   margin, because otherwise the text reads as off-centre by half a space. */
.eyebrow {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: var(--gap-stack);
  margin: 0;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  line-height: var(--text-xs-leading);
  letter-spacing: 0.3em;
  margin-inline-end: -0.3em;
  text-transform: uppercase;
  max-width: 100%;
}

.eyebrow[data-tone="fault"] {
  color: var(--scene-accent-warm);
}

.eyebrow[data-tone="quiet"] {
  color: var(--scene-accent-cool);
}

.eyebrow__dot {
  position: relative;
  display: inline-flex;
  width: 0.5rem;
  height: 0.5rem;
  flex-shrink: 0;
}

.eyebrow__dot::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: var(--radius-pill);
  background: currentcolor;
}

/* The one place a ping is used, and it is not an accident.
   `design.md` §1.3 rules a radar ring out for a *live-status dot*, which
   breathes at 2.6s and marks live rather than urgency. This is not a
   live-status dot: it is the fault marker on a page that exists because
   something broke, where urgency is the message. A `quiet` eyebrow -- a
   404 -- gets no ring at all, which is the same rule working the other
   way: nothing is wrong, so nothing pulses. */
@media (prefers-reduced-motion: no-preference) {
  .eyebrow[data-tone="fault"] .eyebrow__dot::before {
    content: "";
    position: absolute;
    inset: 0;
    border-radius: var(--radius-pill);
    background: currentcolor;
    opacity: 0.6;
    animation: eyebrow-ping 1s cubic-bezier(0, 0, 0.2, 1) infinite;
  }
}

@keyframes eyebrow-ping {
  75%,
  100% {
    transform: scale(2);
    opacity: 0;
  }
}

/* --- error scene -------------------------------------------------------- */

/* Mono because it is data -- the same status code the API would have
   returned, set in the face the docs set one in. */
.error-scene__code {
  font-family: var(--font-mono);
  font-size: 3.75rem;
  font-weight: var(--weight-medium);
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
  line-height: 1;
}

.error-scene__heading {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2xl);
  margin: 0;
}

.error-scene__title {
  font-family: var(--font-heading);
  font-size: var(--text-2xl);
  font-weight: var(--weight-semibold);
  letter-spacing: -0.01em;
  text-wrap: balance;
  max-width: 42rem;
}

.error-scene__accent {
  background-image: linear-gradient(
    to right,
    var(--brand-from),
    var(--brand-to)
  );
  background-clip: text;
  color: transparent;
}

.error-scene__description {
  max-width: 36rem;
  text-wrap: balance;
}

/* Full-bleed on a phone, side by side from `sm` -- the hero's rule for a
   pair of calls to action. */
.error-scene__actions {
  display: flex;
  flex-direction: column;
  gap: var(--gap-stack);
  width: 100%;
}

/* Below the actions, never above: the reference is what you quote once
   retrying has not helped, so it must not stand between the reader and the
   button they came for. */
.error-scene__reference {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
  padding: var(--space-xs) var(--space-xl);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-pill);
  background: var(--raised);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
}

/* The description is the one thing that should not stretch to the full
   48rem: a measure much past 60 characters stops being readable, and on a
   scene there is nothing else on the line to break it up. */
.error-scene__description {
  max-width: 34rem;
  width: 100%;
}

@media (min-width: 40rem) {
  .error-scene__code {
    font-size: 6rem;
  }

  .error-scene__title {
    font-size: var(--text-4xl);
  }

  .error-scene__actions {
    flex-direction: row;
    justify-content: center;
    width: auto;
  }
}

/* Syntax highlighting, for the classes `libs/site-content`'s highlighter
 * emits at build time. syntect names each token by its scope, one class per
 * scope segment (`keyword.control.rust` is `hl-keyword hl-control hl-rust`),
 * so a rule on the first segment covers every language.
 *
 * Its own sheet because nothing in this crate renders these classes from a
 * `class:` literal: theme.rs's class cross-reference would call every rule
 * here unused. The sheet may hold only `hl-` selectors, and every other rule
 * (tokens only, no literal colour, closed blocks) still applies to it.
 *
 * Existing tokens, not a syntax palette: both themes already define them, so
 * highlighted code follows the theme with no new tokens to keep in step. */

.hl-comment {
  color: var(--muted-foreground);
  font-style: italic;
}

.hl-keyword,
.hl-storage {
  color: var(--primary);
}

.hl-string {
  color: var(--status-ok);
}

.hl-constant {
  color: var(--status-warn);
}

.hl-entity,
.hl-support {
  color: var(--brand-ion);
}

.hl-invalid {
  color: var(--destructive);
}
/* The classes the site itself renders, as opposed to the ones libs/ui does.
 *
 * They live here rather than in libs/ui/assets/components/ because that
 * sheet is cross-referenced against the components in that crate, in both
 * directions: a rule nothing there renders fails its tests. Prose and the
 * docs layout are the site's, so the site styles them.
 *
 * Every value still resolves through libs/ui's token layer, which is
 * concatenated before this sheet. */

/* The page's own ground and ink. Left unset, the text followed the dark
   palette while the ground was whatever the browser chose -- white, as far
   as a contrast check is concerned, so every dark page measured light ink
   on white.

   And its face. libs/ui's components each name their own, so text they do
   not wrap -- prose, the docs navigation, the blog -- fell through to the
   browser default, which is Times, and Inter was never even requested. */
body {
  font-family: var(--font-sans);
  font-size: var(--text-base);
  line-height: var(--text-base-leading);
  color: var(--foreground);
  background: var(--background);
}

/* How far under the header a page's first line sits.
 *
 * One value, because there used to be three: 2rem here, 1rem on
 * .docs-layout and 1.5rem on .blog/.post, so walking /security -> /docs ->
 * /blog moved the content up and down by half a heading each time. The
 * bottom is deliberately larger than the top -- a page should end with air
 * rather than against the footer -- and that asymmetry is now a token pair
 * rather than arithmetic at one of the three call sites. */
.prose-page,
.docs-layout,
.blog,
.post {
  --page-start: var(--space-4xl);
  --page-end: var(--space-6xl);
}

/* The frame a prose page (and the 404) sits in: centred, with the side
   gutter. Without it the column started at x=0 against the window edge.
   `padding-inline`, never the shorthand -- see .docs-layout. */
.prose-page {
  box-sizing: content-box;
  max-width: var(--width-prose);
  margin-inline: auto;
  padding-block: var(--page-start) var(--page-end);
  padding-inline: var(--gutter);
}

/* Reading text, not interface text.
 *
 * `--text-base-leading` is 1.5, which is the console's leading: right for a
 * table row, tight for a 42rem paragraph a reader stays inside for a
 * screenful. The prose column sets its own, and everything that inherits
 * (paragraphs, list items, callout bodies) follows without a second rule. */
.prose {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--gap-block);
  max-width: var(--width-prose);
  color: var(--foreground);
  line-height: 1.65;
}

.prose > * {
  margin: 0;
}

.prose h1 {
  font-family: var(--font-heading);
  font-size: var(--text-3xl);
  line-height: var(--text-3xl-leading);
  font-weight: var(--weight-semibold);
}

.prose h2 {
  margin-top: var(--space-5xl);
  font-family: var(--font-heading);
  font-size: var(--text-xl);
  line-height: var(--text-xl-leading);
  font-weight: var(--weight-semibold);
}

.prose h3 {
  margin-top: var(--space-2xl);
  font-size: var(--text-lg);
  line-height: var(--text-lg-leading);
  font-weight: var(--weight-semibold);
}

.prose h4,
.prose h5,
.prose h6 {
  font-size: var(--text-base);
  line-height: var(--text-base-leading);
  font-weight: var(--weight-semibold);
}

/* A heading belongs to what comes after it. The grid gap puts the same
   1.5rem on both sides, which leaves every heading floating equidistant
   between two sections; this pulls its own text back up to it. */
.prose :is(h2, h3, h4, h5, h6) + * {
  margin-top: calc(var(--gap-block) * -1 + var(--space-md));
}

.prose p,
.prose li {
  font-size: var(--text-base);
}

.prose ul,
.prose ol {
  display: grid;
  gap: var(--space-xs);
  padding-left: var(--space-lg);
}

/* One inline link, everywhere prose-like text appears.
 *
 * There used to be no rule at all outside .prose, so a link written
 * straight into a marketing section -- the homepage's "New to OCPP?" line,
 * the closing section's mail link, ten others across products, devices and
 * the 404 -- rendered in the browser's own unvisited blue.
 *
 * `a:not([class])` rather than a list of call sites: an unclassed anchor
 * IS the bug, so the selector is the rule. Anything deliberate (a button, a
 * nav item, a card) carries a class and is untouched, and a link added
 * tomorrow cannot come out blue.
 *
 * And .prose a itself set only a colour: no hover, no focus ring, no
 * explicit text-decoration, which left keyboard users with whatever the UA
 * drew and made the underline hostage to any later
 * `a { text-decoration: none }`.
 *
 * The underline is drawn at 40% of the ink and solidifies on hover, so the
 * link reads as a link at rest without striping a paragraph. */
.prose a,
a:not([class]) {
  color: var(--primary);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-decoration-color: color-mix(in oklch, currentColor 40%, transparent);
  text-underline-offset: 0.2em;
  border-radius: var(--radius-sm);
}

@media (hover: hover) {
  .prose a:hover,
  a:not([class]):hover {
    text-decoration-color: currentColor;
  }
}

/* Every link the site draws answers the keyboard, including the ones that
   are deliberately not primary-coloured. */
.prose a:focus-visible,
a:not([class]):focus-visible,
.docs-article__edit:focus-visible,
.docs-neighbours__link:focus-visible {
  outline: 2px solid var(--ring);
  outline-offset: 2px;
}

.prose blockquote {
  display: grid;
  gap: var(--space-lg);
  margin: 0;
  padding-left: var(--space-lg);
  border-left: 2px solid var(--hairline);
  color: var(--muted-foreground);
}

.prose blockquote > * {
  margin: 0;
}

.prose hr {
  border: 0;
  border-top: 1px solid var(--hairline);
}

.prose img {
  max-width: 100%;
  height: auto;
  border-radius: var(--radius-md);
}

/* An API path in a sentence -- `POST /projects/{projectId}/webhooks/...` --
   is one unbreakable word wider than a phone. It may break anywhere: a
   wrapped path still reads, a page that scrolls sideways does not. */
.prose__code {
  overflow-wrap: anywhere;
  padding: 0.1em 0.35em;
  border-radius: var(--radius-sm);
  background: var(--muted);
  font-family: var(--font-mono);
  font-size: 0.9em;
}

/* A table may be wider than the prose column; the page may not. */
.prose__table {
  overflow-x: auto;
  max-width: 100%;
}

.prose__table table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--text-sm);
  line-height: var(--text-sm-leading);
}

.prose__table caption {
  padding-bottom: var(--space-xs);
  text-align: left;
  font-weight: var(--weight-semibold);
}

/* 0.25rem of vertical padding is a console row, where the density is the
   point. A documentation table is read a cell at a time. */
.prose__table th,
.prose__table td {
  padding: var(--space-lg) var(--space-md);
  border-bottom: 1px solid var(--hairline);
  text-align: left;
  vertical-align: top;
}

.prose__table th {
  font-weight: var(--weight-semibold);
}

.prose__table [data-align="center"] {
  text-align: center;
}

.prose__table [data-align="right"] {
  text-align: right;
}

.event-reference {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-xl);
}

.event-reference__event {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-sm);
}

.event-reference__availability {
  font-size: var(--text-xs);
  line-height: var(--text-xs-leading);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted-foreground);
}

/* One column on a phone; sidebar, article and outline on a wide screen.
 *
 * `padding-inline`, never a `padding` shorthand: the side gutter is the
 * thing that must survive every change here, and the shorthand is how it
 * gets zeroed by someone adding vertical space. */
.docs-layout {
  display: grid;
  /* Declared even for one column: an implicit track is `auto`, which grows
     to the widest unbreakable line in the article, and on a phone that made
     the whole page scroll sideways. */
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-2xl);
  align-items: start;
  padding-block: var(--page-start) var(--page-end);
  padding-inline: var(--gutter);
  max-width: var(--width-page);
  margin-inline: auto;
}

.docs-article {
  display: grid;
  /* See .docs-layout: an implicit track grows to the widest line. */
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-xl);
  min-width: 0;
}

.docs-article__footer {
  font-size: var(--text-sm);
  line-height: var(--text-sm-leading);
}

/* Deliberately quieter than an inline link: it is a maintenance affordance at
   the foot of the page, not part of the reading. It still underlines and
   still answers the keyboard, which is the part that was missing. */
.docs-article__edit {
  color: var(--muted-foreground);
  text-decoration: underline;
  text-decoration-color: color-mix(in oklch, currentColor 40%, transparent);
  text-underline-offset: 0.2em;
  border-radius: var(--radius-sm);
}

@media (hover: hover) {
  .docs-article__edit:hover {
    color: var(--foreground);
    text-decoration-color: currentColor;
  }
}

.docs-neighbours {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-md);
  padding-top: var(--space-lg);
  border-top: 1px solid var(--hairline);
}

.docs-neighbours__link {
  display: grid;
  gap: var(--space-2xs);
  padding: var(--space-md);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-md);
  color: var(--foreground);
  text-decoration: none;
}

@media (hover: hover) {
  .docs-neighbours__link:hover {
    border-color: var(--border);
    background: var(--raised);
  }
}

.docs-neighbours__link[data-direction="next"] {
  text-align: right;
}

.docs-neighbours__label {
  font-size: var(--text-xs);
  line-height: var(--text-xs-leading);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted-foreground);
}

@media (min-width: 48rem) {
  .docs-neighbours {
    grid-auto-flow: column;
    grid-auto-columns: 1fr;
  }
}

@media (min-width: 64rem) {
  .docs-layout {
    grid-template-columns: 16rem minmax(0, 1fr);
  }
}

@media (min-width: 80rem) {
  .docs-layout {
    grid-template-columns: 16rem minmax(0, 1fr) 14rem;
  }
}

@media (max-width: 79.99rem) {
  /* No room for a third column: the outline would squeeze the prose, and
   * the headings are right there in the page anyway. */
  .docs-outline {
    display: none;
  }
}

.docs-sidebar {
  display: grid;
  gap: var(--space-lg);
  align-content: start;
}

@media (max-width: 63.99rem) {
  /* Stacked above the article rather than beside it: keep it to the two
     controls it is, so the page's title is still on the first screen. */
  .docs-sidebar {
    gap: var(--space-md);
  }
}

@media (min-width: 64rem) {
  /* The sidebar and the outline stay with the reader, each scrolling on its
   * own when it is taller than the viewport. Without this the docs
   * navigation is fifty links long and scrolls off the top of a guide,
   * which leaves the reader with no way back but the browser's own. */
  .docs-sidebar,
  .docs-outline {
    position: sticky;
    top: var(--space-xl);
    max-height: calc(100vh - var(--space-2xl));
    overflow-y: auto;
    overscroll-behavior: contain;
  }
}

/* --- a docs page's header ----------------------------------------------
 *
 * The page draws its own title (the Markdown's `#` is taken out of the
 * body), so the lede and the page's facts can sit under it: the reader
 * learns what the page is for, how long it takes, and whether they can try
 * it without hardware, before the first paragraph. */
.docs-header {
  display: grid;
  gap: var(--space-lg);
  padding-bottom: var(--space-xl);
  border-bottom: 1px solid var(--hairline);
}

.docs-crumbs {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-sm);
  font-size: var(--text-sm);
  line-height: var(--text-sm-leading);
  color: var(--muted-foreground);
}

.docs-crumbs__link {
  color: var(--muted-foreground);
  text-decoration: none;
  border-radius: var(--radius-sm);
}

@media (hover: hover) {
  .docs-crumbs__link:hover {
    color: var(--foreground);
    text-decoration: underline;
  }
}

.docs-header__title {
  margin: 0;
  font-family: var(--font-heading);
  font-size: var(--text-4xl);
  line-height: var(--text-4xl-leading);
  font-weight: var(--weight-semibold);
  letter-spacing: -0.02em;
  text-wrap: balance;
}

.docs-header__lede {
  margin: 0;
  max-width: var(--width-prose);
  font-size: var(--text-lg);
  line-height: var(--text-lg-leading);
  color: var(--muted-foreground);
  text-wrap: pretty;
}

.docs-header__facts {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-md) var(--space-xl);
  font-size: var(--text-sm);
  line-height: var(--text-sm-leading);
  color: var(--muted-foreground);
}

.docs-header__fact {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
}

.docs-header__sandbox {
  display: inline-flex;
  align-items: center;
  gap: var(--space-sm);
  min-height: var(--space-3xl);
  padding: 0 var(--space-lg);
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  color: var(--foreground);
  text-decoration: none;
}

.docs-header__sandbox-icon {
  display: flex;
  color: var(--primary);
}

@media (hover: hover) {
  .docs-header__sandbox:hover {
    background: var(--raised);
  }
}

.docs-header__edit {
  margin-left: auto;
  color: var(--muted-foreground);
  text-decoration: underline;
  text-decoration-color: color-mix(in oklch, currentColor 40%, transparent);
  text-underline-offset: 0.2em;
  border-radius: var(--radius-sm);
}

@media (hover: hover) {
  .docs-header__edit:hover {
    color: var(--foreground);
    text-decoration-color: currentColor;
  }
}

.docs-crumbs__link:focus-visible,
.docs-header__sandbox:focus-visible,
.docs-header__edit:focus-visible,
.docs-endpoints__card:focus-visible,
.docs-outline-sheet__summary:focus-visible,
.docs-card__link:focus-visible,
.docs-start__action:focus-visible,
.docs-start__link:focus-visible,
.docs-start__page:focus-visible,
.docs-home__aside:focus-visible {
  outline: 2px solid var(--ring);
  outline-offset: 2px;
}

/* "On this page" on a screen too narrow for the outline column: a sheet
   that opens with no script, at the top of the article. */
.docs-outline-sheet {
  border: 1px solid var(--hairline);
  border-radius: var(--radius-lg);
}

.docs-outline-sheet__summary {
  display: flex;
  align-items: center;
  min-height: var(--touch-target);
  padding: 0 var(--space-lg);
  font-weight: var(--weight-medium);
  cursor: pointer;
}

.docs-outline-sheet__body {
  padding: 0 var(--space-lg) var(--space-lg);
}

@media (min-width: 80rem) {
  .docs-outline-sheet {
    display: none;
  }
}

/* --- links into the API reference, at the foot of a page --------------- */
.docs-endpoints {
  display: grid;
  gap: var(--space-lg);
  margin-top: var(--space-3xl);
}

.docs-endpoints__title {
  margin: 0;
  font-size: var(--text-xs);
  line-height: var(--text-xs-leading);
  font-weight: var(--weight-semibold);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted-foreground);
}

.docs-endpoints__list {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(16rem, 100%), 1fr));
  gap: var(--space-md);
  margin: 0;
  padding: 0;
  list-style: none;
}

.docs-endpoints__card {
  display: grid;
  gap: var(--space-2xs);
  height: 100%;
  box-sizing: border-box;
  padding: var(--space-lg) var(--space-xl);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-lg);
  color: var(--foreground);
  text-decoration: none;
}

@media (hover: hover) {
  .docs-endpoints__card:hover {
    border-color: var(--border);
    background: var(--raised);
  }
}

.docs-endpoints__resource {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  line-height: var(--text-xs-leading);
  color: var(--muted-foreground);
}

.docs-endpoints__label {
  font-weight: var(--weight-medium);
}

/* --- the docs home -------------------------------------------------------
 *
 * The map rather than a page in it: no sidebar, full width, the reader's
 * starting points first. */
.docs-home {
  --page-start: var(--space-5xl);
  --page-end: var(--space-6xl);
  display: grid;
  gap: var(--space-5xl);
  max-width: var(--width-page);
  margin-inline: auto;
  padding-block: var(--page-start) var(--page-end);
  padding-inline: var(--gutter);
}

.docs-home__hero {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-4xl);
  align-items: center;
}

@media (min-width: 64rem) {
  .docs-home__hero {
    grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr);
  }
}

.docs-home__intro {
  display: grid;
  gap: var(--space-xl);
}

.docs-home__eyebrow {
  margin: 0;
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  line-height: var(--text-sm-leading);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--primary);
}

.docs-home__title {
  margin: 0;
  font-family: var(--font-heading);
  font-size: var(--text-5xl);
  line-height: var(--text-5xl-leading);
  font-weight: var(--weight-semibold);
  letter-spacing: -0.025em;
  text-wrap: balance;
}

.docs-home__lede,
.docs-home__sub {
  margin: 0;
  max-width: var(--width-prose);
  font-size: var(--text-lg);
  line-height: var(--text-lg-leading);
  color: var(--muted-foreground);
  text-wrap: pretty;
}

.docs-home__sub {
  font-size: var(--text-base);
  line-height: var(--text-base-leading);
}

.docs-home__tour {
  display: grid;
  border: 1px solid var(--hairline);
  border-radius: var(--radius-lg);
  background: var(--raised);
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  line-height: var(--text-sm-leading);
}

.docs-home__tour-host {
  margin: 0;
  padding: var(--space-md) var(--space-xl);
  border-bottom: 1px solid var(--hairline);
  font-size: var(--text-xs);
  line-height: var(--text-xs-leading);
  color: var(--muted-foreground);
}

.docs-home__tour-list {
  display: grid;
  gap: var(--space-lg);
  margin: 0;
  padding: var(--space-xl);
  list-style: none;
}

.docs-home__tour-item {
  display: grid;
  gap: var(--space-2xs);
}

.docs-home__tour-what {
  color: var(--muted-foreground);
}

.docs-home__tour-call {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-md);
  overflow-wrap: anywhere;
}

.docs-home__tour-method {
  font-weight: var(--weight-semibold);
  color: var(--primary);
}

.docs-home__tour-method[data-method="get"] {
  color: var(--status-busy);
}

.docs-home__tour-method[data-method="put"] {
  color: var(--status-warn);
}

.docs-home__band {
  display: grid;
  gap: var(--space-2xl);
}

.docs-home__band[data-tint="true"] {
  padding: var(--space-4xl) var(--gutter);
  margin-inline: calc(var(--gutter) * -1);
  border-block: 1px solid var(--hairline);
  background: var(--raised);
}

.docs-home__heading {
  margin: 0;
  font-family: var(--font-heading);
  font-size: var(--text-2xl);
  line-height: var(--text-2xl-leading);
  font-weight: var(--weight-semibold);
  letter-spacing: -0.015em;
}

.docs-home__band[data-tint="true"] > .docs-home__heading + .docs-home__sub {
  margin-top: calc(var(--space-2xl) * -1 + var(--space-xs));
}

.docs-home__starts,
.docs-home__cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(19rem, 100%), 1fr));
  gap: var(--space-xl);
}

.docs-start,
.docs-card {
  display: grid;
  align-content: start;
  gap: var(--space-lg);
  padding: var(--space-2xl);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-lg);
  background: var(--background);
}

.docs-start[data-lead="true"] {
  border-color: var(--primary);
  box-shadow: 0 0 0 3px color-mix(in oklch, var(--primary) 15%, transparent);
}

.docs-start__tag {
  justify-self: start;
  margin: 0;
  padding: var(--space-2xs) var(--space-md);
  border-radius: var(--radius-pill);
  background: color-mix(in oklch, var(--primary) 12%, transparent);
  color: var(--primary);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  line-height: var(--text-xs-leading);
}

.docs-start__title,
.docs-card__title {
  margin: 0;
  font-family: var(--font-heading);
  font-size: var(--text-xl);
  line-height: var(--text-xl-leading);
  font-weight: var(--weight-semibold);
}

.docs-start__summary,
.docs-card__summary,
.docs-start__meta {
  margin: 0;
  color: var(--muted-foreground);
}

.docs-start__meta {
  font-size: var(--text-sm);
  line-height: var(--text-sm-leading);
}

.docs-start__steps {
  display: grid;
  gap: var(--space-sm);
  margin: 0;
  padding-left: var(--space-xl);
}

.docs-start__brands {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-sm);
  margin: 0;
  padding: 0;
  list-style: none;
}

.docs-start__brand {
  padding: var(--space-xs) var(--space-lg);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-pill);
  font-size: var(--text-sm);
  line-height: var(--text-sm-leading);
}

.docs-start__brand[data-more="true"] {
  border-style: dashed;
  color: var(--muted-foreground);
}

.docs-start__links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xl);
}

.docs-start__link,
.docs-start__page {
  font-weight: var(--weight-medium);
  color: var(--primary);
  text-decoration: none;
  border-radius: var(--radius-sm);
}

.docs-start__pages {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-sm) var(--space-xl);
  margin: 0;
  padding: 0;
  list-style: none;
}

.docs-start__page {
  font-weight: var(--weight-regular);
  color: var(--foreground);
}

@media (hover: hover) {
  .docs-start__link:hover,
  .docs-start__page:hover,
  .docs-card__link:hover {
    text-decoration: underline;
  }
}

.docs-start__action {
  justify-self: start;
  display: inline-flex;
  align-items: center;
  min-height: var(--touch-target);
  padding: 0 var(--space-xl);
  border-radius: var(--radius-md);
  background: var(--primary);
  color: var(--primary-foreground);
  font-weight: var(--weight-semibold);
  text-decoration: none;
}

.docs-card__head {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--space-lg);
  align-items: start;
}

.docs-card__icon {
  display: grid;
  place-items: center;
  width: var(--touch-target);
  height: var(--touch-target);
  border-radius: var(--radius-md);
  background: color-mix(in oklch, var(--primary) 12%, transparent);
  color: var(--primary);
}

.docs-card__titles {
  display: grid;
  gap: var(--space-2xs);
}

.docs-card__pages {
  display: grid;
  margin: 0;
  padding: 0;
  list-style: none;
  border-top: 1px solid var(--hairline);
}

.docs-card__page {
  border-bottom: 1px solid var(--hairline);
}

.docs-card__page:last-child {
  border-bottom: 0;
}

.docs-card__link,
.docs-card__planned {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  min-height: var(--touch-target);
  color: var(--foreground);
  text-decoration: none;
  border-radius: var(--radius-sm);
}

.docs-card__planned {
  color: var(--muted-foreground);
}

.docs-card__soon {
  padding: 0 var(--space-md);
  border-radius: var(--radius-pill);
  background: var(--muted);
  font-size: var(--text-xs);
  line-height: var(--text-xs-leading);
}

.docs-home__reference {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-2xl);
  align-items: center;
}

@media (min-width: 64rem) {
  .docs-home__reference {
    grid-template-columns: minmax(0, 1fr) auto;
  }
}

.docs-home__reference-intro {
  display: grid;
  gap: var(--space-lg);
}

.docs-home__figures {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3xl);
  margin: 0;
}

.docs-home__figure {
  display: grid;
}

.docs-home__figure dt {
  order: 2;
  font-size: var(--text-sm);
  line-height: var(--text-sm-leading);
  color: var(--muted-foreground);
}

.docs-home__figure dd {
  order: 1;
  margin: 0;
  font-family: var(--font-heading);
  font-size: var(--text-4xl);
  line-height: var(--text-4xl-leading);
  font-weight: var(--weight-semibold);
}

.docs-home__aside {
  display: flex;
  align-items: center;
  gap: var(--space-xl);
  padding: var(--space-xl) var(--space-2xl);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-lg);
  color: var(--muted-foreground);
  text-decoration: none;
}

@media (hover: hover) {
  .docs-home__aside:hover {
    background: var(--raised);
  }
}

.docs-home__aside-text {
  display: grid;
  gap: var(--space-2xs);
}

.docs-home__aside-title {
  font-weight: var(--weight-semibold);
  color: var(--foreground);
}

/* The copy button search.js adds to every code block. In this sheet for the
 * same reason as the search results below: a script writes it, so no
 * component renders it. */
.code-copy {
  min-height: var(--space-3xl);
  padding: 0 var(--space-md);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-md);
  background: var(--background);
  color: var(--muted-foreground);
  font-family: var(--font-sans);
  font-size: var(--text-xs);
  line-height: var(--text-xs-leading);
  cursor: pointer;
}

.code-copy[data-below="true"] {
  justify-self: end;
  margin-top: var(--space-xs);
}

@media (hover: hover) {
  .code-copy:hover {
    color: var(--foreground);
    border-color: var(--border);
  }
}

.code-copy:focus-visible {
  outline: 2px solid var(--ring);
  outline-offset: 2px;
}

/* The search results the loader renders. In this sheet, not libs/ui's,
 * because no component renders them: search.js writes this markup after
 * Pagefind answers, and libs/ui's cross-reference would call the rules
 * unused. The field itself is a component and is styled there. */

.search__list {
  display: grid;
  gap: var(--space-xs);
  margin: 0;
  padding: 0;
  list-style: none;
}

.search__result {
  font-size: var(--text-sm);
  line-height: var(--text-sm-leading);
}

.search__excerpt,
.search__empty {
  margin: 0;
  font-size: var(--text-xs);
  line-height: var(--text-xs-leading);
  color: var(--muted-foreground);
}

/* The blog: the index and one post. Both sit in the page's own column with
 * the same gutter every other page has -- `padding-inline`, never a
 * shorthand, so vertical spacing cannot zero it. */

.blog,
.post {
  display: grid;
  /* --gap-block, not --space-md: a list of posts is a list of blocks, and
     0.5rem between two cards read as one block with a seam in it. */
  gap: var(--gap-block);
  max-width: var(--width-prose);
  margin-inline: auto;
  padding-block: var(--page-start) var(--page-end);
  padding-inline: var(--gutter);
}

.blog__empty {
  margin: 0;
  color: var(--muted-foreground);
}

/* --- the API reference ------------------------------------------------
 *
 * Its own frame, not .docs-layout's: an endpoint page is an article and its
 * examples side by side, which does not fit in --width-page with a sidebar
 * beside them. One column on a phone (the examples fall under the article,
 * where the reader has finished the fields), sidebar + article from 64rem,
 * and the examples column from 80rem, sticky beside the fields they show. */
.reference-layout {
  --page-start: var(--space-4xl);
  --page-end: var(--space-6xl);
  --width-reference: 100rem;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-2xl);
  align-items: start;
  padding-block: var(--page-start) var(--page-end);
  padding-inline: var(--gutter);
  max-width: var(--width-reference);
  margin-inline: auto;
}

.reference-article {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-2xl);
  align-content: start;
  min-width: 0;
}

.reference-examples {
  display: grid;
  gap: var(--space-lg);
  align-content: start;
  min-width: 0;
}

@media (min-width: 64rem) {
  .reference-layout {
    grid-template-columns: 16rem minmax(0, 1fr);
  }

  .reference-layout[data-examples="true"] .reference-examples {
    grid-column: 2;
  }

  .reference-layout .docs-sidebar {
    grid-row: 1 / span 2;
  }
}

@media (min-width: 80rem) {
  .reference-layout[data-examples="true"] {
    grid-template-columns: 16rem minmax(0, 1fr) minmax(0, 30rem);
  }

  .reference-layout[data-examples="true"] .reference-examples {
    grid-column: 3;
    grid-row: 1;
    position: sticky;
    top: var(--space-xl);
    max-height: calc(100vh - var(--space-2xl));
    overflow-y: auto;
    overscroll-behavior: contain;
  }

  .reference-layout .docs-sidebar {
    grid-row: 1;
  }
}

.reference-eyebrow {
  margin: 0;
  font-size: var(--text-sm);
  line-height: var(--text-sm-leading);
  font-weight: 600;
  color: var(--muted-foreground);
}

.reference-eyebrow__link {
  justify-self: start;
  text-decoration: none;
}

@media (hover: hover) {
  .reference-eyebrow__link:hover {
    color: var(--foreground);
    text-decoration: underline;
  }
}

.reference-home {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-2xl);
  align-items: start;
}

@media (min-width: 80rem) {
  .reference-home {
    grid-template-columns: minmax(0, 1fr) minmax(0, 28rem);
  }
}

.reference-home__intro,
.reference-resource__intro {
  display: grid;
  gap: var(--space-md);
}

.reference-home__title,
.reference-resource__title {
  margin: 0;
  font-size: var(--text-4xl);
  line-height: var(--text-4xl-leading);
  letter-spacing: -0.02em;
  color: var(--foreground);
}

.reference-home__lede,
.reference-resource__lede {
  margin: 0;
  max-width: var(--width-prose);
  font-size: var(--text-lg);
  line-height: var(--text-lg-leading);
  color: var(--muted-foreground);
}

.reference-resource__lede p {
  margin: 0 0 var(--space-sm);
}

.reference-home__text {
  margin: 0;
  max-width: var(--width-prose);
  color: var(--muted-foreground);
}

.reference-home__groups {
  display: grid;
  gap: var(--space-2xl);
}

.reference-home__group {
  display: grid;
  gap: var(--space-md);
}

.reference-home__group-title {
  margin: 0;
  font-size: var(--text-sm);
  line-height: var(--text-sm-leading);
  font-weight: 600;
  color: var(--muted-foreground);
}

.reference-response {
  display: grid;
  gap: var(--space-md);
}

.reference-response__meaning,
.reference-variant__lede,
.reference-variant__summary {
  margin: 0;
  color: var(--muted-foreground);
}

.reference-response__label {
  margin: 0;
  font-size: var(--text-sm);
  line-height: var(--text-sm-leading);
  font-weight: 600;
  color: var(--foreground);
}

.reference-variant__summary {
  margin-block-end: var(--space-md);
}

/* Text for a screen reader only: a table's empty corner header names its
   column without drawing anything. */
.prose__sr {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* --- the price calculator (/pricing) ------------------------------------
 *
 * Rendered hidden; `assets/pricing-calculator.js` reveals it. A reader
 * without JavaScript sees the worked examples instead, so nothing here
 * styles a control that cannot work.
 *
 * Every value is a token: the sliders borrow the brand green through
 * `accent-color`, which is the one property that themes a range input and
 * a checkbox without rebuilding either. */
.calc {
  display: grid;
  gap: var(--space-2xl);
  margin-top: var(--space-2xl);
}

/* An author `display` beats the user agent's `[hidden] { display: none }`,
   so without this the whole calculator renders for a reader with no
   JavaScript -- sliders and all, none of them doing anything. That is the
   one failure this design is supposed to make impossible, and it is a
   single missing rule away. */
.calc[hidden],
.calc__line[hidden],
.calc__field [hidden] {
  display: none;
}

@media (min-width: 60rem) {
  .calc {
    grid-template-columns: 1fr 1fr;
    gap: var(--space-4xl);
    align-items: start;
  }
}

.calc__controls {
  display: grid;
  gap: var(--space-xl);
  align-content: start;
}

.calc__field {
  display: grid;
  gap: var(--space-sm);
}

.calc__row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-md);
}

.calc__row label {
  font-weight: var(--weight-semibold);
}

.calc__row output {
  font-family: var(--font-heading);
  font-size: var(--text-xl);
  font-weight: var(--weight-semibold);
  font-variant-numeric: tabular-nums;
}

.calc input[type="range"] {
  width: 100%;
  accent-color: var(--brand-star);
}

.calc__check {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  font-weight: var(--weight-semibold);
}

.calc__check input {
  inline-size: var(--touch-target);
  block-size: var(--touch-target);
  accent-color: var(--brand-star);
}

.calc__note {
  margin: 0;
  font-size: var(--text-sm);
  line-height: var(--text-sm-leading);
  color: var(--pn-dim);
}

/* The bill. A definition list would be the honest element, but each row
   carries two values -- how many were billable and what they came to --
   so it is a three-column grid of plain text and `output`. */
.calc__bill {
  padding: var(--space-xl);
  border: var(--hairline) solid var(--pn-line);
  border-radius: var(--radius-2xl);
  background: var(--pn-raise);
}

.calc__line {
  display: grid;
  grid-template-columns: 1fr auto auto;
  gap: var(--space-md);
  padding-block: var(--space-md);
  border-bottom: var(--hairline) solid var(--pn-line);
  font-size: var(--text-sm);
}

.calc__line output {
  font-variant-numeric: tabular-nums;
}

.calc__line output:first-of-type {
  color: var(--pn-dim);
}

.calc__total {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-md);
  margin: var(--space-lg) 0 0;
  font-weight: var(--weight-semibold);
}

.calc__total output {
  font-family: var(--font-heading);
  font-size: var(--text-3xl);
  font-variant-numeric: tabular-nums;
}
