/* @webcommerciale/os v0.113.0 */
/* ==========================================================================
   @webcommerciale/os

   One import for everything:

     @import '@webcommerciale/os';

   Everything lives inside a single named layer with its own sub-layers. The
   previous system declares bare `tokens, base, components, utilities`, so a
   project holding both would land them in the same layers and interleave two
   cascades. Prefixes stop the class names colliding; only nesting stops the
   cascade colliding.

   Order inside the layer is the order of increasing specificity of intent:
   tokens hold values, base holds element defaults, roles name what things
   are, components are the assembled objects, utilities are the residue and
   therefore last.
   ========================================================================== */

@layer ds {
  @layer tokens, base, roles, components, utilities;

@layer tokens {
  /* Tokens: the primitives, and nothing else.

     No token here is named after a component. If a value only makes sense
     inside one component, it belongs in that component's file, where it can be
     read next to the thing it serves.

     The type scale is deliberately absent, and step 4 brings it in with the
     roles rather than before them. Publishing a raw size ramp now would hand
     everyone a way to set type before there is a way to name it, and that is
     the exact order that produced 891 hand-built text elements in the system
     this one replaces. See ROADMAP.md.
  */

    /* ==========================================================================
       Colour

       Two scales and seven groups of roles, counted on 20 August 2026 because the
       line said four and had said four since there were four. Nothing here is
       named after a component, and nothing is named after a CSS property:
       `surface` is a place a thing sits on, not `background-color`. A token called `bg-accent-blue`
       would imply a page background that is accent blue, which is not a thing
       this system has.

       Light is the default. Dark re-declares the same names on
       [data-theme="dark"], so a component never asks which theme it is in, and it
       repeats this file's order so the two read side by side.

       THE ORDER IS THE DEPENDENCY, as far as it can be. A group that reads
       another group's token sits below it, which is why Interactive comes before
       Border: `--ds-border-active` is `var(--ds-interactive)` and used to name a
       token declared further down the file. Two references still point forward,
       Brand and Status both reading `--ds-static-white`, and they stay: moving
       Utility above them would put the odds and ends in the middle of the colour
       groups AND invert the range `npm run check` uses to find the statuses,
       which is bounded by `--ds-success:` and `--ds-static-white:` and goes
       silently empty backwards. Named here rather than left to be rediscovered.

       THE NEUTRALS ARE A RAMP, and `scripts/ramp.mjs` is it: one hue, one chroma
       curve, and a lightness grid that every grey in this file stands on. Before
       20 August 2026 they were Tailwind's zinc written out by hand, and the ramp
       had already been left eight times, every escape a line or a secondary ink
       and every one forced by a contrast measurement. Zinc's steps run 1.8 to
       16.0 apart in OKLCH lightness and three of the eight landed inside the two
       sixteen-point gaps: the values the system kept needing were the ones the
       borrowed ramp did not have.

       The grid hangs from the light page at lightness 98.5 and steps down by 2,
       forty-three rungs to 14.5. Two is what the snippet's ground fixes rather
       than a preference: the syntax palette clears 4.5:1 on a ground of 96.4 and
       fails under it, so the well sits at 96.5, one rung down from the page, with
       one 8-bit step of margin and no more: that rung is #f3f3f4 at 4.52 and the
       next value darker, #f2f2f4, measures 4.49. A step of 3 puts the well at 95
       and takes the green and the amber under AA.

       `npm run check` reads the ramp and fails on any neutral that is not one of
       its rungs, so a ninth hand-picked grey cannot be written here. What it does
       NOT cover is the seventeen chromatic values, which are Tailwind steps still:
       they were walked up their own ramps until the contrast check passed and
       they pass, and D4 left them where they are.
       ========================================================================== */

    :root {
      /* --- Surfaces: a scale, numbered because position IS the meaning -------
         Each level sits ON TOP OF the one before it. Pick by what your element is
         stacked on, never by how light or dark you want it: in dark the scale
         climbs and in light it inverts, because nothing goes lighter than a white
         card. Same numbers, opposite direction, one decision to make. */
      --ds-surface-0: #fafafa;  /* the page itself, behind everything */
      --ds-surface-1: #ffffff;  /* a card, a panel, a sheet sitting on the page */
      --ds-surface-2: #f3f3f4;  /* a field, a row, a well inside that card */
      --ds-surface-3: #e6e6e8;  /* a chip or a thumbnail inside that; the deepest the stack goes */
      /* A disabled control loses its tone entirely and lands on one grey, so a
         row of them reads as one dead row instead of three tinted ones, which
         means the fill, the outline and the label all move together. They used to
         sit in a section of their own for that reason, and it put the answer to
         "what grey is a switched-off border" three ladders away from every other
         border in the system. */
      --ds-surface-disabled: #f3f3f4;  /* the fill of a control that is switched off */

      /* A state is a different axis and does not get a level. An active row is not
         stacked higher than its neighbours, it is selected. */
      /* A state is a wash and not a step of the ladder above. These were
         surface 2 and surface 3, which is a whole storey of the stack for a row
         that is only being pointed at: on a dark page the hovered row read as a
         raised panel and the selected one as two. A wash also lands correctly on
         whatever surface the row happens to sit on, which a fixed colour cannot. */
      --ds-surface-hover: rgb(10 10 13 / 0.04);  /* a row or a card under the pointer, before it is chosen */
      --ds-surface-active: rgb(10 10 13 / 0.08);  /* the row that is selected, and stays selected */

      /* --- Text: three neutrals, named by the job they do --------------------
         Primary is the default for anything with content in it. Secondary is the
         lesser half of a pair and needs a primary beside it. Tertiary is
         decorative only and does not reach 4.5:1 on every surface, so never
         content.

         `-neutral` is part of the name because the status colours below can also
         be text: primary-neutral against danger is the distinction that makes
         both readable at a glance.

         The three sit on a ramp of their own, and the ramp is what makes high
         contrast a shift rather than a second palette: `data-contrast="high"`
         points secondary at ink 1 and tertiary at ink 2, so every value it lands
         on is one the theme already chose. See tokens/access.css.

         The ink names are internal and a page never types one. A colour used
         directly is a colour nothing can move. */
      --ds-ink-1: #0a0a0d;
      --ds-ink-2: #53535c;
      --ds-ink-3: #6f6f79;

      --ds-text-primary-neutral: var(--ds-ink-1);  /* anything with content in it: a heading, a paragraph, a value */
      --ds-text-secondary-neutral: var(--ds-ink-2);  /* the lesser half of a pair, and only beside a primary */
      --ds-text-tertiary-neutral: var(--ds-ink-3);
      /* 4.48:1 on the disabled fill. An inactive control is exempt from the
         contrast rules and that is not a reason to make it invisible: a reader
         still has to see WHICH control is unavailable. */
      --ds-text-disabled: #6f6f79;  /* the label of a control that is switched off */

      /* --- Interactive: the neutral that reacts to a pointer ------------------ */
      --ds-interactive: #3e3d46;  /* a neutral that answers a pointer: an icon button, a sort arrow */
      --ds-interactive-tint: rgb(62 61 70 / 0.1);  /* the wash behind a quiet neutral control */
      --ds-interactive-tint-hover: rgb(62 61 70 / 0.18);  /* that wash, deeper, under the pointer */
      --ds-interactive-edge: rgb(62 61 70 / 0.2);  /* the line around a quiet neutral control */

      /* --- Border -------------------------------------------------------------
         The lines: between two things, around a card, around a control.

         Four, and the fourth is a state. The surfaces have had `-hover` and
         `-active` since the first commit and the borders had neither, which meant
         a selected card could change its fill and not its outline: half a state.

         `-active` is the interactive neutral rather than a fifth grey, so the
         border of a chosen thing and the control that reacts to a pointer are the
         same colour by construction and not by coincidence.

         `-strong` moved from #a1a1aa. Anything that identifies a component or its
         state owes 3:1 under WCAG 1.4.11, and #a1a1aa measured 2.46 on the page
         here and 2.57 in dark: an input outline nobody with low vision could find.
         Checked now, for every border except `-subtle`, which is a divider and is
         meant to be barely there. */
      /* The owner brought this line and its dark twin from another system,
         because what they replaced equalled `--ds-surface-3` in light and was
         invisible on the surface it drew round. They came at a different hue from
         everything around them, 219 and 232 against the greys' 286, so a field's
         outline was a colder colour than the field. On the ramp now, at the hue
         the surfaces use, and holding the weight they were brought for: 1.51:1 on
         white against the 1.27 of the value they replaced. */
      --ds-border: #d2d2d6;  /* the line of a CONTROL at rest: a field, an outlined button */
      /* The line that separates rather than bounds: a row from the next row, a
         head from a body, a strip from the list under it. Quieter than the
         control line and equal to no surface, which is what killed the last one:
         it was #f4f4f5, byte for byte `--ds-surface-2`, so every divider it drew
         measured 1.00:1 against the ground it was drawn on and a table went its
         whole life with no row lines. Measured now: 1.33:1 on a sheet, 1.27 on the
         page, 1.20 in a well, against the control line's 1.51. */
      --ds-border-subtle: #dfdfe2;
      /* The rung that clears 3:1 on the deepest ground a control stands on, and
         the deepest ground is what decides how far this can go. Measured 3.10 on
         surface 3, 3.48 in a well, 3.86 on a sheet. One rung darker is ink round
         every control: zinc 500, which this used to sit half a step from, reads
         4.63 against the page. */
      /* A line that DRAWS A SHAPE rather than encloses a box, which is the job
         the owner named on 12 September 2026 after reading the eight rules that
         take it: the ring round a face, the box of a checkbox, the dashed target
         of a drop zone, the node on a roadmap's path, the zero line and the
         cursor on a chart, the alert's edge, the page-links border. A box that
         merely holds something takes `--ds-border` and the resting rule, 50 uses
         to 8. Read the shape, not the container. */
      --ds-border-strong: #81818a;
      /* Half way between the resting line and the strong one, so being switched
         off is a colour and not only a fill. It was #e4e4e7, the same value as
         `--ds-border`, so a disabled field and a resting one had the identical
         outline and only the fill said anything: measured 1.55:1 on the fill it
         sits on against 1.15 before. */
      --ds-border-disabled: #c5c5cb;  /* the outline of a control that is switched off */
      --ds-border-active: var(--ds-interactive);  /* the outline of the control that is chosen or focused */


      /* --- Inverse: a dark block in a light theme, and the text on it --------- */
      --ds-inverse: #17171b;  /* a block that quotes the page back at itself: a filled neutral button, a dark banner */
      --ds-on-inverse: #fafafa;  /* the label on that block, whichever way round the theme has it */
      /* Lighter on the way down, because this one is already the darkest thing on
         a light page and there is nowhere below it to go. The label flips with the
         theme, which is what `inverse` means: this is the page quoted back at
         itself, so it is white on black here and black on white in dark. */
      --ds-inverse-hover: #25252b;  /* that block under the pointer */
      --ds-inverse-active: #3e3d46;  /* that block while it is held down */
      /* Ink on the inverse block, beyond the one label: the block IS the other
         theme's page quoted, so these are the other theme's own vetted values,
         rung for rung. The lesser line is the dark theme's ink-2, and the four
         tone marks are the dark theme's tone inks, whose worst pairs are written
         beside them in the block below. Born 22 August 2026 for the toast, the
         first component to put a full sentence on the inverse ground. */
      --ds-secondary-on-inverse: #b2b2b9;
      --ds-success-on-inverse: #4ade80;
      --ds-warning-on-inverse: #fbbf24;
      --ds-danger-on-inverse: #fca5a5;
      --ds-info-on-inverse: #7dd3fc;

      /* --- Brand: one hook, meant to be overridden per project ----------------
         The tint and the edge are mixed FROM the brand rather than written as
         their own hex, so a project that re-points --ds-brand gets a matching
         wash for free. The status colours below are literal because they are the
         system's own and nobody overrides them; this one exists to be overridden,
         and a hardcoded tint would survive the override and clash with it. */
      --ds-brand: #2563eb;  /* the fill of the one action a page wants: the primary button, the checked box */
      --ds-on-brand: var(--ds-static-white);  /* the label on the brand fill, white in both themes on purpose (5.17:1) */
      --ds-brand-hover: color-mix(in srgb, var(--ds-brand) 85%, black);  /* that fill under the pointer */
      --ds-brand-active: color-mix(in srgb, var(--ds-brand) 72%, black);  /* that fill while it is held down */
      --ds-brand-tint: color-mix(in srgb, var(--ds-brand) 10%, transparent);  /* the wash behind a quiet brand thing: a selected tab, a soft badge */
      --ds-brand-tint-hover: color-mix(in srgb, var(--ds-brand) 18%, transparent);  /* that wash, deeper, under the pointer */
      --ds-brand-edge: color-mix(in srgb, var(--ds-brand) 20%, transparent);  /* the line around a brand-tinted block */
      /* Darkened rather than picked, for the same reason the tint is mixed: the
         brand is the one colour a project replaces, and a hand-picked ink would
         survive the replacement. */
      --ds-brand-ink: color-mix(in srgb, var(--ds-brand) 78%, black);  /* the brand as TEXT on the page: a link, a brand label */

      /* A TONE MAY BE A LINE where the line is the signal, ruled 12 September
         2026. Eight rules paint a border with a fill: a field, a code field and
         the editor go `--ds-danger` when what they hold is broken, and the
         current tab, the chosen chip and today's square go `--ds-inverse` because
         being the one you are on is the whole of what the line says. The `-edge`
         values below are not that: they are the 20% line around a TINTED BLOCK,
         which is a container's outline and would be a whisper under a field that
         failed. A tone gains no third value; where the line carries the meaning,
         the fill is what draws it. */

      /* --- Status --------------------------------------------------------------
         What happened, in four colours built the same way each time.

         Status and not state. A state is what an element is doing under a pointer
         or a keyboard, hovered, held, chosen, and the four rules for those are
         above. These four report an outcome, and an element wearing one is not in
         a state at all: a badge is green because the thing it names succeeded, not
         because anybody is touching it. Called `States` here until 20 August 2026,
         which put two sections under one word on the colour page, since that page
         reads these markers for its headings.

         Five parts each, and every suffix means the same thing everywhere: the
         bare name is the fill, `-tint` is a wash behind it, `-edge` is the line
         around it, `-ink` is the colour as text, `-on-` is the label on the fill.

         `-on` is the fourth part, and it is not decoration: a status used as a
         BACKGROUND needs a text colour that survives on it, and white is the
         wrong answer more often than not. White on this green measures 3.3:1 and
         on this amber 3.2:1, both under AA, while black measures 6.4 and 6.6.

         `-ink` is the fifth, and it exists because the bare name cannot do two
         jobs. A status colour is chosen to be SEEN as a block, and a colour
         chosen to be seen as a block is too light to be read as text on a pale
         one: the green 600 this replaced measured 3.16:1 on the page and 2.95 on
         its own tint, the amber 600 3.05 and 2.86. Every coloured label and every
         soft badge in the system was under AA and looked fine, which is the whole
         problem with judging contrast by eye.

         Both are measurements. `npm run check` asserts every `-on` against the
         colour it names and every `-ink` against all four surfaces AND against
         its own tint over each of them, and the values below were picked by
         walking the ramp until it passed rather than by choosing a shade. */
      /* Green 700 and not 600. A filled control carries white, and white on 600
         measures 3.30, which is what made this label black while brand and danger
         carried white. Same green in both themes, same reason as those two: a
         filled thing is one decision. */
      --ds-success: #15803d;  /* the fill of a success block: a solid badge, a confirm button */
      --ds-success-hover: #166534;  /* that fill under the pointer */
      --ds-success-active: #14532d;  /* that fill while it is held down */
      --ds-success-tint: rgb(21 128 61 / 0.1);  /* the wash behind a quiet success block */
      --ds-success-tint-hover: rgb(21 128 61 / 0.18);  /* that wash, deeper, under the pointer */
      --ds-success-edge: rgb(21 128 61 / 0.2);  /* the line around it */
      --ds-success-ink: #166534;  /* success as TEXT: "Published", a passing count */
      --ds-on-success: var(--ds-static-white);  /* the label on the success fill (5.01:1) */

      /* Amber 700. Six hundred measures 3.19 against white, and every filled
         thing in this system carries white, so the amber comes to the label
         rather than the label going to the amber. It is a browner amber than the
         one people picture, and that is what carrying white costs. */
      --ds-warning: #b45309;  /* the fill of a warning block */
      --ds-warning-tint: rgb(180 83 9 / 0.1);  /* the wash behind a quiet warning block */
      --ds-warning-edge: rgb(180 83 9 / 0.2);  /* the line around it */
      --ds-warning-ink: #92400e;  /* warning as TEXT: "Expires in 3 days" */
      --ds-on-warning: var(--ds-static-white);  /* the label on the warning fill (5.02:1) */

      --ds-danger: #dc2626;  /* the fill of a destructive control or a failure block */
      --ds-danger-hover: #b91c1c;  /* that fill under the pointer */
      --ds-danger-active: #991b1b;  /* that fill while it is held down */
      --ds-danger-tint: rgb(220 38 38 / 0.1);  /* the wash behind a quiet danger block: an error panel */
      --ds-danger-tint-hover: rgb(220 38 38 / 0.18);  /* that wash, deeper, under the pointer */
      --ds-danger-edge: rgb(220 38 38 / 0.2);  /* the line around it, and around a field that failed */
      --ds-danger-ink: #991b1b;  /* danger as TEXT: the message under a broken field (5.67:1) */
      --ds-on-danger: var(--ds-static-white);  /* the label on the danger fill (4.83:1) */

      /* Cyan, and no longer the brand's blue. It was #2563eb, which IS the brand
         to the pixel, so an info block and a brand block were the same object
         wearing two names and neither said anything the other did not. It also
         carries white at 5.93, where the blue at its dark end did not. */
      --ds-info: #0369a1;  /* the fill of an informational block, which is a container and not a button */
      --ds-info-tint: rgb(3 105 161 / 0.1);  /* the wash behind a quiet info block */
      --ds-info-edge: rgb(3 105 161 / 0.2);  /* the line around it */
      --ds-info-ink: #075985;  /* info as TEXT, when a sentence is the whole notice */
      --ds-on-info: var(--ds-static-white);  /* the label on the info fill (5.93:1) */

      /* --- Utility -------------------------------------------------------------
         Four that belong to no ladder.

         The two static ones are for text over a photograph, where the theme is
         irrelevant because the backdrop is not a surface, so they hold the same
         value in both.

         Focus is its own token rather than the brand, because a brand can be a
         pale yellow and this ring has to hold contrast on every surface the system
         ships. It is the page's own opposite and not the blue every framework
         reaches for: blue is a colour this system spends on the brand, and a ring
         that is nearly black on a light screen and nearly white on a dark one is
         the highest contrast either theme has to offer. A project that wants a
         coloured ring changes this one token and every ring in the system moves,
         which is the point of it having a token at all. */
      /* The wash over the page behind a panel that has to be closed. Its own
         token because two of them would disagree the day a modal lands beside the
         drawer.

         Light: enough to say the page is not yours, not enough to hide it. It was
         40% and that turned the page into a photograph of itself, which is louder
         than the panel it is meant to sit behind. */
      --ds-scrim: rgb(10 10 13 / 0.2);
      /* ── Hues ────────────────────────────────────────────────────────────────
         Twelve colours that MEAN nothing: not a status, not the brand, but the
         colour a reader gave an option of their own, "High" red because they
         said so. A tone says what happened; a hue says which one this is. The
         same five parts a tone has, so a badge reads either with one rule, and
         each vetted the way a tone is: the ink over every surface and over its
         own tint in both themes. Tailwind's ramps like the tones, 700 for the
         fill under a white label, 800 for the ink on the light theme. */
      --ds-hue-red: #b91c1c;
      --ds-hue-red-tint: rgb(185 28 28 / 0.12);
      --ds-hue-red-edge: rgb(185 28 28 / 0.25);
      --ds-hue-red-ink: #991b1b;
      --ds-on-hue-red: var(--ds-static-white);
      --ds-hue-orange: #c2410c;
      --ds-hue-orange-tint: rgb(194 65 12 / 0.12);
      --ds-hue-orange-edge: rgb(194 65 12 / 0.25);
      --ds-hue-orange-ink: #9a3412;
      --ds-on-hue-orange: var(--ds-static-white);
      --ds-hue-amber: #b45309;
      --ds-hue-amber-tint: rgb(180 83 9 / 0.12);
      --ds-hue-amber-edge: rgb(180 83 9 / 0.25);
      --ds-hue-amber-ink: #92400e;
      --ds-on-hue-amber: var(--ds-static-white);
      --ds-hue-yellow: #a16207;
      --ds-hue-yellow-tint: rgb(161 98 7 / 0.12);
      --ds-hue-yellow-edge: rgb(161 98 7 / 0.25);
      --ds-hue-yellow-ink: #854d0e;
      --ds-on-hue-yellow: var(--ds-static-white);
      --ds-hue-lime: #4d7c0f;
      --ds-hue-lime-tint: rgb(77 124 15 / 0.12);
      --ds-hue-lime-edge: rgb(77 124 15 / 0.25);
      --ds-hue-lime-ink: #3f6212;
      --ds-on-hue-lime: var(--ds-static-white);
      --ds-hue-green: #15803d;
      --ds-hue-green-tint: rgb(21 128 61 / 0.12);
      --ds-hue-green-edge: rgb(21 128 61 / 0.25);
      --ds-hue-green-ink: #166534;
      --ds-on-hue-green: var(--ds-static-white);
      --ds-hue-teal: #0f766e;
      --ds-hue-teal-tint: rgb(15 118 110 / 0.12);
      --ds-hue-teal-edge: rgb(15 118 110 / 0.25);
      --ds-hue-teal-ink: #115e59;
      --ds-on-hue-teal: var(--ds-static-white);
      --ds-hue-cyan: #0e7490;
      --ds-hue-cyan-tint: rgb(14 116 144 / 0.12);
      --ds-hue-cyan-edge: rgb(14 116 144 / 0.25);
      --ds-hue-cyan-ink: #155e75;
      --ds-on-hue-cyan: var(--ds-static-white);
      --ds-hue-blue: #1d4ed8;
      --ds-hue-blue-tint: rgb(29 78 216 / 0.12);
      --ds-hue-blue-edge: rgb(29 78 216 / 0.25);
      --ds-hue-blue-ink: #1e40af;
      --ds-on-hue-blue: var(--ds-static-white);
      --ds-hue-indigo: #4338ca;
      --ds-hue-indigo-tint: rgb(67 56 202 / 0.12);
      --ds-hue-indigo-edge: rgb(67 56 202 / 0.25);
      --ds-hue-indigo-ink: #3730a3;
      --ds-on-hue-indigo: var(--ds-static-white);
      --ds-hue-violet: #6d28d9;
      --ds-hue-violet-tint: rgb(109 40 217 / 0.12);
      --ds-hue-violet-edge: rgb(109 40 217 / 0.25);
      --ds-hue-violet-ink: #5b21b6;
      --ds-on-hue-violet: var(--ds-static-white);
      --ds-hue-pink: #be185d;
      --ds-hue-pink-tint: rgb(190 24 93 / 0.12);
      --ds-hue-pink-edge: rgb(190 24 93 / 0.25);
      --ds-hue-pink-ink: #9d174d;
      --ds-on-hue-pink: var(--ds-static-white);


      --ds-static-white: #ffffff;  /* text over a photograph, where the theme is irrelevant */
      --ds-static-black: #000000;  /* the same, the other way up */
      --ds-focus: #17171b;  /* the ring a keyboard leaves behind, and nothing else */

      /* --- Syntax ---------------------------------------------------------------
         Four for code, and the only place in the system where colour says what
         KIND of word something is rather than what state something is in. Inside a
         snippet that is what a reader wants, and nothing in there has a state to
         report.

         Measured against one background and not four. A snippet paints its own
         ground, surface 2, precisely so this palette has a single known thing to
         hold against: on surface 3 two of these fall under 4.5, and there is no
         reason to put code there. */
      --ds-syntax-string: #15803d;  /* anything in quotes */
      --ds-syntax-keyword: #7c3aed;  /* the words of the language: import, const, @media */
      --ds-syntax-number: #b45309;  /* numbers, and the custom properties that hold them */
      --ds-syntax-name: #0369a1;  /* what a thing is called: an element, a prop, a flag */

      /* --- Charts ---------------------------------------------------------------
         Five for data, and the second place colour says WHICH rather than what
         state: a series is a series, and painting the second one `--ds-danger`
         would put an alarm on a line that only means "mobile". Categorical on
         purpose, and the values are shadcn's own CURRENT chart quintet, hue
         for hue, their oklch converted to hex because the check's parser reads
         hex and nothing else. Warm here, vivid in dark: each theme carries the
         pairing that was designed for it. A
         chart that MEANS a tone, a danger threshold, a success band, reaches
         for the tone by name instead. */
      --ds-chart-1: #f54900;  /* the first series: orange */
      --ds-chart-2: #009689;  /* teal */
      --ds-chart-3: #104e64;  /* deep petrol */
      --ds-chart-4: #ffb900;  /* gold */
      --ds-chart-5: #fe9a00;  /* amber */

      /* ── The maker's mark ──────────────────────────────────────────────────
         WebCommerciale's logo, as the maker draws it, in the two versions the
         maker supplied: the dark one for a light ground, the light one for a
         dark ground. Four inks: the wordmark's first word and the main dots,
         the second word, and the two quieter dots. Not opacities of one ink:
         the light version is not the dark one faded, it is a different drawing.
         Read only by `site-credits.css`. */
      --ds-maker-word-1: #141413;
      --ds-maker-word-2: #666666;
      --ds-maker-dot-2: #808080;
      --ds-maker-dot-3: #999999;
      /* The symbol alone, the dots without the words, has two inks in the
         maker's file: the main dots and one quieter grey. */
      --ds-maker-symbol-dot: #999999;
    }

    [data-theme="dark"] {
      --ds-surface-0: #0a0a0d;
      --ds-surface-1: #17171b;
      --ds-surface-2: #25252b;
      --ds-surface-3: #3e3d46;
      --ds-surface-disabled: #25252b;

      --ds-surface-hover: rgb(250 250 250 / 0.05);
      --ds-surface-active: rgb(250 250 250 / 0.09);

      --ds-ink-1: #fafafa;
      /* Between two rungs that both fail it for different reasons, which is why
         zinc had nothing here: its 400 (#a1a1aa) measures 4.18:1 on surface 3 and
         its 300 (#d4d4d8) is close enough to primary to lose the pair. This holds
         5.08:1, the nearest rung that clears AA on every dark surface. Found by
         the contrast check, not by looking. */
      --ds-ink-2: #b2b2b9;
      --ds-ink-3: #6f6f79;

      --ds-text-primary-neutral: var(--ds-ink-1);
      --ds-text-secondary-neutral: var(--ds-ink-2);
      --ds-text-tertiary-neutral: var(--ds-ink-3);
      /* 4.29:1 on the disabled fill. An inactive control is exempt from the
         contrast rules and that is not a reason to make it invisible: a reader
         still has to see WHICH control is unavailable. */
      --ds-text-disabled: #6f6f79;   /* 3.07:1 on the disabled fill */

      --ds-interactive: #d2d2d6;
      --ds-interactive-tint: rgb(210 210 214 / 0.1);
      --ds-interactive-tint-hover: rgb(210 210 214 / 0.2);
      --ds-interactive-edge: rgb(210 210 214 / 0.2);

      --ds-border: #43434b;
      --ds-border-subtle: #2e2e35;   /* 1.33:1 on a sheet, 1.47 on the page */
      /* The dark theme had the outline of a control at 7.8 against the page while
         the light theme had it at 4.8, so the same button was a line on one and a
         lit box on the other. This holds 6.01 on the page, 5.43 on surface 1 and
         4.63 on surface 2, which is the light theme's relationship in the dark
         theme's colours, and 3.26 on surface 3, clear of the three to one a
         control's edge has to keep. */
      --ds-border-strong: #8d8d96;
      --ds-border-disabled: #53535c;   /* 2.00:1 on the disabled fill, was 1:1 */
      --ds-border-active: var(--ds-interactive);

      --ds-inverse: #fafafa;
      --ds-on-inverse: #17171b;
      /* Darker on the way down, for the same reason and in the other direction. */
      --ds-inverse-hover: #e6e6e8;
      --ds-inverse-active: #d2d2d6;
      /* Ink on the inverse block: the light theme's own values, because here the
         block is the light page quoted. Same reasoning as the light block's. */
      --ds-secondary-on-inverse: #53535c;
      --ds-success-on-inverse: #166534;
      --ds-warning-on-inverse: #92400e;
      --ds-danger-on-inverse: #991b1b;
      --ds-info-on-inverse: #075985;

      /* The same blue as light, which is the whole idea: a filled button is one
         decision and not two. White measures 5.17 on it, and it measures 3.83
         against the page, so it is a visible object carrying readable text. The
         lifted #3b82f6 it replaced measured 3.68 against white, which is why the
         label used to turn black here. */
      --ds-brand: #2563eb;
      /* Every status is lifted in dark so it holds against a near-black page, and
         lifting it is exactly what makes white text fail on top of it: all four
         measure between 3.7 and 3.8 against white and between 5.6 and 9.7 against
         black. A bright chip with dark text is the shape a dark theme wants
         anyway. Asserted by `npm run check`. */
      --ds-on-brand: var(--ds-static-white);   /* 5.17:1 */
      /* Lifted rather than darkened: on a near-black page the way to say "you are
         on it" is more light, not less. */
      --ds-brand-hover: color-mix(in srgb, var(--ds-brand) 80%, white);
      --ds-brand-active: color-mix(in srgb, var(--ds-brand) 65%, white);
      --ds-brand-tint: color-mix(in srgb, var(--ds-brand) 15%, transparent);
      --ds-brand-tint-hover: color-mix(in srgb, var(--ds-brand) 25%, transparent);
      --ds-brand-edge: color-mix(in srgb, var(--ds-brand) 30%, transparent);
      /* Lightened here, for the same reason every status ink is. */
      /* Half and half now that the brand itself is darker. At 62% it landed at
         3.98 on surface 3; at 50% its worst pair is 4.58, measured by hand
         because color-mix is the one value `npm run check` cannot resolve and
         says so. */
      --ds-brand-ink: color-mix(in srgb, var(--ds-brand) 50%, white);

      --ds-success: #15803d;
      --ds-success-hover: #166534;
      --ds-success-active: #14532d;
      --ds-success-tint: rgb(21 128 61 / 0.15);
      --ds-success-tint-hover: rgb(21 128 61 / 0.25);
      --ds-success-edge: rgb(21 128 61 / 0.3);
      --ds-success-ink: #4ade80;   /* worst pair 5.59:1 */
      --ds-on-success: var(--ds-static-white);

      --ds-warning: #b45309;
      --ds-warning-tint: rgb(180 83 9 / 0.15);
      --ds-warning-edge: rgb(180 83 9 / 0.3);
      --ds-warning-ink: #fbbf24;   /* worst pair 5.87:1 */
      --ds-on-warning: var(--ds-static-white);

      /* Same red as light, same reason as the brand. White measures 4.83 on it
         and it measures 4.09 against the page. */
      --ds-danger: #dc2626;
      --ds-danger-hover: #b91c1c;
      --ds-danger-active: #991b1b;
      --ds-danger-tint: rgb(220 38 38 / 0.15);
      --ds-danger-tint-hover: rgb(220 38 38 / 0.25);
      --ds-danger-edge: rgb(220 38 38 / 0.3);
      --ds-danger-ink: #fca5a5;   /* worst pair 5.33:1 */
      --ds-on-danger: var(--ds-static-white);   /* 4.83:1 */

      --ds-info: #0369a1;
      --ds-info-tint: rgb(3 105 161 / 0.15);
      --ds-info-edge: rgb(3 105 161 / 0.3);
      --ds-info-ink: #7dd3fc;
      --ds-on-info: var(--ds-static-white);

      --ds-focus: #fafafa;

      /* A touch heavier, because the same wash over a near-black page barely
         registers. Still short of the 60% it was. */
      --ds-scrim: rgb(10 10 13 / 0.35);

      --ds-syntax-string: #4ade80;
      --ds-syntax-keyword: #c4b5fd;
      --ds-syntax-number: #fbbf24;
      --ds-syntax-name: #7dd3fc;

      /* The chart palette, shadcn's dark quintet: the light one's warm five
         read as mud on near-black, so dark carries the vivid pairing that was
         designed for it. */
      --ds-chart-1: #1447e6;  /* electric blue */
      --ds-chart-2: #00bc7d;  /* green */
      --ds-chart-3: #fe9a00;  /* amber */
      --ds-chart-4: #ad46ff;  /* violet */
      --ds-chart-5: #ff2056;  /* raspberry */

      /* The hues, on the dark ground: the fill stays, the ink is the 300 of the
         ramp so it reads on every surface and over its own wash, the wash a
         little thinner than the light theme's: 400 measured 3.07:1 over it. */
      --ds-hue-red: #b91c1c;
      --ds-hue-red-tint: rgb(248 113 113 / 0.14);
      --ds-hue-red-edge: rgb(248 113 113 / 0.35);
      --ds-hue-red-ink: #fca5a5;
      --ds-on-hue-red: var(--ds-static-white);
      --ds-hue-orange: #c2410c;
      --ds-hue-orange-tint: rgb(251 146 60 / 0.14);
      --ds-hue-orange-edge: rgb(251 146 60 / 0.35);
      --ds-hue-orange-ink: #fdba74;
      --ds-on-hue-orange: var(--ds-static-white);
      --ds-hue-amber: #b45309;
      --ds-hue-amber-tint: rgb(251 191 36 / 0.14);
      --ds-hue-amber-edge: rgb(251 191 36 / 0.35);
      --ds-hue-amber-ink: #fcd34d;
      --ds-on-hue-amber: var(--ds-static-white);
      --ds-hue-yellow: #a16207;
      --ds-hue-yellow-tint: rgb(250 204 21 / 0.14);
      --ds-hue-yellow-edge: rgb(250 204 21 / 0.35);
      --ds-hue-yellow-ink: #fde047;
      --ds-on-hue-yellow: var(--ds-static-white);
      --ds-hue-lime: #4d7c0f;
      --ds-hue-lime-tint: rgb(163 230 53 / 0.14);
      --ds-hue-lime-edge: rgb(163 230 53 / 0.35);
      --ds-hue-lime-ink: #bef264;
      --ds-on-hue-lime: var(--ds-static-white);
      --ds-hue-green: #15803d;
      --ds-hue-green-tint: rgb(74 222 128 / 0.14);
      --ds-hue-green-edge: rgb(74 222 128 / 0.35);
      --ds-hue-green-ink: #86efac;
      --ds-on-hue-green: var(--ds-static-white);
      --ds-hue-teal: #0f766e;
      --ds-hue-teal-tint: rgb(45 212 191 / 0.14);
      --ds-hue-teal-edge: rgb(45 212 191 / 0.35);
      --ds-hue-teal-ink: #5eead4;
      --ds-on-hue-teal: var(--ds-static-white);
      --ds-hue-cyan: #0e7490;
      --ds-hue-cyan-tint: rgb(34 211 238 / 0.14);
      --ds-hue-cyan-edge: rgb(34 211 238 / 0.35);
      --ds-hue-cyan-ink: #67e8f9;
      --ds-on-hue-cyan: var(--ds-static-white);
      --ds-hue-blue: #1d4ed8;
      --ds-hue-blue-tint: rgb(96 165 250 / 0.14);
      --ds-hue-blue-edge: rgb(96 165 250 / 0.35);
      --ds-hue-blue-ink: #93c5fd;
      --ds-on-hue-blue: var(--ds-static-white);
      --ds-hue-indigo: #4338ca;
      --ds-hue-indigo-tint: rgb(129 140 248 / 0.14);
      --ds-hue-indigo-edge: rgb(129 140 248 / 0.35);
      --ds-hue-indigo-ink: #c7d2fe;
      --ds-on-hue-indigo: var(--ds-static-white);
      --ds-hue-violet: #6d28d9;
      --ds-hue-violet-tint: rgb(167 139 250 / 0.14);
      --ds-hue-violet-edge: rgb(167 139 250 / 0.35);
      --ds-hue-violet-ink: #c4b5fd;
      --ds-on-hue-violet: var(--ds-static-white);
      --ds-hue-pink: #be185d;
      --ds-hue-pink-tint: rgb(244 114 182 / 0.14);
      --ds-hue-pink-edge: rgb(244 114 182 / 0.35);
      --ds-hue-pink-ink: #f9a8d4;
      --ds-on-hue-pink: var(--ds-static-white);


      /* The maker's mark on a dark ground: the dark version mirrored, white
         where it was black, and the two quieter greys under it. The file the
         maker supplied as the light version had the first word in mid grey and
         read as faded on the site; the owner, 18 September 2026. */
      --ds-maker-word-1: #ffffff;
      --ds-maker-word-2: #999999;
      --ds-maker-dot-2: #999999;
      --ds-maker-dot-3: #808080;
      /* The symbol on a dark ground is all white, as the maker's file has it. */
      --ds-maker-symbol-dot: #ffffff;
    }

    /* --- The inverse ground, as a scope ---------------------------------------
       `data-ground="inverse"` on the element that paints `--ds-inverse` behind
       whole sentences: the toast wears it on its root and the site section's
       inverse band wears it now too. An axis like the surfaces and the radii,
       held in this file because these are colours and the literals live here.

       Two things happen inside, and the split matters:

       The NEUTRAL text names flip to what reads on the block, so a component
       that speaks in `--ds-text-*-neutral` keeps speaking without knowing where
       it stands. Only the neutrals: the `inverse` and `on-inverse` names stay
       the page's own, because the wearer paints its ground FROM them on the very
       element that carries the attribute, and because `button.css` answers this
       attribute by reading them as the ground's own pair since the toast was
       born. Flipping them broke both at once: the toast's field took the page's
       colour, and the banner button's label took the band's.

       The QUOTE family is the page's own pair, kept reachable on the block
       under names of its own: what a filled control on the ground becomes,
       because the block is the page quoted back at itself and a control
       standing on it is quoted back. Its hover and active are the other
       column's inverse pair, which is the vetted answer to "how does this fill
       move under the pointer" for a fill of exactly this colour.

       Cross-wired as literals, the way `--ds-secondary-on-inverse` already is:
       every value below is a rung the two columns already hold. An alias cannot
       say this on one element: a name reading the neutral while the neutral is
       being flipped on the same element is a cycle, and a cycle resolves both
       names to the guaranteed-invalid value. */
    [data-ground="inverse"] {
      /* In light the block is dark: the dark column's inks read on it. */
      --ds-text-primary-neutral: #fafafa;
      --ds-text-secondary-neutral: #b2b2b9;
      /* The light page, quoted onto its own dark block. */
      --ds-quote: #fafafa;
      --ds-on-quote: #0a0a0d;
      --ds-quote-hover: #e6e6e8;
      --ds-quote-active: #d2d2d6;
    }

    [data-theme="dark"] [data-ground="inverse"] {
      /* In dark the block is light: the light column's inks read on it. */
      --ds-text-primary-neutral: #0a0a0d;
      --ds-text-secondary-neutral: #53535c;
      /* The dark page, quoted onto its own light block. */
      --ds-quote: #0a0a0d;
      --ds-on-quote: #fafafa;
      --ds-quote-hover: #25252b;
      --ds-quote-active: #3e3d46;
    }

    /* A panel opened from the inverse ground stands on the page, not on the
       ground: a popover, a dialog and a drawer are page surfaces in the top
       layer, and each is still a DOM child of the bar that opened it, so it
       inherited the flipped inks and drew the page's ink on the page's surface.
       The selection bar's Set menu came out empty and its dialog unreadable
       (owner, 11 September 2026). The page's own inks are read back by their
       theme names, which the ground never flips, so one rule serves both
       themes; `button.css` steps aside inside the same three. */
    [data-ground="inverse"] :is(.ds-popover, .ds-dialog, .ds-drawer) {
      --ds-text-primary-neutral: var(--ds-ink-1);
      --ds-text-secondary-neutral: var(--ds-ink-2);
    }

    /* ==========================================================================
       Space

       A ramp, and three named distances built on it.

       The ramp is the raw material: numbers, in 4px steps, no opinions. The three
       named distances are the opinions, and they are the ones a page should reach
       for. A layout that spells out a ramp value at every level has taken the
       same decision many times; a layout that says "these are one element" has
       taken it once.

       The three named distances (element, block, section) are NOT here: they
       change with the surface, so they live in surface.css where both columns
       can be read side by side. On a marketing page an element gap of 16px reads
       as one group; on a dashboard the same 16px reads as loose.
       ========================================================================== */

    :root {
      --ds-space-0: 0;
      --ds-space-px: 1px;
      --ds-space-0-5: 0.125rem; /*  2px */
      --ds-space-1: 0.25rem;    /*  4px */
      --ds-space-2: 0.5rem;     /*  8px */
      --ds-space-3: 0.75rem;    /* 12px */
      --ds-space-4: 1rem;       /* 16px */
      --ds-space-5: 1.25rem;    /* 20px */
      --ds-space-6: 1.5rem;     /* 24px */
      --ds-space-8: 2rem;       /* 32px */
      --ds-space-10: 2.5rem;    /* 40px */
      --ds-space-12: 3rem;      /* 48px */
      --ds-space-16: 4rem;      /* 64px */
      --ds-space-20: 5rem;      /* 80px */
      --ds-space-24: 6rem;      /* 96px */
      --ds-space-32: 8rem;      /* 128px */
    }

    /* ==========================================================================
       Control heights

       Six, and they are the heights of things you can press or type into: a
       button, a field, a select, an icon button, a segmented control. Not a space
       ramp, which is the distance BETWEEN things; this is how tall a thing is.

       They live here rather than inside any one component because they are the
       same six everywhere. Two controls on one row have to be exactly as tall as
       each other or the row is visibly crooked, and two files writing `2.5rem` at
       each other is a match that holds until one of them is edited. There is one
       ladder, and a row lines up because both ends read the same tier.

       The floor is 24px, which is what WCAG 2.5.8 asks of a target. Nothing in
       this system is allowed below it, so the ramp starts there rather than
       offering a step that would have to be documented as unusable.

       In rem, so the whole ladder grows with `data-text`.

       ── What is written inside them ───────────────────────────────────────────
       One map, for every control that stands on a tier, so two controls on one row
       read at one size as well as standing at one height:

         24  small  12/16      48  body  16/24
         32  label  14/20      56  body  16/24
         40  body   16/24      64  body  16/24

       It stops at body. The level above it is fluid on the web surface, 18px
       growing to 20 with the window, and its leading is a ratio rather than a
       multiple of the 4px step: a word that changes size inside a box whose height
       is fixed by this ramp. Tabs, accordion and the tallest field each reached
       for it and each has been brought back.

       Four levels for six tiers, so the top four share one. A 64px control with a
       16px word in it is held together by the room at its sides, which every
       component here takes in em and therefore off the size.

       Each component writes the pair onto the element that wears it, never into a
       variable: a font token varies with the surface and an alias resolves where
       it is declared, not where it is read. This paragraph is the only place the
       map is stated; a component that disagrees with it is the one that is wrong.
       ========================================================================== */

    :root {
      --ds-size-1: 1.5rem;  /* 24px, the smallest target WCAG allows */
      --ds-size-2: 2rem;    /* 32px */
      --ds-size-3: 2.5rem;  /* 40px, the one a form uses by default */
      --ds-size-4: 3rem;    /* 48px */
      --ds-size-5: 3.5rem;  /* 56px */
      --ds-size-6: 4rem;    /* 64px */
    }

    /* ── The width the fluid sizes measure against ────────────────────────────
       The window, and something else where something else is the truth.

       Three sizes and one rhythm in this system grow with the room: a headline is
       56 on a phone and 80 on a desk, and it gets there through `vw`. `vw` is the
       WINDOW, which is right on a page and wrong the moment a page is shown inside
       a frame narrower than the window: the docs draw a 430 canvas and the
       headline in it stayed at the desk's 80, one word to a line.

       Container units would read the nearest container, and this package already
       makes five things containers — a header, a toolbar, a pagination — so a
       heading inside any of them would have measured itself against a bar. A
       declared width has one owner and no ambiguity: the window says `100vw` and
       anything drawing a frame says its own width instead.

       Nothing else has to change for it: a page that never sets it behaves exactly
       as it did. */
    :root {
      --ds-viewport: 100vw;
    }

    /* ── The widths a layout changes its mind at ──────────────────────────────
       Four, and each one is here because a component came to use it rather than
       because a device exists. They are declared as tokens so they have a name
       and a page, and they are ALSO written as literals in the queries that ask
       about them, because a container query cannot read a custom property in its
       condition. That is a limitation of CSS and not a choice.

       What keeps the two copies honest is the check: every length inside an
       `@container` in this package has to be one of these values, so a query
       asking about a width nobody named fails the build. */
    :root {
      --ds-break-compact: 30rem;   /* 480: a toolbar loses the room for labels */
      --ds-break-phone: 40rem;     /* 640: wider than every phone, narrower than every tablet */
      --ds-break-wide: 60rem;      /* 960: an auth shell can hold two columns */
      --ds-break-tablet: 64rem;    /* 1024: the widest tablet, so a sidebar stops being a column */
      --ds-break-desktop: 80rem;   /* 1280: where a WIDE column still leaves a page beside it */
    }

    /* ==========================================================================
       Type: the parts that do not change with the surface

       Families and weights live here. Sizes, leadings and trackings do not: they
       are different on web and on product, so they sit in surface.css where both
       columns can be read together.

       There is no public size ramp, and that is the point. A ramp published
       beside the roles is a way to set type without naming it, and the roles lose
       every time. The sizes exist only as the values behind the levels.
       ========================================================================== */

    :root {
      /* Two faces and a monospace. General Sans is drawn for large type and
         Satoshi for reading, and which one a level takes is decided by its size,
         so these two are the whole typographic choice. Space Mono carries code,
         and it is here for the reason a code face exists at all: it is obviously
         not the other two, so a snippet reads as a snippet before it is read as
         text. All three are free to serve, which SF Pro is not: Apple licenses it
         for interfaces, not for redistribution as a webfont.

         The value is the font, not a pointer to somewhere else the font might be
         named. A project that wants different faces re-declares these three, in
         its own stylesheet, and everything follows from there:

           :root { --ds-typeface-display: "Cabinet Grotesk", sans-serif; }

         This used to read a --font-* variable the project was expected to set,
         with these as the fallback. It cost every project a setup step, gave
         every project a way to get that step wrong, and the way it went wrong was
         invisible: a variable read one level too high resolves before the project
         has declared it and freezes the fallback, so the page renders in
         system-ui and nothing complains. An override is the same power without
         the ceremony, and it fails loudly when it fails at all.

         `typeface` and not `font`: `font-` belongs to the text classes,
         `.ds-font-body` being a size, and one prefix meaning two things is the
         thing this system is built to avoid. */
      --ds-typeface-display: "General Sans", system-ui, sans-serif;
      --ds-typeface-body: Satoshi, system-ui, sans-serif;
      --ds-typeface-mono: "Space Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
    }

    :root {
      /* Four weights, named for how the text should feel rather than for a
         number or a CSS keyword.

         `light`, `medium`, `semibold` and `bold` read like promises the typeface
         has to keep, and no two typefaces keep them the same way: one ships a
         Medium at 500 and another calls 500 Book, and a project that wants its
         robust weight to be 400 because its face has no middle cut then has a
         token called `medium` holding 400. The name stops describing anything.

         Named this way, a project re-points the value once in theme.css and every
         level that asked for robust gets what the brand means by robust.

         Four and not five: a fifth step needs a job, and the job the heaviest one
         does is emphasis inside running text, which `intense` already covers. */
      --ds-weight-delicate: 300;
      --ds-weight-standard: 400;
      --ds-weight-robust: 500;
      --ds-weight-intense: 600;
    }

    /* ==========================================================================
       Surfaces

       A surface is a declaration on a subtree that says what kind of screen this
       is. Two of them:

         web       a site, a landing page, an article. Generous.
         product   an admin panel, a dashboard, a form. Dense.

       Density is a property of the surface, not a knob each page turns. The same
       card in a marketing page and in a dashboard is the same card; what differs
       is the room around it, and that is decided once on the shell:

         <body data-surface="product">

       Everything that varies by surface is declared here, both columns side by
       side, so the difference can be read rather than reconstructed from two
       files. Today that is the rhythm. Step 4 adds the type scale to the same
       two blocks.

       ── The trap ─────────────────────────────────────────────────────────────
       A var() inside a custom property is substituted WHERE THE PROPERTY IS
       DECLARED, not where it is used. So this looks right and is not:

         :root                      { --ds-pad: var(--ds-rhythm-section); }
         [data-surface="product"]   { --ds-rhythm-section: 2rem; }

       `--ds-pad` resolved at :root, froze the web value, and every descendant
       inherits it already resolved. The product block changes the rhythm and the
       alias never notices. The other system shipped this twice, once on font
       families and once on section padding, and both times the symptom was the
       same: half the values switch with the surface and half do not, which reads
       as a random bug rather than a systematic one.

       The rule that avoids it: **an alias of a surface-varying token must be
       re-declared inside every surface block.** If you cannot re-declare it,
       do not alias it.
       ========================================================================== */

    /* Web is the default, and is also written out explicitly so a subtree can go
       back to it. Without the second selector, a web island inside a product
       shell is impossible: product would win by inheritance and there would be
       no way to say "this part is not a panel".

       `body` used to be in this list too, because the face tokens read a variable
       the project declared on body and an alias resolved at :root froze the
       fallback. The faces hold their own values now, so there is nothing left to
       resolve too early and `data-surface` goes anywhere, html included. */
    :root,
    [data-surface="web"] {
      /* line     between two lines of ONE thing: a name and its value, a label
                   and the number under it
         element  between the parts of one thing: a label and its field, an
                   overline and the title under it
         block    between two things in the same section
         section  between sections

         `line` is the same on both surfaces and the other three are not, which is
         deliberate. The other three are density, and density is what a surface
         decides. This one is typography: two lines sit close because they are the
         same item, and that reason does not change between a landing page and a
         panel. It was added when the colour page put a token name, its two hex
         values and its note 16px apart and they read as three separate things. */
      --ds-rhythm-line: var(--ds-space-1);      /* 4px */
      --ds-rhythm-element: var(--ds-space-4);   /* 16px */
      --ds-rhythm-block: var(--ds-space-12);    /* 48px */
      --ds-rhythm-section: clamp(5rem, calc(var(--ds-viewport) * 0.12), 10rem);

      /* ── Type on web ────────────────────────────────────────────────────────
         Headings are fluid: a marquee title that does not grow with the viewport
         reads as a mistake on a wide screen.

         The face follows the SIZE, not the surface and not the role. The two
         faces are drawn for different ranges: display is the tighter one, made
         for large type, and body is opened up so small sizes stay readable. Apple
         sets that boundary at 20pt for its own pair and so does this: a heading
         at or above 20 takes display, below it takes body.

         Running text stays on the body face at every size, including the 20px
         lede. The boundary is a guide, and on a paragraph the openness is worth
         more than the tightening.

         ── The leading is a step, not a ratio ─────────────────────────────────
         Every fixed size carries its leading in rem, and every one of those is a
         multiple of 4, the same step the space ramp is built on. As ratios they
         were 1.55 and 1.5 and 1.6, which put a 14px label in a 21.7px box: two
         stacked lines then sat 10.9px apart, a number the ramp cannot produce and
         nobody can reason about.

         In rem and not in px, so the whole ladder scales with `data-text` and
         stays in step with the space ramp, which is also in rem. The three fluid
         sizes keep a ratio, because a leading that does not move with a clamped
         size is a leading that is wrong at one end of it. */
      --ds-font-heading-1-face: var(--ds-typeface-display);
      --ds-font-heading-1-size: clamp(3.5rem, calc(var(--ds-viewport) * 0.05), 5rem);       /* 56 -> 80 */
      --ds-font-heading-1-weight: var(--ds-weight-standard);
      --ds-font-heading-1-leading: 1.05;
      --ds-font-heading-1-tracking: -0.03em;

      --ds-font-heading-2-face: var(--ds-typeface-display);
      --ds-font-heading-2-size: clamp(2.5rem, calc(var(--ds-viewport) * 0.035), 3.5rem);   /* 40 -> 56 */
      --ds-font-heading-2-weight: var(--ds-weight-standard);
      --ds-font-heading-2-leading: 1.1;
      --ds-font-heading-2-tracking: -0.025em;

      --ds-font-heading-3-face: var(--ds-typeface-display);
      --ds-font-heading-3-size: clamp(1.875rem, calc(var(--ds-viewport) * 0.02), 2.25rem);  /* 30 -> 36 */
      --ds-font-heading-3-weight: var(--ds-weight-standard);
      --ds-font-heading-3-leading: 1.15;
      --ds-font-heading-3-tracking: -0.02em;

      /* Levels 4 to 6 are the functional headings: a card title, a panel title, a
         field group, and the deep levels of an article. Fixed rather than fluid,
         because none of them is editorial.

         The face follows the SIZE and not the level: 20px and up takes the
         display face, below it the body face. So it lands on 6 here and on 4 in
         the product column, and the same class can be right in both places. Nobody
         flips it, which is the point: a functional heading arriving in editorial
         clothes is what put an editorial typeface on 75 admin panels in the other
         system.

         Six and not four. The first version of this file stopped at four,
         reasoning from zero uses of h4-h6 across twenty projects. That measured
         the markup DESIGNERS write and applied it to the markup AUTHORS write,
         and h4 to h6 only ever appear in the second: an article, a CMS body,
         markdown. Collapsing them onto one level flattens a hierarchy exactly
         where the person who wrote it had no say.

         The zero was also read wrong. It said the number was wrong, not that the
         level was unwanted: h4 over there was 28 to 40px, which is absurd for a
         fourth level, so of course nobody reached for it. */
      --ds-font-heading-4-face: var(--ds-typeface-display);
      --ds-font-heading-4-size: 1.5rem;                         /* 24 */
      --ds-font-heading-4-weight: var(--ds-weight-robust);
      --ds-font-heading-4-leading: 2rem;   /* 32 on 24 */
      --ds-font-heading-4-tracking: -0.01em;

      --ds-font-heading-5-face: var(--ds-typeface-display);
      --ds-font-heading-5-size: 1.25rem;                        /* 20 */
      --ds-font-heading-5-weight: var(--ds-weight-robust);
      --ds-font-heading-5-leading: 1.75rem;   /* 28 on 20 */
      --ds-font-heading-5-tracking: 0;

      --ds-font-heading-6-face: var(--ds-typeface-body);
      --ds-font-heading-6-size: 1.125rem;                       /* 18 */
      --ds-font-heading-6-weight: var(--ds-weight-robust);
      --ds-font-heading-6-leading: 1.5rem;   /* 24 on 18 */
      --ds-font-heading-6-tracking: 0;

      /* Running text. Level 3 is 14px because that is where the largest group of
         hand-built text sat, and the other scale had nothing there. */
      --ds-font-copy-size: clamp(1.125rem, calc(var(--ds-viewport) * 0.003 + 1.05rem), 1.25rem); /* 18 -> 20 */
      --ds-font-copy-leading: 1.5rem;   /* 24, and a length rather than the ratio it
                                           was: a ratio on a clamped size gives a
                                           line that is off the 4px step everywhere
                                           between the two ends. */
      --ds-font-body-size: 1rem;                       /* 16 */
      --ds-font-body-leading: 1.25rem;   /* 20 on 16 */
      /* The same two sizes as PARAGRAPHS. The pairs above are UI lines, and their
         leading is what every mark in the package measures itself against: a body
         row keeps a 20px mark beside a 20px line. Running text wants air a row of
         controls cannot afford, so the prose pair holds the same size on a looser
         line, and marks never stand beside it. Owner's call, 22 August 2026. */
      --ds-font-copy-prose-size: clamp(1.125rem, calc(var(--ds-viewport) * 0.003 + 1.05rem), 1.25rem); /* 18 -> 20 */
      --ds-font-copy-prose-leading: 1.75rem;   /* 28 */
      --ds-font-body-prose-size: 1rem;                 /* 16 */
      --ds-font-body-prose-leading: 1.5rem;   /* 24 on 16 */
      --ds-font-label-size: 0.875rem;                   /* 14 */
      --ds-font-label-leading: 1rem;   /* 16 on 14 */
      --ds-font-small-size: 0.75rem;                    /* 12 */
      --ds-font-small-leading: 1rem;   /* 16 on 12 */
      /* Code, in the code face, at small's measures: a shortcut at the end of a
         menu line, a snippet, a file's name. Named 4 September 2026 because five
         components wanted the monospace and, with no role to read, reached for
         the primitive, which is the one thing a component may not do. */
      --ds-font-mono-size: 0.75rem;                     /* 12 */
      --ds-font-mono-leading: 1rem;   /* 16 on 12 */


      /* A role is a complete set, not a size and a leading with the rest borrowed.
         Every one of these points at a primitive and every component reads the
         ROLE: that is what makes a role's face or weight movable without touching
         the scale, and what stops a reading of a paragraph naming the primitive
         where the source meant `copy`.
         The headings had all four from the start. These four had two. */
      --ds-font-copy-face: var(--ds-typeface-body);
      --ds-font-copy-weight: var(--ds-weight-standard);
      --ds-font-body-face: var(--ds-typeface-body);
      --ds-font-body-weight: var(--ds-weight-standard);
      --ds-font-copy-prose-face: var(--ds-typeface-body);
      --ds-font-copy-prose-weight: var(--ds-weight-standard);
      --ds-font-body-prose-face: var(--ds-typeface-body);
      --ds-font-body-prose-weight: var(--ds-weight-standard);
      --ds-font-label-face: var(--ds-typeface-body);
      --ds-font-label-weight: var(--ds-weight-standard);
      --ds-font-small-face: var(--ds-typeface-body);
      --ds-font-small-weight: var(--ds-weight-standard);
      --ds-font-mono-face: var(--ds-typeface-mono);
      --ds-font-mono-weight: var(--ds-weight-standard);
      --ds-font-overline-face: var(--ds-typeface-body);
      /* The one that is not standard: an overline is a label in capitals with the
         tracking opened, and at that size and that spacing the standard weight
         reads as a whisper. */
      --ds-font-overline-weight: var(--ds-weight-robust);
      --ds-font-overline-size: 0.875rem;                 /* 14 */
      /* A role is a pair, and this one was half of one: everything that set an
         overline reached for `label`'s leading, which is the same 16 and is
         nobody's decision. Named, it is a decision. */
      --ds-font-overline-leading: 1rem;   /* 16 on 14 */
      --ds-font-overline-tracking: 0.08em;

      /* ── Micro: the metadata voice, one step under everything ───────────────
         The category and the date over a title, the key over a figure. The first
         consumer cut it by hand a rung under `small` and wrote down why: in
         capitals with the tracking this wide, a 10px line reads larger than its
         nominal size, and at 12 those lines compete with the title they are
         meant to introduce. The overline stays the sentence-level eyebrow; this
         is the one for rows of metadata, and it is the same on both surfaces
         because there is nothing under it to step down to. */
      --ds-font-micro-face: var(--ds-typeface-body);
      --ds-font-micro-weight: var(--ds-weight-robust);
      --ds-font-micro-size: 0.625rem;                    /* 10 */
      --ds-font-micro-leading: 0.75rem;   /* 12 on 10 */
      --ds-font-micro-tracking: 0.16em;

      /* ── Two weights that are not a role's, and are not a component's ────────
         Nineteen rules in this package set a weight from the primitive, and they
         were saying two things between them.

         Eight said "this is the NAME of the thing": a card's title, a dialog's
         title, a field's label, a table's header cell, `strong` in prose, a
         button's words. Seven said "this is the one you are ON": every
         `[aria-current]` in the header, the sidebar, the tabs, the tab bar, the
         breadcrumb, the language switcher and the link.

         Written as `--ds-weight-robust` in nineteen places that is one decision
         copied nineteen times, and moving it means finding all nineteen. It also
         reports itself back as the primitive to anybody inspecting a card title,
         which says the component reached past the system rather than into it.

         Both are per-surface, like every other semantic here: a surface that
         wanted a heavier hand would move these and nothing else. */
      /* The four text roles get a tracking too, and it is `0`.
         The headings have carried one from the start and these four never did, so
         anything taking `body`'s size on an element the tag types as a heading
         kept the HEADING's tracking: a card's title is 16px body with -0.32px on
         it, which is the spacing drawn for 32px type. A role is face, size,
         leading, weight and tracking, and four of them were missing the fifth.
         Zero is a decision here, not an absence: text at these sizes is set as
         drawn and the value says so. */
      --ds-font-copy-tracking: 0;
      --ds-font-body-tracking: 0;
      --ds-font-copy-prose-tracking: 0;
      --ds-font-body-prose-tracking: 0;
      --ds-font-label-tracking: 0;
      --ds-font-small-tracking: 0;
      --ds-font-mono-tracking: 0;
      --ds-emphasis-weight: var(--ds-weight-robust);
      --ds-current-weight: var(--ds-weight-robust);
      /* The weight of a `<strong>`, which is a different job from the two above.
         Those are UI emphasis — the row you are on, the head of an accordion —
         and they sit alone, so 500 reads. This one is emphasis INSIDE running
         text, where 500 among 400 is a word slightly darker than the others.

         It exists as a name because a project can want a bolder bold without
         moving everything else `intense` dresses. The first one did: on a site
         that spends `intense` on its overlines and its buttons, taking the
         primitive to 700 would have taken those with it, and a 700 button is a
         different button. */
      --ds-bold-weight: var(--ds-weight-intense);
    }

    [data-surface="product"] {
      --ds-rhythm-line: var(--ds-space-1);      /* 4px, as on web */
      --ds-rhythm-element: var(--ds-space-3);   /* 12px */
      --ds-rhythm-block: var(--ds-space-6);     /* 24px */
      --ds-rhythm-section: var(--ds-space-8);   /* 32px */

      /* ── Type on product ────────────────────────────────────────────────────
         No clamp anywhere: density is a product decision, not the viewport's.

         The same rule applies, so the split lands in a different place because
         the sizes are different: heading 1 to 3 are 28, 24 and 20 and take the
         display face, and 4 to 6 are below the boundary and take the body face.

         This reads as a reversal of "the body face on a data screen" and is not.
         That rule guarded against a heading being dressed up because of what it
         is called. Here nothing is dressed up by name: a 28px title on a panel
         gets the face drawn for 28px, which is the same answer the web column
         gives at the same size. */
      --ds-font-heading-1-face: var(--ds-typeface-display);
      --ds-font-heading-1-size: 1.75rem;                        /* 28 */
      --ds-font-heading-1-weight: var(--ds-weight-intense);
      --ds-font-heading-1-leading: 2.25rem;   /* 36 on 28 */
      --ds-font-heading-1-tracking: -0.01em;

      --ds-font-heading-2-face: var(--ds-typeface-display);
      --ds-font-heading-2-size: 1.5rem;                         /* 24 */
      --ds-font-heading-2-weight: var(--ds-weight-robust);
      --ds-font-heading-2-leading: 2rem;   /* 32 on 24 */
      --ds-font-heading-2-tracking: -0.01em;

      --ds-font-heading-3-face: var(--ds-typeface-display);
      --ds-font-heading-3-size: 1.25rem;                        /* 20 */
      --ds-font-heading-3-weight: var(--ds-weight-robust);
      --ds-font-heading-3-leading: 1.75rem;   /* 28 on 20 */
      --ds-font-heading-3-tracking: -0.005em;

      --ds-font-heading-4-face: var(--ds-typeface-body);
      --ds-font-heading-4-size: 1.125rem;                       /* 18 */
      --ds-font-heading-4-weight: var(--ds-weight-robust);
      --ds-font-heading-4-leading: 1.5rem;   /* 24 on 18 */
      --ds-font-heading-4-tracking: 0;

      --ds-font-heading-5-face: var(--ds-typeface-body);
      --ds-font-heading-5-size: 1rem;                           /* 16 */
      --ds-font-heading-5-weight: var(--ds-weight-robust);
      --ds-font-heading-5-leading: 1.5rem;   /* 24 on 16 */
      --ds-font-heading-5-tracking: 0;

      --ds-font-heading-6-face: var(--ds-typeface-body);
      --ds-font-heading-6-size: 0.875rem;                       /* 14 */
      --ds-font-heading-6-weight: var(--ds-weight-intense);
      --ds-font-heading-6-leading: 1.25rem;   /* 20 on 14 */
      --ds-font-heading-6-tracking: 0.01em;

      /* Reading sizes hold still: a timestamp is 12px on both surfaces. Only the
         lede loses its clamp, because a panel has no viewport to grow into. */
      --ds-font-copy-size: 1.125rem;                   /* 18 */
      --ds-font-copy-leading: 1.5rem;   /* 24 on 18 */
      --ds-font-body-size: 1rem;                       /* 16 */
      --ds-font-body-leading: 1.25rem;   /* 20 on 16 */
      /* The prose pair, as on web: same sizes, looser line, for running text. */
      --ds-font-copy-prose-size: 1.125rem;             /* 18 */
      --ds-font-copy-prose-leading: 1.75rem;   /* 28 on 18 */
      --ds-font-body-prose-size: 1rem;                 /* 16 */
      --ds-font-body-prose-leading: 1.5rem;   /* 24 on 16 */
      --ds-font-label-size: 0.875rem;                   /* 14 */
      --ds-font-label-leading: 1rem;   /* 16 on 14 */
      --ds-font-small-size: 0.75rem;                    /* 12 */
      --ds-font-small-leading: 1rem;   /* 16 on 12 */
      /* Code, in the code face, at small's measures: a shortcut at the end of a
         menu line, a snippet, a file's name. Named 4 September 2026 because five
         components wanted the monospace and, with no role to read, reached for
         the primitive, which is the one thing a component may not do. */
      --ds-font-mono-size: 0.75rem;                     /* 12 */
      --ds-font-mono-leading: 1rem;   /* 16 on 12 */


      /* A role is a complete set, not a size and a leading with the rest borrowed.
         Every one of these points at a primitive and every component reads the
         ROLE: that is what makes a role's face or weight movable without touching
         the scale, and what stops a reading of a paragraph naming the primitive
         where the source meant `copy`.
         The headings had all four from the start. These four had two. */
      --ds-font-copy-face: var(--ds-typeface-body);
      --ds-font-copy-weight: var(--ds-weight-standard);
      --ds-font-body-face: var(--ds-typeface-body);
      --ds-font-body-weight: var(--ds-weight-standard);
      --ds-font-copy-prose-face: var(--ds-typeface-body);
      --ds-font-copy-prose-weight: var(--ds-weight-standard);
      --ds-font-body-prose-face: var(--ds-typeface-body);
      --ds-font-body-prose-weight: var(--ds-weight-standard);
      --ds-font-label-face: var(--ds-typeface-body);
      --ds-font-label-weight: var(--ds-weight-standard);
      --ds-font-small-face: var(--ds-typeface-body);
      --ds-font-small-weight: var(--ds-weight-standard);
      --ds-font-mono-face: var(--ds-typeface-mono);
      --ds-font-mono-weight: var(--ds-weight-standard);
      --ds-font-overline-face: var(--ds-typeface-body);
      /* The one that is not standard: an overline is a label in capitals with the
         tracking opened, and at that size and that spacing the standard weight
         reads as a whisper. */
      --ds-font-overline-weight: var(--ds-weight-robust);
      --ds-font-overline-size: 0.8125rem;                /* 13 */
      --ds-font-overline-leading: 1rem;   /* 16 on 13 */
      --ds-font-overline-tracking: 0.08em;

      /* Micro, as on web: there is nothing under it to step down to, so density
         has nothing to take away. */
      --ds-font-micro-face: var(--ds-typeface-body);
      --ds-font-micro-weight: var(--ds-weight-robust);
      --ds-font-micro-size: 0.625rem;                    /* 10 */
      --ds-font-micro-leading: 0.75rem;   /* 12 on 10 */
      --ds-font-micro-tracking: 0.16em;

      /* ── Two weights that are not a role's, and are not a component's ────────
         Nineteen rules in this package set a weight from the primitive, and they
         were saying two things between them.

         Eight said "this is the NAME of the thing": a card's title, a dialog's
         title, a field's label, a table's header cell, `strong` in prose, a
         button's words. Seven said "this is the one you are ON": every
         `[aria-current]` in the header, the sidebar, the tabs, the tab bar, the
         breadcrumb, the language switcher and the link.

         Written as `--ds-weight-robust` in nineteen places that is one decision
         copied nineteen times, and moving it means finding all nineteen. It also
         reports itself back as the primitive to anybody inspecting a card title,
         which says the component reached past the system rather than into it.

         Both are per-surface, like every other semantic here: a surface that
         wanted a heavier hand would move these and nothing else. */
      /* The four text roles get a tracking too, and it is `0`.
         The headings have carried one from the start and these four never did, so
         anything taking `body`'s size on an element the tag types as a heading
         kept the HEADING's tracking: a card's title is 16px body with -0.32px on
         it, which is the spacing drawn for 32px type. A role is face, size,
         leading, weight and tracking, and four of them were missing the fifth.
         Zero is a decision here, not an absence: text at these sizes is set as
         drawn and the value says so. */
      --ds-font-copy-tracking: 0;
      --ds-font-body-tracking: 0;
      --ds-font-copy-prose-tracking: 0;
      --ds-font-body-prose-tracking: 0;
      --ds-font-label-tracking: 0;
      --ds-font-small-tracking: 0;
      --ds-font-mono-tracking: 0;
      --ds-emphasis-weight: var(--ds-weight-robust);
      --ds-current-weight: var(--ds-weight-robust);
      --ds-bold-weight: var(--ds-weight-intense);
    }

    /* ── A box that is not the window ─────────────────────────────────────────
       The five sizes above grow with `--ds-viewport`, and they are declared on
       `:root`, where a `var()` inside a custom property is substituted. So the
       window's width is baked into them before any element sees them, and a frame
       further down that says `--ds-viewport: 430px` changes nothing at all: the
       headline in a 430 canvas came out drawn for a desk, one word to a line.

       That is how custom properties work and not a bug to fix. What closes it is
       declaring the five again on the box itself, where the smaller width is the
       one in scope. One class, opt-in, and nothing changes for a page that never
       uses it.

       Written out rather than derived, because there is no way in CSS to say "the
       same formula, resolved here". The check `la scala fluida e' la stessa in
       entrambi i posti` compares the two lists character for character, so they
       cannot drift.

       Not for `product`: that surface has no clamp anywhere, because density
       there is a decision and not a width. */
    .ds-viewport-scope {
      --ds-rhythm-section: clamp(5rem, calc(var(--ds-viewport) * 0.12), 10rem);
      --ds-font-heading-1-size: clamp(3.5rem, calc(var(--ds-viewport) * 0.05), 5rem);
      --ds-font-heading-2-size: clamp(2.5rem, calc(var(--ds-viewport) * 0.035), 3.5rem);
      --ds-font-heading-3-size: clamp(1.875rem, calc(var(--ds-viewport) * 0.02), 2.25rem);
      --ds-font-copy-size: clamp(1.125rem, calc(var(--ds-viewport) * 0.003 + 1.05rem), 1.25rem);
      --ds-font-copy-prose-size: clamp(1.125rem, calc(var(--ds-viewport) * 0.003 + 1.05rem), 1.25rem);
    }

    /* And the same box inside a product surface keeps the product's sizes, which
       are fixed. Without this the class would put the web scale on a panel. The
       admin vocabulary needs no line here: its roots carry
       `data-surface="product"` in the markup, so a head that calls itself admin
       is a product's wherever it stands. */
    [data-surface="product"] .ds-viewport-scope,
    .ds-viewport-scope[data-surface="product"] {
      --ds-rhythm-section: var(--ds-space-8);
      --ds-font-heading-1-size: 1.75rem;
      --ds-font-heading-2-size: 1.5rem;
      --ds-font-heading-3-size: 1.25rem;
      --ds-font-copy-size: 1.125rem;
      --ds-font-copy-prose-size: 1.125rem;
    }

    /* ==========================================================================
       Shape

       A radius ramp, and four modes that re-scale it for a whole project.

       How round a product is reads as personality: the same dashboard feels
       engineered at 0 and friendly at 16. That is one decision, taken once on the
       shell, not a value argued over per component:

         <body data-radius="square">

       Every component reads the ramp, so every component follows. `round` is the
       default and is written out as well, so a subtree can go back to it.

       Radius is its own axis, deliberately not folded into data-surface. How
       round a brand is does not change between its marketing page and its
       dashboard; how dense it is does. Tying them would make "soft and dense"
       unsayable.

       Two steps never move. `--ds-radius-0` is square by definition, and
       `--ds-radius-full` is the circle and the pill: an avatar, a radio dot, a
       toggle thumb are round because of what they are, not because of how the
       brand feels this year. Re-scaling those would turn a circle into a squircle
       under `square`, which nobody asks for.
       ========================================================================== */

    :root {
      --ds-radius-0: 0;
      --ds-radius-full: 9999px;

      /* One lever a brand turns to move the whole system without touching a
         component. Every step below is a multiple of it, so scaling it scales
         everything in proportion. */
      --ds-radius: 0.5rem; /* 8px */
    }

    :root,
    [data-radius="round"] {
      --ds-radius-1: calc(var(--ds-radius) * 0.5);  /*  4px */
      --ds-radius-2: var(--ds-radius);              /*  8px */
      --ds-radius-3: calc(var(--ds-radius) * 1.5);  /* 12px */
      --ds-radius-4: calc(var(--ds-radius) * 2);    /* 16px */
    }

    /* Drawn, not softened. Everything square except the things that are circles. */
    [data-radius="square"] {
      --ds-radius-1: 0;
      --ds-radius-2: 0;
      --ds-radius-3: 0;
      --ds-radius-4: 0;
    }

    /* Corners taken off, no more. Half the default at every step, so the steps
       stay distinguishable instead of collapsing onto the same 2px. */
    [data-radius="soft"] {
      --ds-radius-1: calc(var(--ds-radius) * 0.25); /* 2px */
      --ds-radius-2: calc(var(--ds-radius) * 0.5);  /* 4px */
      --ds-radius-3: calc(var(--ds-radius) * 0.75); /* 6px */
      --ds-radius-4: var(--ds-radius);              /* 8px */
    }

    /* Generous. Three times the default, which is where a card stops being a
       rectangle with soft corners and starts reading as a lozenge. */
    [data-radius="pill"] {
      --ds-radius-1: calc(var(--ds-radius) * 1.5);  /* 12px */
      --ds-radius-2: calc(var(--ds-radius) * 3);    /* 24px */
      --ds-radius-3: calc(var(--ds-radius) * 4.5);  /* 36px */
      --ds-radius-4: calc(var(--ds-radius) * 6);    /* 48px */
    }

    /* ==========================================================================
       Motion

       Durations and easings. Three durations for transitions, because a fourth
       invites a debate nobody wins, and one for a loop.

       A loop is not a transition and cannot borrow one of the three: 400ms is a
       long transition and a frantic spin. It sits here anyway rather than inside
       the spinner, so that reduced motion keeps being handled in one place.

       Reduced motion is handled here, once, by zeroing the durations. A component
       that reads these tokens gets it for free and never writes its own media
       query; a component that hardcodes 200ms opts every one of its users out of
       a preference they set on purpose.
       ========================================================================== */

    :root {
      /* 100ms is the length of a small MOVEMENT: the eye follows the thing and the
         time is only there to stop it teleporting. It is too short for a colour,
         where there is nothing to follow and the whole of the change is the fade,
         which is why every hover in this system fades over the next one up. */
      --ds-duration-fast: 100ms;
      --ds-duration: 200ms;
      --ds-duration-slow: 400ms;
      --ds-duration-loop: 700ms;   /* one turn of something that repeats */

      --ds-ease: cubic-bezier(0.4, 0, 0.2, 1);
      --ds-ease-out: cubic-bezier(0, 0, 0.2, 1);
      --ds-ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
    }

    @media (prefers-reduced-motion: reduce) {
      :root {
        --ds-duration-fast: 0s;
        --ds-duration: 0s;
        --ds-duration-slow: 0s;
        --ds-duration-loop: 0s;
      }
    }

    /* ==========================================================================
       Accessibility: two axes, both independent of everything else

       Reading needs and screen kind are different questions. A dashboard can need
       large text exactly as much as an article can, and folding either of these
       into `data-surface` would make "dense panel, large text" unsayable. Same
       reason radius is its own axis.

       They are also independent of EACH OTHER. Someone who needs 200% text
       usually has no trouble with a grey caption, and someone who needs contrast
       often reads at the normal size. One combined "accessible mode" would force
       both on anyone who needs one.

         <html data-text="large" data-contrast="high">

       Both belong on `<html>`. `data-text` has to be there because that is the
       element `rem` is measured from; `data-contrast` has to be at or above every
       element it recolours, and html is the only place that is always true.
       ========================================================================== */

    /* ── Text size ───────────────────────────────────────────────────────────
       One declaration for the whole system, because every size in it is in `rem`.
       Type, spacing, radius and the gaps between them all scale together, which
       is the difference between large text and large text crammed into the layout
       it had before.

       A percentage and not a pixel value: the reader's own browser setting is the
       100%, so this multiplies their choice instead of overwriting it. Someone
       who already reads at 20px gets 30px at `large`, not 24px.

       The fluid headings keep working. `clamp(3.5rem, 5vw, 5rem)` has its floor
       in rem, so at 115% the floor becomes 64px and wins over a viewport term
       that did not move: the heading grows even where the viewport did not.

       115 and 130, which is smaller than it sounds and was arrived at by looking.
       Everything scales together here, headings included, so a step that reads as
       reasonable on a paragraph is already violent on an 80px title: at 200% the
       first heading passed 150px and left three words on the screen. The reader
       who needs more than 130% has the browser's own zoom, which this multiplies
       rather than replaces. */
    [data-text="large"] {
      font-size: 115%;
    }

    [data-text="huge"] {
      font-size: 130%;
    }

    /* ── Contrast ────────────────────────────────────────────────────────────
       The three text neutrals move up one step: secondary takes what primary had,
       tertiary takes what secondary had. Nothing new is invented, no palette is
       duplicated, and no value can be worse than the one it replaces because it
       is already the darker one on light and the lighter one on dark.

       What it costs is the distinction between primary and secondary, which
       becomes nothing. That is the correct trade rather than a shortcut: a reader
       who cannot separate #52525b from its background is not helped by keeping a
       hierarchy they cannot see, and hierarchy survives in size, weight and
       position, which do not depend on contrast at all.

       Tertiary is the one that mattered. It is documented as decorative and does
       not reach 4.5:1 on every surface, and here it does. Measured per theme and
       per surface by `npm run check`, which fails below the floor rather than
       trusting the reasoning above.

       Follows the operating system when the reader has not said otherwise: on
       macOS this is Increase Contrast, on Windows the contrast themes. An
       explicit `normal` wins over the query, which is what lets a preview switch
       it back off. */
    @media (prefers-contrast: more) {
      :root:not([data-contrast="normal"]) {
        --ds-text-secondary-neutral: var(--ds-ink-1);
        --ds-text-tertiary-neutral: var(--ds-ink-2);
      }
    }

    [data-contrast="high"] {
      --ds-text-secondary-neutral: var(--ds-ink-1);
      --ds-text-tertiary-neutral: var(--ds-ink-2);
    }


}
@layer base {
  /* base. Empty until its step opens. See ROADMAP.md. */

    /* ==========================================================================
       The three faces, fetched by the package.

       General Sans and Satoshi are on Fontshare, and these rules are its variable
       cut: one file per family covering the whole weight axis, so 600 is a real
       600 and not the browser picking the nearest static weight it was handed.
       Space Mono is on Google and is not variable, so it arrives as the two
       weights and the one italic that code actually uses.

       Why the rules are copied here instead of an @import of Fontshare's own
       stylesheet: a bundler eats the @import. Measured, not assumed. The package
       shipped with `@import url("https://api.fontshare.com/...")` as the first
       line of dist/os.css, correct CSS by itself, and Next dropped it from the
       built chunk without a word. Every face resolved by name to a font nobody
       had loaded, which renders as system-ui and reports nothing. @font-face
       survives the same pipeline intact.

       Why not the files themselves: they would have to be redistributed, and
       pinned, and updated by hand. These URLs are Fontshare's own delivery, the
       same ones its stylesheet hands out.

       Those URLs are content-hashed, so they can rotate. `npm run check` requests
       each one and fails if it does not answer, which turns a silent fallback to
       system-ui into a build that stops.

       A project that wants other faces overrides --ds-typeface-* in tokens/type.css
       and pays one unused request for these. A project that wants to serve them
       itself declares its own @font-face for the same families and wins by order.
       ========================================================================== */

    @font-face {
      font-family: "General Sans";
      src: url("https://cdn.fontshare.com/wf/LHQJ5KSAL7VGAEIDSTEXCCOIUKFLT2I6/GW57XUEG4ZBVMLZZTQZTGYPROITRRQ5W/JA3IZUEMJ2J6WWT2OQVJOAWDXO3YL4YG.woff2") format("woff2");
      font-weight: 200 700;
      font-style: normal;
      font-display: swap;
    }

    @font-face {
      font-family: "General Sans";
      src: url("https://cdn.fontshare.com/wf/RRO6TYZXO7WQBHSH4BZYSTI7E7T2HKLF/55LXPUDQQQTSLB3M2T377FIN2EKQQWJY/3HUYMY6WP3BZQYPOVGG6IZQAM476CQDC.woff2") format("woff2");
      font-weight: 200 700;
      font-style: italic;
      font-display: swap;
    }

    @font-face {
      font-family: "Satoshi";
      src: url("https://cdn.fontshare.com/wf/NWBQYJIM7GCZ5XWD7D26ARB3VDY55ZRT/K63EV2KZIGKLE7RANQ2U42S6SVHU5RJ7/X6XYTKIVDUW7GZTZPZNN4EUM5KH54KHF.woff2") format("woff2");
      font-weight: 300 900;
      font-style: normal;
      font-display: swap;
    }

    @font-face {
      font-family: "Satoshi";
      src: url("https://cdn.fontshare.com/wf/3JZIFUW7KLFSPLVCIUI2DLGNIER6IFCX/MMP4RUF5LYGUAZS3DCJC3GJ6F6SAGKSN/E3NEPGEAHYCHR37DSBXHV33XCYYRE2CY.woff2") format("woff2");
      font-weight: 300 900;
      font-style: italic;
      font-display: swap;
    }

    /* Space Mono, latin and latin-ext. The ranges are Google's own and they are
       what keeps this cheap: a page of English code downloads one file, and the
       accented file arrives only if an accent is on the page. */
    @font-face {
      font-family: "Space Mono";
      src: url("https://fonts.gstatic.com/s/spacemono/v17/i7dPIFZifjKcF5UAWdDRYEF8RXi4EwQ.woff2") format("woff2");
      font-weight: 400;
      font-style: normal;
      font-display: swap;
      unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
    }

    @font-face {
      font-family: "Space Mono";
      src: url("https://fonts.gstatic.com/s/spacemono/v17/i7dPIFZifjKcF5UAWdDRYE98RXi4EwSsbg.woff2") format("woff2");
      font-weight: 400;
      font-style: normal;
      font-display: swap;
      unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
    }

    @font-face {
      font-family: "Space Mono";
      src: url("https://fonts.gstatic.com/s/spacemono/v17/i7dMIFZifjKcF5UAWdDRaPpZUFWaHi6WZ3Q.woff2") format("woff2");
      font-weight: 700;
      font-style: normal;
      font-display: swap;
      unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
    }

    @font-face {
      font-family: "Space Mono";
      src: url("https://fonts.gstatic.com/s/spacemono/v17/i7dMIFZifjKcF5UAWdDRaPpZUFuaHi6WZ3S_Yg.woff2") format("woff2");
      font-weight: 700;
      font-style: normal;
      font-display: swap;
      unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
    }

    @font-face {
      font-family: "Space Mono";
      src: url("https://fonts.gstatic.com/s/spacemono/v17/i7dNIFZifjKcF5UAWdDRYERMR3K_MQacbw.woff2") format("woff2");
      font-weight: 400;
      font-style: italic;
      font-display: swap;
      unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
    }

    /* ==========================================================================
       Base: text elements

       Defaults for bare elements, so markup nobody wrote by hand still reads:
       CMS output, markdown, a pasted article. Everything here is overridable by
       a role, because the roles live in a later layer.

       Each element maps onto its own level, one to one. An earlier version pointed
       h4, h5 and h6 at the same level, which rendered three different levels
       identically. That is worst precisely here, in the base layer, because this
       layer exists for markup nobody wrote by hand: a CMS body or an article,
       where the author chose the level and cannot choose the style.
       ========================================================================== */

    body {
      /* The browser's own eight pixels, cleared. Everything the system draws to
         an edge is drawn to the window's edge: a header, a bar at the foot, a
         sheet. With the default in place they all stopped short and left a band
         of the page showing round the outside, which reads as a page inside a
         page. Anything that should stay inside a measure holds its own width. */
      margin: 0;
      font-family: var(--ds-typeface-body);
      font-size: var(--ds-font-body-size);
      line-height: var(--ds-font-body-leading);
      color: var(--ds-text-primary-neutral);
      background-color: var(--ds-surface-0);
      -webkit-font-smoothing: antialiased;
    }

    h1 { font: var(--ds-font-heading-1-weight) var(--ds-font-heading-1-size) / var(--ds-font-heading-1-leading) var(--ds-font-heading-1-face); letter-spacing: var(--ds-font-heading-1-tracking); }
    h2 { font: var(--ds-font-heading-2-weight) var(--ds-font-heading-2-size) / var(--ds-font-heading-2-leading) var(--ds-font-heading-2-face); letter-spacing: var(--ds-font-heading-2-tracking); }
    h3 { font: var(--ds-font-heading-3-weight) var(--ds-font-heading-3-size) / var(--ds-font-heading-3-leading) var(--ds-font-heading-3-face); letter-spacing: var(--ds-font-heading-3-tracking); }
    h4 { font: var(--ds-font-heading-4-weight) var(--ds-font-heading-4-size) / var(--ds-font-heading-4-leading) var(--ds-font-heading-4-face); letter-spacing: var(--ds-font-heading-4-tracking); }
    h5 { font: var(--ds-font-heading-5-weight) var(--ds-font-heading-5-size) / var(--ds-font-heading-5-leading) var(--ds-font-heading-5-face); letter-spacing: var(--ds-font-heading-5-tracking); }
    h6 { font: var(--ds-font-heading-6-weight) var(--ds-font-heading-6-size) / var(--ds-font-heading-6-leading) var(--ds-font-heading-6-face); letter-spacing: var(--ds-font-heading-6-tracking); }

    h1, h2, h3, h4, h5, h6 {
      margin: 0;
      color: inherit;
    }

    /* Bold is one weight in this system, and the browser's is not it. Left alone,
       `<strong>` renders at 700 everywhere except inside `.ds-prose`, which sets
       it from a token — so the same word was one weight in an article body and
       another in a summary beside it, and neither was the one the system chose.
       This layer exists for markup nobody wrote by hand, and a `<strong>` out of
       a CMS is exactly that. */
    strong,
    b {
      font-weight: var(--ds-bold-weight);
    }

    p {
      margin: 0;
      /* The prose rung, not body's: a bare paragraph is running text, and the UI
         line belongs to rows with marks beside them. It read the body leading
         until 22 August 2026, which put 20px lines on every unstyled paragraph
         and overrode the Prose fence's own 28 from underneath. A component that
         renders a p declares its type anyway, so rows are untouched. */
      line-height: var(--ds-font-body-prose-leading);
    }

    small { font-size: var(--ds-font-small-size); }
    code, pre, kbd, samp { font-family: var(--ds-typeface-mono); }

    /* ── Links ────────────────────────────────────────────────────────────────
       A link is an element and its look is the system's to decide, not each
       project's and certainly not each page's. The docs site invented one for
       itself before this existed, which is the tell: whatever a documentation
       site has to invent, every consumer will invent too, differently.

       Underlined and not only coloured. A colour on its own is a link that is
       invisible to anyone who cannot see that colour, and it fails WCAG 1.4.1 for
       exactly that reason.

       `-ink` and not the bare brand, for the same reason the status colours have
       inks: the brand is chosen to be seen as a block, and text has to be read.
       Checked at 4.5:1 like every other ink.

       The offset and the thickness are the only raw numbers in this file, and
       they are numbers about drawing rather than decisions anyone tunes: the
       default underline sits on the baseline and cuts through every descender,
       and `from-font` takes the weight the typeface itself specifies instead of
       the browser's guess. */
    /* The colour of the words around it, with a line under it that is a fraction
       of that colour. Blue and underlined is the link of 1996: it was right when a
       page had five of them and no other blue, and it reads as a museum piece on a
       page that has a palette.

       The line does the work the colour used to. WCAG 1.4.1 asks that a link in
       running text be told apart by something other than hue, and an underline is
       that something; it can then be quiet, because it is the only job it has.
       Full strength on hover, so the link answers.

       `color-mix` on `currentColor` rather than a token: the link takes the
       colour of whatever it is standing in, so its underline has to as well. */
    a {
      color: inherit;
      text-decoration: underline;
      text-decoration-thickness: from-font;
      text-decoration-color: color-mix(in srgb, currentColor 35%, transparent);
      text-underline-offset: 0.15em;
      /* Both, and on the element rather than on the hover rule, so it eases in
         and back out again. A colour that snaps reads as a redraw; the same change
         over the fast token reads as an answer. Reduced motion takes the token to
         zero and the answer becomes instant, which is the point of it being a
         token. */
      transition:
        color var(--ds-duration) var(--ds-ease-out),
        text-decoration-color var(--ds-duration) var(--ds-ease-out);
    }

    a:hover {
      text-decoration-color: currentColor;
    }

    /* ── Focus ────────────────────────────────────────────────────────────────
       Every focusable thing, once, here. A keyboard is how a page is used
       without a pointer, and the browser default is a fixed blue that has no
       idea which theme it is on: readable on white, nearly invisible on the dark
       surfaces this system also ships.

       `:focus-visible` and not `:focus`, so a pointer click does not leave a ring
       behind. The offset keeps the ring off the glyphs, and the outline is drawn
       rather than a box-shadow so it survives inside anything that clips. */
    :focus-visible {
      outline: 2px solid var(--ds-focus);
      outline-offset: 2px;
      border-radius: var(--ds-radius-1);
    }


}
@layer roles {
  /* roles. Empty until its step opens. See ROADMAP.md. */

    /* ==========================================================================
       Text roles

       One class per element. The class says what the text IS; the surface says
       what number that comes out as.

       Two scales, largest first, and a word for the one treatment that is not a
       position on a scale.

         ds-font-heading-1   the page statement
         ds-font-heading-2   a section
         ds-font-heading-3   an article or a card, at editorial size
         ds-font-heading-4   a functional heading: a panel, a field group, a card in
                         a dashboard
         ds-font-heading-5   a level below that
         ds-font-heading-6   the deepest level an article reaches

         ds-font-copy   the lede, and large reading text, on the UI line
         ds-font-body   reading text, on the UI line
         ds-font-copy-prose   a paragraph at copy size, on the reading line
         ds-font-body-prose   a paragraph at body size, on the reading line
         ds-font-label  supporting text, a form label, a table header
         ds-font-small  timestamps, counts, the genuinely supplementary

         ds-font-overline    an eyebrow: uppercase, spaced, small
         ds-font-micro       the metadata voice: uppercase, spaced wider, a step
                         under everything, because in capitals at this tracking a
                         10px line reads larger than its number

         ds-text-colour--primary-neutral    the default for content
         ds-text-colour--secondary-neutral  the lesser half of a pair
         ds-text-colour--tertiary-neutral   decorative only
         ds-text-colour--success | --warning | --danger   what happened

       ── Three editorial, three functional ───────────────────────────────────
       Levels 1 to 3 are fluid on web; 4 to 6 are fixed everywhere, because a
       functional heading is not editorial: 129 uses on the previous system meant
       exactly "a heading that is not in editorial clothes" and had no level to go
       to, so most of them bolted a size utility on instead.

       The face is not chosen per level, it follows the size: 20px and up gets the
       display face, below it the body face. The boundary lands between 5 and 6 on
       web and between 3 and 4 on product, which is why no level here names a face.

       This file stopped at four levels for one commit, on the grounds that h4 to
       h6 had zero uses across twenty projects. That counted the markup DESIGNERS
       write and applied it to the markup AUTHORS write, and the deep levels only
       ever appear in the second: an article, a CMS body, markdown. Four levels
       meant h4, h5 and h6 rendering identically, which flattens a hierarchy
       exactly where the person who wrote it had no say.

       ── No level carries a colour ────────────────────────────────────────────
       A level sizes; a colour class colours. The previous system baked secondary into its
       caption and tertiary into its meta, and both went unused: the size someone
       wanted never arrived with the colour they wanted. Default is inherit, and
       anything else is one explicit class.
       ========================================================================== */

    .ds-font-heading-1,
    .ds-font-heading-2,
    .ds-font-heading-3,
    .ds-font-heading-4,
    .ds-font-heading-5,
    .ds-font-heading-6 {
      margin: 0;
      color: inherit;
      text-wrap: balance;
    }

    .ds-font-heading-1 {
      font-family: var(--ds-font-heading-1-face);
      font-size: var(--ds-font-heading-1-size);
      font-weight: var(--ds-font-heading-1-weight);
      line-height: var(--ds-font-heading-1-leading);
      letter-spacing: var(--ds-font-heading-1-tracking);
    }

    .ds-font-heading-2 {
      font-family: var(--ds-font-heading-2-face);
      font-size: var(--ds-font-heading-2-size);
      font-weight: var(--ds-font-heading-2-weight);
      line-height: var(--ds-font-heading-2-leading);
      letter-spacing: var(--ds-font-heading-2-tracking);
    }

    .ds-font-heading-3 {
      font-family: var(--ds-font-heading-3-face);
      font-size: var(--ds-font-heading-3-size);
      font-weight: var(--ds-font-heading-3-weight);
      line-height: var(--ds-font-heading-3-leading);
      letter-spacing: var(--ds-font-heading-3-tracking);
    }

    .ds-font-heading-4 {
      font-family: var(--ds-font-heading-4-face);
      font-size: var(--ds-font-heading-4-size);
      font-weight: var(--ds-font-heading-4-weight);
      line-height: var(--ds-font-heading-4-leading);
      letter-spacing: var(--ds-font-heading-4-tracking);
    }

    .ds-font-heading-5 {
      font-family: var(--ds-font-heading-5-face);
      font-size: var(--ds-font-heading-5-size);
      font-weight: var(--ds-font-heading-5-weight);
      line-height: var(--ds-font-heading-5-leading);
      letter-spacing: var(--ds-font-heading-5-tracking);
    }

    .ds-font-heading-6 {
      font-family: var(--ds-font-heading-6-face);
      font-size: var(--ds-font-heading-6-size);
      font-weight: var(--ds-font-heading-6-weight);
      line-height: var(--ds-font-heading-6-leading);
      letter-spacing: var(--ds-font-heading-6-tracking);
    }

    /* Running text, named by the job rather than numbered.
       The headings are a hierarchy, so a number tells you where you are on it.
       These four are four different jobs at four sizes, and copy, body, label and
       small are what they are called out loud.

       `text-wrap: pretty` rather than `balance`: balance is for short blocks and
       the browser gives up past a handful of lines anyway. */
    /* What the four share is everything EXCEPT the type, which is each one's own
       now that a role carries all four halves. Written as one rule with the
       primitive in it, the class for a role was the last place in the package
       still saying `--ds-weight-standard` where it meant `copy`. */
    .ds-font-copy,
    .ds-font-body,
    .ds-font-label,
    .ds-font-small,
    .ds-font-mono {
      margin: 0;
      color: inherit;
      text-wrap: pretty;
    }

    .ds-font-copy {
      font-family: var(--ds-font-copy-face);
      font-size: var(--ds-font-copy-size);
      line-height: var(--ds-font-copy-leading);
      font-weight: var(--ds-font-copy-weight);
    }

    .ds-font-body {
      font-family: var(--ds-font-body-face);
      font-size: var(--ds-font-body-size);
      line-height: var(--ds-font-body-leading);
      font-weight: var(--ds-font-body-weight);
    }

    /* The same two sizes as paragraphs: the UI pairs above keep the line every
       mark measures itself against, and these hold the same size on the reading
       line, for running text with no marks beside it. */
    .ds-font-copy-prose {
      font-family: var(--ds-font-copy-prose-face);
      font-size: var(--ds-font-copy-prose-size);
      line-height: var(--ds-font-copy-prose-leading);
      font-weight: var(--ds-font-copy-prose-weight);
    }

    .ds-font-body-prose {
      font-family: var(--ds-font-body-prose-face);
      font-size: var(--ds-font-body-prose-size);
      line-height: var(--ds-font-body-prose-leading);
      font-weight: var(--ds-font-body-prose-weight);
    }

    .ds-font-label {
      font-family: var(--ds-font-label-face);
      font-size: var(--ds-font-label-size);
      line-height: var(--ds-font-label-leading);
      font-weight: var(--ds-font-label-weight);
    }

    .ds-font-small {
      font-family: var(--ds-font-small-face);
      font-size: var(--ds-font-small-size);
      line-height: var(--ds-font-small-leading);
      font-weight: var(--ds-font-small-weight);
    }

    /* Code: small's measures in the code face. A key to press, a value as it is
       written, a file's name. The fifth role of the running text, and the only
       one whose face is not the body's. */
    .ds-font-mono {
      font-family: var(--ds-font-mono-face);
      font-size: var(--ds-font-mono-size);
      line-height: var(--ds-font-mono-leading);
      font-weight: var(--ds-font-mono-weight);
      letter-spacing: var(--ds-font-mono-tracking);
    }

    /* A word rather than a number, because this is a treatment and not a step on
       a scale. It is also the only role of the previous system's five worded ones
       that people actually reached for: 223 uses against zero for three of the
       others. */
    .ds-font-overline {
      margin: 0;
      font-family: var(--ds-font-overline-face);
      font-size: var(--ds-font-overline-size);
      font-weight: var(--ds-font-overline-weight);
      /* The role's own, and this is a fix: it was `1.4`, a number no token holds,
         which on 14px is 19.6 and lands the line off every 4px step in the
         package. `--ds-font-overline-leading` is 16. */
      line-height: var(--ds-font-overline-leading);
      letter-spacing: var(--ds-font-overline-tracking);
      text-transform: uppercase;
      color: inherit;
    }

    /* The overline one step down, for rows of metadata: the category and the
       date over a title, the key over a figure. The first consumer cut it by
       hand and wrote down why 10 and not 12: at this tracking, in capitals, the
       line reads larger than its number, and at 12 it competes with the title
       it introduces. */
    .ds-font-micro {
      margin: 0;
      font-family: var(--ds-font-micro-face);
      font-size: var(--ds-font-micro-size);
      font-weight: var(--ds-font-micro-weight);
      line-height: var(--ds-font-micro-leading);
      letter-spacing: var(--ds-font-micro-tracking);
      text-transform: uppercase;
    }

    /* ── Text colour: the modifier ───────────────────────────────────────────
       Sits ON a role, never instead of one. Measured on the previous system: of
       863 uses of its text-colour utilities, 859 were on an element with no role
       at all, which means the colour was not modifying anything, it was standing
       in for the name that should have been there.

       The name says the whole thing: `.ds-text-colour--secondary-neutral` is the
       secondary neutral used as a text colour. What it applies is the token for
       that colour AS TEXT, and there are two shapes of that. A neutral exists
       only as text, so its token is named for it and the two are spelled the
       same. A status is a family where text is one of five uses, so the one for
       text carries `-ink` and the class stays named for the colour rather than
       for the token.

       `npm run check` holds the rule that matters underneath both: one of these
       classes may only apply a colour meant for text. Handing a reader the FILL
       is what they did before the inks existed, and a status fill measures around
       3:1 as text.

       `-neutral` earns its place: the three strengths and the three status
       colours are both text colours, and the suffix is what tells them apart at
       a glance in a className.

       A status colour has two jobs and cannot hold both in one value. As a block
       it has to be seen, as text it has to be read, and the value that gets seen
       is too light to be read: bare `--ds-success` measured 3.16:1 on the page.
       The `-ink` is the same colour taken to where it passes.

       Four statuses, four classes. Info used to be left out on the grounds that
       it belongs to a container like an alert, which is true of warning and
       danger too, so it separated nothing: three of the four had a class and one
       did not, and that gap was the whole argument. `npm run check` holds the
       symmetry now.

       Brand is the one that stays out, and its reason is different in kind: a
       brand-coloured word is a link, which the element earns by being a link. It
       arrives with the anchor, not as a colour an author picks. */
    .ds-text-colour--primary-neutral { color: var(--ds-text-primary-neutral); }
    .ds-text-colour--secondary-neutral { color: var(--ds-text-secondary-neutral); }
    .ds-text-colour--tertiary-neutral { color: var(--ds-text-tertiary-neutral); }

    /* Numbers for strength, words for meaning, one prefix for both, because they
       answer the same question: what colour is this text. A number says where on
       the scale, a word says what happened.

       Three and not five. Measured across twenty projects on the other system:
       error 60 uses, success 41, warning 22, and then info 2, which is two uses
       saying the need is not there. Info is a container's job, an alert or a
       banner, and it has tokens for that.

       `interactive` had 58 and is missing on purpose too: that is the colour of a
       link, which the element earns by being a link, and not a colour an author
       picks per element. It arrives with prose.

       These stay classes rather than becoming a component's business, because
       that is where the uses are. Of the ones counted, not one sat inside
       anything that would become a component: 57 were loose error messages and
       54 were loose status text, an overdue date in a table row, a count that is
       going badly. A Field will own its own error message and will use these
       underneath, and the rest of it never had a component to belong to. */
    /* The `-ink` and not the bare name. The bare name is chosen to be seen as a
       block of colour and is too light to be read as text on a pale surface: it
       measured 3.16:1 on the page for success and 3.05 for warning, both under
       AA. Nothing looked broken, which is why it needed measuring. */
    .ds-text-colour--success { color: var(--ds-success-ink); }
    .ds-text-colour--warning { color: var(--ds-warning-ink); }
    .ds-text-colour--danger { color: var(--ds-danger-ink); }
    .ds-text-colour--info { color: var(--ds-info-ink); }


}
@layer components {
  /* components. See ROADMAP.md step 6. */

    /* ==========================================================================
       Layout

       The CSS behind Stack, Row and Grid. Not public: nothing outside this
       package should type these class names, and they are absent from the
       exports map for that reason. The component IS the API.

       That is the whole point of the split. The other system published
       `.ds-stack` AND a `<Stack>` doing the same job, and 879 elements used
       neither: with `flex`, `flex-col` and `gap-6` also on the shelf, the column
       gets assembled by hand every time, because the utility is already in the
       file you are editing and the import is not. Here the utilities do not
       exist, so there is one way to make a column.

       Gap takes the four named distances and nothing else. `line` means the
       children are two lines of one thing, `element` means they are parts of one
       thing, `block` means they are separate things in a section, `section` means
       they are sections. The last three change with the surface, so a stack is
       automatically denser in a panel than on a page.
       There is deliberately no arbitrary gap: if a layout wants 27px, that is a
       sign it is not a stack of related things, and step 7 will say whether the
       need is real.
       ========================================================================== */

    .ds-stack,
    .ds-row {
      display: flex;
      min-width: 0;
    }

    .ds-stack { flex-direction: column; }
    .ds-row { flex-direction: row; }

    /* A stack, a row or a grid written as a list is a list to the reader and
       an arrangement to the eye: the marks and the indent a list arrives with
       are the browser's, and the container is the only owner of the distance
       here. Two pages had reset them by hand before the rule was written. */
    .ds-stack:is(ul, ol),
    .ds-row:is(ul, ol),
    .ds-grid:is(ul, ol) {
      list-style: none;
      margin: 0;
      padding: 0;
    }

    .ds-grid {
      /* The narrowest a column is allowed to be before the grid gives one up.
         One number, and it is the whole of how this grid is responsive: see the
         columns below. A caller wanting cards that hold more or less moves it on
         the one grid rather than reaching for a breakpoint.

         384, and the number is chosen to produce the ramp people expect from a
         row of cards without anybody writing it: a three-column grid draws one
         column on a phone, two on a tablet and three on a desk, because a third of
         a tablet is 325 and that is under the floor while a half of it is 481 and
         that is over. Measured on the three canvases rather than reasoned about.
         At 16rem a tablet kept all three at 314 apiece, which fits and reads
         cramped: the floor is a judgement about how narrow a card may be, and 314
         was on the wrong side of it. */
      --ds-grid-floor: 24rem;
      /* Zero until a gap class says otherwise, so the arithmetic below never
         reads a name nobody declared. */
      --ds-grid-gap: 0px;
      display: grid;
      min-width: 0;
    }

    /* One distance, one owner: the container holds it and the children carry no
       margins. Two owners is how a 16px gap becomes 48 and nobody can say which
       rule to edit. */
    /* The gap is named as well as set, because the column arithmetic below has to
       subtract it and `calc()` cannot read the `gap` property. */
    .ds-gap-line { --ds-grid-gap: var(--ds-rhythm-line); gap: var(--ds-grid-gap); }
    .ds-gap-element { --ds-grid-gap: var(--ds-rhythm-element); gap: var(--ds-grid-gap); }
    .ds-gap-block { --ds-grid-gap: var(--ds-rhythm-block); gap: var(--ds-grid-gap); }
    .ds-gap-section { --ds-grid-gap: var(--ds-rhythm-section); gap: var(--ds-grid-gap); }

    .ds-align-start { align-items: flex-start; }
    .ds-align-center { align-items: center; }
    .ds-align-end { align-items: flex-end; }
    .ds-align-stretch { align-items: stretch; }
    .ds-align-baseline { align-items: baseline; }

    .ds-justify-start { justify-content: flex-start; }
    .ds-justify-center { justify-content: center; }
    .ds-justify-end { justify-content: flex-end; }
    .ds-justify-between { justify-content: space-between; }

    .ds-wrap { flex-wrap: wrap; }

    /* ── Columns ──────────────────────────────────────────────────────────────
       The number is a CEILING, not a promise: at most this many, and fewer where
       a column would come out under the floor.

       One line each, and no breakpoint anywhere. Every track is at least a share
       of the grid's own width — a third of it on a three-column grid — so the
       grid can never draw more columns than it was asked for; and every track is
       also at least `--ds-grid-floor`, so when a third of the room is narrower
       than that, only two fit, then one. `auto-fill` counts how many fit and
       draws that many tracks, whether or not anything lands in them.

       The `100%` is the GRID's width, which is the point. This was a media query
       at 47.99rem, and a media query asks the WINDOW: three cards in a 380px
       canvas inside a 1280px window stayed three cards of 116px with the badge
       sitting on top of the title, because as far as the query was concerned the
       window was wide. The same is true of a panel, a split view, a drawer and an
       embed. A grid does not know the window and has no business asking about it;
       it knows how much room it was given.

       1 / 2 / 3 / 4 by width falls out of this rather than being written: with the
       default floor a three-column grid draws one column on a phone, two on a
       tablet and three on a desk. Moving the floor moves all three, which is the
       one dial.

       And the whole thing is capped at `100%`, which is not a detail: without it
       the track stays the FLOOR when the grid is narrower than the floor, so a
       384 card sat in a 380 column and every card in the row hung four pixels out
       of the canvas with its badge cut off. A column can never be wider than the
       grid it is in, and saying so is one `min()`.

       ── Why `auto-fill` and not `auto-fit` ────────────────────────────────────
       `auto-fit` collapses the tracks nothing landed in and hands their room to
       the ones that filled, so a grid asked for three columns and given one card
       draws that card across the whole row. On a page of 306 wide cards the
       single one came out 1296 by 1818: not a wide card, a poster. Measured 27
       August 2026 on a landing page whose calendar had emptied down to one date,
       and the same shape turns up whenever a list is filtered down to its last
       item or a client unpublishes all but one.

       `auto-fill` keeps the empty tracks, so the ceiling holds from both sides:
       at most this many, and each one no wider than its share. A grid that
       genuinely wants its only child to fill the row asks for one column, which
       is a thing it can say. */
    .ds-cols-1 { grid-template-columns: 1fr; }

    .ds-cols-2 {
      grid-template-columns: repeat(
        auto-fill,
        minmax(min(max(var(--ds-grid-floor), (100% - var(--ds-grid-gap)) / 2), 100%), 1fr)
      );
    }

    .ds-cols-3 {
      grid-template-columns: repeat(
        auto-fill,
        minmax(min(max(var(--ds-grid-floor), (100% - 2 * var(--ds-grid-gap)) / 3), 100%), 1fr)
      );
    }

    .ds-cols-4 {
      grid-template-columns: repeat(
        auto-fill,
        minmax(min(max(var(--ds-grid-floor), (100% - 3 * var(--ds-grid-gap)) / 4), 100%), 1fr)
      );
    }

    /* Posters: a picture with a line under it reads at less than half the room a
       card of words needs, so a grid of them lowers its floor and the ceiling
       above does the rest. 10rem is a poster on a phone with two abreast. */
    .ds-grid--posters {
      --ds-grid-floor: 10rem;
    }

    /* No ceiling at all: as many as fit, and the floor is the only rule. Its own
       floor because a grid that is not counting columns is usually holding wider
       things than a grid that is. */
    .ds-cols-auto {
      --ds-grid-floor: 18rem;
      grid-template-columns: repeat(auto-fill, minmax(min(var(--ds-grid-floor), 100%), 1fr));
    }

    /* ==========================================================================
       Avatar

       The CSS behind <Avatar> and <AvatarStack>. Private, like every other
       component's: nothing outside this package types these names.

       An avatar is the small picture that stands for someone, in the one place a
       row has for it. It holds a photo when there is one and the initials of the
       name when there is not, and the two are the same disc at the same size, so
       a list where half the people have uploaded a photo still lines up.

       ── The size is the control ramp ──────────────────────────────────────────
       The same six tiers a button and a field read, and for the same reason: an
       avatar sits on a row beside them, and two things on one row have to be
       exactly as tall as each other. Its own ladder would line up until the day
       one of the two was edited.

       ── The disc is neutral, and opaque ───────────────────────────────────────
       Initials sit on a plain grey surface. A palette keyed off the name is the
       usual thing here and it spends colour on identity, which leaves nothing to
       say status with two columns later.

       Opaque because the disc is a lid as much as a ground. It was the interactive
       tint first, which is a 10% wash, and a wash shows what is under it: in a
       stack that is the face of the person before, so "TB" came out as two dark
       letters lying on somebody's photograph. A surface, not a veil.
       ========================================================================== */

    .ds-avatar {
      --ds-avatar-face: var(--ds-font-body-face);
      /* This component's own. The role says what the words are; this says that
         THIS component wants a heavier hand than the role has, which is a
         decision the component makes and nobody else. Pointed at the semantic
         rather than holding a number, so moving the system moves every component
         that has not been moved on purpose, and moving one moves one. */
      --ds-avatar-weight: var(--ds-emphasis-weight);
      /* Its initials are sized off the avatar and not off the type scale, because
         a circle of 24 and one of 64 want a letter in proportion to the circle.
         There is no paired leading for a size that has no token, so the line box
         is the letter itself, said here so a tool reads the decision.

         The size is in this list too. It was left out while the leading was in it,
         so a tool reading the pair was told half the decision: the leading was
         answered "on purpose" and the 9.6px letter beside it, from the same
         sentence, was reported as a value nobody had named. */
      --ds-avatar-intent: line-height font-size;
      /* Positioned because the stack orders the discs with z-index, which does
         nothing to a static box. */
      position: relative;
      display: inline-flex;
      align-items: center;
      justify-content: center;
      /* Never squeezed by the text beside it: the name gives, the face does not. */
      flex: none;
      inline-size: var(--ds-avatar-size);
      block-size: var(--ds-avatar-size);
      border-radius: var(--ds-radius-full);
      background-color: var(--ds-surface-3);
      --ds-avatar-ink: var(--ds-text-primary-neutral);
      color: var(--ds-avatar-ink);
      font-family: var(--ds-avatar-face);
      font-weight: var(--ds-avatar-weight);
      /* A share of the disc rather than a tier of the type scale, so the initials
         are the same weight of mark at 24px and at 64px. Fixed at 14 they fill the
         small disc and rattle around inside the large one. */
      /* Named, because a value the panel cannot name is a value nobody can find:
         read back it was a bare 16px on a 40px disc and no token held it. */
      --ds-avatar-initials-size: calc(var(--ds-avatar-size) * 0.4);
      font-size: var(--ds-avatar-initials-size);
      line-height: 1;
      /* The photo is cropped by the disc rather than by its own rounding, which is
         what lets `shape` change one number here and be done. */
      overflow: hidden;
      /* A double click on a row should not select the initials. */
      user-select: none;
    }

    .ds-avatar--xsmall { --ds-avatar-size: var(--ds-size-1); }  /* 24 */
    .ds-avatar--small { --ds-avatar-size: var(--ds-size-2); }   /* 32 */
    .ds-avatar--medium { --ds-avatar-size: var(--ds-size-3); }  /* 40 */
    .ds-avatar--large { --ds-avatar-size: var(--ds-size-4); }   /* 48 */
    .ds-avatar--xlarge { --ds-avatar-size: var(--ds-size-5); }  /* 56 */
    .ds-avatar--xxlarge { --ds-avatar-size: var(--ds-size-6); } /* 64 */

    /* Square reads the roundness ramp rather than a number of its own, so it
       follows `data-radius` with everything else: rounded on the soft setting and
       a true square on the sharp one. */
    .ds-avatar--square {
      border-radius: var(--ds-radius-2);
    }

    .ds-avatar__image {
      /* Over the initials rather than instead of them: both are rendered, because
         a server cannot hear an image fail and the behaviour bundle must not build
         the replacement. The picture covers the letters while it is there, and the
         rule below takes it away when it is not. */
      position: absolute;
      inset: 0;
      display: block;
      inline-size: 100%;
      block-size: 100%;
      /* Cover and not contain: a portrait is cropped to the disc, and a letterboxed
         face with tint above and below it looks like a broken image. */
      object-fit: cover;
      border-radius: inherit;
    }

    /* ── The stack ────────────────────────────────────────────────────────────
       Several faces in the room one takes. Used where the count is the point and
       the faces are the texture: a bulk bar over a selection, the people on a
       project.

       The ring is what keeps them apart. It is drawn OUTSIDE the disc as a shadow,
       so it costs no layout and the discs stay exactly as far apart as the margin
       says.

       White, in both themes, for the reason the token itself gives: what it
       separates is photographs, and a photograph has no theme. It was the surface
       underneath at first, which is right while the stack sits on the page and
       gone the moment it does not: on a grey bar the ring came out grey and four
       faces ran together into one shape. `--ds-avatar-ring` is still there for
       wherever white is the wrong answer. */
    .ds-avatar-stack {
      display: inline-flex;
      align-items: center;
    }

    .ds-avatar-stack > .ds-avatar {
      box-shadow: 0 0 0 2px var(--ds-avatar-ring, var(--ds-static-white));
    }

    /* A share of the disc, so the overlap looks the same at every size. Read off
       the child, which is where the size variable is declared. */
    .ds-avatar-stack > .ds-avatar + .ds-avatar {
      margin-inline-start: calc(var(--ds-avatar-size) * -0.3);
    }

    /* Every disc but the first has its left third under the one before it, and
       initials centred in the disc are therefore initials with a letter half
       hidden: a stack of six people read as a row of smudges. The letters move
       into the part that is still visible, by half of what is covered, which
       centres them there. Photographs need none of this, since a face is cropped
       by the disc already. */
    .ds-avatar-stack > .ds-avatar:not(:first-child) > .ds-avatar__initials {
      transform: translateX(calc(var(--ds-avatar-size) * 0.15));
    }


    /* ── The rest, as a number ────────────────────────────────────────────────
       A number, written as a number. It was a disc for a revision, and a grey
       circle at the end of a row of photographs reads as a face that failed to
       load: the eye counts five people and finds four.

       Sized off the stack rather than off the type scale, so it stays in
       proportion at every tier: the ramp is declared on the stack for this one
       line, since a span outside the discs cannot read a variable one of them
       set. */
    .ds-avatar-stack--xsmall { --ds-avatar-size: var(--ds-size-1); }
    .ds-avatar-stack--small { --ds-avatar-size: var(--ds-size-2); }
    .ds-avatar-stack--medium { --ds-avatar-size: var(--ds-size-3); }
    .ds-avatar-stack--large { --ds-avatar-size: var(--ds-size-4); }
    .ds-avatar-stack--xlarge { --ds-avatar-size: var(--ds-size-5); }
    .ds-avatar-stack--xxlarge { --ds-avatar-size: var(--ds-size-6); }

    .ds-avatar-stack__more {
      --ds-avatar-stack-more-face: var(--ds-font-body-face);
      margin-inline-start: var(--ds-space-2);
      font-family: var(--ds-avatar-stack-more-face);
      --ds-avatar-stack-more-size: calc(var(--ds-avatar-size) * 0.35);
      font-size: var(--ds-avatar-stack-more-size);
      font-weight: var(--ds-avatar-weight);
      --ds-avatar-stack-more-ink: var(--ds-text-secondary-neutral);
      color: var(--ds-avatar-stack-more-ink);
      font-variant-numeric: tabular-nums;
      white-space: nowrap;
    }


    /* ── The face that is a control ───────────────────────────────────────────
       An account menu opens from the reader's own picture, and the way that was
       built here was an avatar inside a mark button: a circle inside a rounded
       square, with the focus ring drawing the square around the circle. Two
       shapes for one target.

       So the face IS the control. It keeps its own shape, which is the point,
       and takes the ring and the hover a control owes a reader. */
    .ds-avatar--pressable {
      padding: 0;
      border: 0;
      cursor: pointer;
      /* A button inherits neither the face nor the leading, and the sizes below
         are read off the tier rather than the line, but the initials inside are
         type: without this they come out at the user agent's own size. */
      font: inherit;
      /* The ring follows the picture, because `border-radius` is what an outline
         follows and the avatar's is already the circle. */
      transition: box-shadow var(--ds-duration) var(--ds-ease);
    }

    .ds-avatar--pressable:hover {
      /* A ring the colour of the page's own line, in the gap the ring offset
         leaves, so the face is not dimmed or tinted: a photograph under a wash
         is a photograph nobody chose. */
      box-shadow: 0 0 0 2px var(--ds-border-strong);
    }

    .ds-avatar--pressable:focus-visible {
      outline: 2px solid var(--ds-focus);
      outline-offset: 2px;
    }

    /* What the bundle writes when the picture fails, and the whole of what it
       writes: the letters were underneath the entire time. */
    .ds-avatar[data-broken] .ds-avatar__image {
      display: none;
    }

    /* ── The face beside its name ─────────────────────────────────────────────
       Owner's call, 22 August 2026: the face-and-name pair was hand-built in two
       playground tables with a Row and a Stack, and the gap between the two was a
       decision taken twice. The pair is the avatar's own now: `label` draws the
       name at the type the size asks for, `description` the quiet line under it,
       and when anything presses it is the whole row that presses. */

    .ds-avatar-identity {
      display: inline-flex;
      align-items: center;
      gap: var(--ds-rhythm-element);
      min-inline-size: 0;
      color: inherit;
      text-decoration: none;
      text-align: start;
    }

    /* A <button> brings a face and a ground of its own; both go. */
    button.ds-avatar-identity {
      font: inherit;
      padding: 0;
      border: 0;
      background: none;
    }

    .ds-avatar-identity--pressable {
      cursor: pointer;
    }

    .ds-avatar-identity:focus-visible {
      outline: 2px solid var(--ds-focus);
      outline-offset: 2px;
      border-radius: var(--ds-radius-1);
    }

    .ds-avatar-identity__text {
      display: flex;
      flex-direction: column;
      /* No gap of its own between the name and the quiet line: the two carry
         their own leading, and a step on top of it reads as two separate things
         rather than a name and what it says. The card's description, the
         collapsible's summary and the two-line cell all settled this way; this
         one carried a line step until 10 September 2026, when the owner read
         "Cortex" and the email under it in an account menu as too far apart:
         two 16px line boxes with 4px between them. */
      min-inline-size: 0;
    }

    /* The name at the label rung beside the small faces, and the body rung from
       medium up, where a 14px name under a 40px face reads as a caption on a
       photograph. Both lines truncate rather than wrap: this stands in table
       cells and header corners, where a long name owns one line and no more. */
    .ds-avatar-identity__name {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-avatar-identity-name-face: var(--ds-font-label-face);
      --ds-avatar-identity-name-size: var(--ds-font-label-size);
      --ds-avatar-identity-name-weight: var(--ds-font-label-weight);
      --ds-avatar-identity-name-leading: var(--ds-font-label-leading);
      --ds-avatar-identity-name-tracking: var(--ds-font-label-tracking);
      font-family: var(--ds-avatar-identity-name-face);
      font-size: var(--ds-avatar-identity-name-size);
      font-weight: var(--ds-avatar-identity-name-weight);
      line-height: var(--ds-avatar-identity-name-leading);
      letter-spacing: var(--ds-avatar-identity-name-tracking);
      --ds-avatar-identity-name-ink: var(--ds-text-primary-neutral);
      color: var(--ds-avatar-identity-name-ink);
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
    }

    .ds-avatar-identity__description {
      --ds-avatar-identity-description-face: var(--ds-font-small-face);
      --ds-avatar-identity-description-size: var(--ds-font-small-size);
      --ds-avatar-identity-description-weight: var(--ds-font-small-weight);
      --ds-avatar-identity-description-leading: var(--ds-font-small-leading);
      --ds-avatar-identity-description-tracking: var(--ds-font-small-tracking);
      font-family: var(--ds-avatar-identity-description-face);
      font-size: var(--ds-avatar-identity-description-size);
      font-weight: var(--ds-avatar-identity-description-weight);
      line-height: var(--ds-avatar-identity-description-leading);
      letter-spacing: var(--ds-avatar-identity-description-tracking);
      --ds-avatar-identity-description-ink: var(--ds-text-secondary-neutral);
      color: var(--ds-avatar-identity-description-ink);
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
    }

    :is(.ds-avatar-identity--medium, .ds-avatar-identity--large, .ds-avatar-identity--xlarge, .ds-avatar-identity--xxlarge)
      .ds-avatar-identity__name {
      --ds-avatar-identity-name-face: var(--ds-font-body-face);
      --ds-avatar-identity-name-size: var(--ds-font-body-size);
      --ds-avatar-identity-name-weight: var(--ds-font-body-weight);
      --ds-avatar-identity-name-leading: var(--ds-font-body-leading);
      --ds-avatar-identity-name-tracking: var(--ds-font-body-tracking);
      font-family: var(--ds-avatar-identity-name-face);
      font-size: var(--ds-avatar-identity-name-size);
      font-weight: var(--ds-avatar-identity-name-weight);
      line-height: var(--ds-avatar-identity-name-leading);
      letter-spacing: var(--ds-avatar-identity-name-tracking);
    }

    :is(.ds-avatar-identity--medium, .ds-avatar-identity--large, .ds-avatar-identity--xlarge, .ds-avatar-identity--xxlarge)
      .ds-avatar-identity__description {
      --ds-avatar-identity-description-face: var(--ds-font-label-face);
      --ds-avatar-identity-description-size: var(--ds-font-label-size);
      --ds-avatar-identity-description-weight: var(--ds-font-label-weight);
      --ds-avatar-identity-description-leading: var(--ds-font-label-leading);
      --ds-avatar-identity-description-tracking: var(--ds-font-label-tracking);
      font-family: var(--ds-avatar-identity-description-face);
      font-size: var(--ds-avatar-identity-description-size);
      font-weight: var(--ds-avatar-identity-description-weight);
      line-height: var(--ds-avatar-identity-description-leading);
      letter-spacing: var(--ds-avatar-identity-description-tracking);
    }

    /* ==========================================================================
       Badge

       The CSS behind <Badge>. Not public: nothing outside this package types
       these names, and they are absent from the exports map for that reason.

       A short label for what something IS: a status, a state, a category. Given
       something to do it becomes the control the rest of the world calls a chip,
       a tag or a pill: pressed, it is a filter that holds itself down; with a
       cross on it, it is a value the reader can take off again. One drawing, and
       what it does is decided by what it was handed.

       A control also reads as words rather than as an abbreviation, and it stands
       on a tier so it can be hit, which is the whole of what changes between the
       two jobs. Both are below.

       ── Read off the Figma library ────────────────────────────────────────────
       Taken from it: both icon slots, the uppercase label, the 1px inside border,
       and the filled and unfilled appearances.

       Not taken: five sizes. The type scale has two levels down here, 14 and 12,
       and five steps of badge is four decisions nobody makes the same way twice.
       Added: `soft`, the tinted middle between filled and outlined, which costs
       no new token because every tone already ships a `-tint` and an `-edge`.

       ── Everything scales from the font ───────────────────────────────────────
       Padding, gap and icon size are in `em`, so a badge is proportional at both
       sizes and at every `data-text` setting. Padding in fixed px looks tight at
       115% and loose at the small size.

       The height is not set either. A badge is as tall as its line plus its
       padding, and fixing it in px is how a badge gets clipped in the one
       language nobody tested in.
       ========================================================================== */

    .ds-badge {
      --ds-badge-tracking: var(--ds-font-overline-tracking);
      /* This component's own. The role says what the words are; this says that
         THIS component wants a heavier hand than the role has, which is a
         decision the component makes and nobody else. Pointed at the semantic
         rather than holding a number, so moving the system moves every component
         that has not been moved on purpose, and moving one moves one. */
      --ds-badge-weight: var(--ds-emphasis-weight);
      /* The tier is a floor on the BOX, and without this it is a floor on the
         content: measured 26 on a rung of 24, which is the border counted twice
         outside the number that was supposed to hold the height. */
      box-sizing: border-box;
      display: inline-flex;
      align-items: center;
      /* ── Off `em`, onto the ramps ─────────────────────────────────────────
         This was `gap: 0.4em; padding: 0.25em 0.7em`, and the case for it was
         that a badge should be proportioned by its label rather than by its box.
         What it actually produced on 12px type was a gap of 4.8, a side of 8.4
         and a top of 3: three numbers no token holds, on a package whose every
         other measurement is a step of 4. Proportion by the label is a real
         idea and this is not how the system spends it; the type ramp already
         carries the proportion, and the space ramp has a step for each rung of
         it.

         The tier is what the `--control` variant was already doing, and the
         comment there had the rule in it: the small badge reads at 12 and stands
         at 24, the medium reads at 14 and stands at 32, which are the rungs
         those two sizes take everywhere else. A badge is not a target and does
         not need the floor for one, but it does need a height somebody chose.
         With the tier holding the box the block padding is nothing, which is why
         only the sides are set below. */
      min-block-size: var(--ds-badge-tier);
      gap: var(--ds-space-1);
      /* Zero, and it has to be said: the shorthand this replaced was resetting the
         user agent's own 1px on a `<button>`, and the control variant IS a button.
         Measured 1px 12px on it with only the sides set. */
      padding-block: 0;
      padding-inline: var(--ds-badge-inset);
      border: 1px solid transparent;
      /* Step 2, which is where the button sits too. It was step 4, and on
         something 26px tall step 4 is a pill under `round` AND a pill under
         `pill`: two of the four modes drew the same badge, so the axis had three
         positions instead of four. From step 2 the four are 8, 4, 0 and 24px,
         which is rounded, barely rounded, square and a pill. */
      border-radius: var(--ds-radius-2);
      font-weight: var(--ds-badge-weight);
      /* Uppercase and tracked, which is the overline treatment and is what makes
         a badge read as an object rather than as a word in the sentence next to
         it. It also means a two-word badge stays short enough to sit on one line. */
      text-transform: uppercase;
      letter-spacing: var(--ds-badge-tracking);
      /* Its own object, never part of a line of running text: a descender pushing
         it half a pixel out of line is visible across a table of them. */
      vertical-align: middle;
      white-space: nowrap;
    }

    /* Face, size and leading together, per size, because a role is all of them.
       Written the other way round the base took `label`'s face, the modifier took
       `small`'s size, and the leading was `1.4`, which no token holds: three
       halves of three different decisions, and the panel read the box back as
       12px type on a 16.8px line with a heading's weight on it. On the step of 4
       the whole package is built from, 16.8 is the only number that misses. */
    /* ── The ramp, the same one ───────────────────────────────────────────────
       A tier is a height, and a name for a height means the same thing on every
       component or it means nothing. This one used to name its rungs after the
       TYPE in them: `small` read at 12 and stood at 24, `medium` read at 14 and
       stood at 32, which are one rung below what those words buy anywhere else.
       So a badge beside a button could match its height or match its name and
       never both, and the pair that OS's own demos shipped — a badge at small
       next to a button at xsmall, the same 24 pixels under two different words —
       is what the owner read on 7 September 2026 and called what it was.

       The heights have not moved. The words have: 24 is xsmall as it is
       everywhere, 32 is small, and 40 is the medium the ramp always had and this
       component never offered. `badgeFor` still says which one goes INSIDE a
       control of a given tier, which is a different question and still answered
       one rung down. */
    .ds-badge--medium {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-badge-face: var(--ds-font-body-face);
      --ds-badge-size: var(--ds-font-body-size);
      --ds-badge-leading: var(--ds-font-body-leading);
      --ds-badge-tier: var(--ds-size-3); /* 40 */
      --ds-badge-inset: var(--ds-space-4); /* 16 */
      font-family: var(--ds-badge-face);
      font-size: var(--ds-badge-size); /* 16 */
      letter-spacing: var(--ds-badge-tracking);
      line-height: var(--ds-badge-leading); /* 20 */
    }

    .ds-badge--small {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-badge-face: var(--ds-font-label-face);
      --ds-badge-size: var(--ds-font-label-size);
      --ds-badge-leading: var(--ds-font-label-leading);
      --ds-badge-tier: var(--ds-size-2); /* 32 */
      --ds-badge-inset: var(--ds-space-3); /* 12 */
      font-family: var(--ds-badge-face);
      font-size: var(--ds-badge-size); /* 14 */
      letter-spacing: var(--ds-badge-tracking);
      line-height: var(--ds-badge-leading); /* 16 */
    }

    .ds-badge--xsmall {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-badge-face: var(--ds-font-small-face);
      --ds-badge-size: var(--ds-font-small-size);
      --ds-badge-leading: var(--ds-font-small-leading);
      --ds-badge-tier: var(--ds-size-1); /* 24 */
      --ds-badge-inset: var(--ds-space-2); /* 8 */
      font-family: var(--ds-badge-face);
      font-size: var(--ds-badge-size); /* 12 */
      letter-spacing: var(--ds-badge-tracking);
      line-height: var(--ds-badge-leading); /* 16 */
    }

    /* The icon slots. Sized here rather than by the caller, so a badge handed a
       24px icon does not grow to fit it. `1em` follows the label at both sizes,
       and `flex: none` keeps the icon whole when the label is what has to give. */
    /* Words, for the half of the job that is a control. Caps and tracking are how
       a status reads as an object rather than as a word in the sentence beside it;
       a filter that says "Agriculture" or a date that says "13 Feb" is a word
       somebody chose, and shouting it back at them helps nobody read it.
       The default follows what the badge was asked to do, and `case` overrides. */
    .ds-badge--plain {
      text-transform: none;
      letter-spacing: normal;
    }

    /* Everything below is worn by both halves of the control job, the one that is
       pressed and the one that is removed. It was keyed to the click alone, so a
       badge whose whole shape removes it came out with the arrow cursor, no wash
       under the pointer and no ring on focus: a button dressed as a label.

       Standing on a tier, for the same half. A label is read and has no height of
       its own on purpose; a control is HIT, and the floor under a target is 24px.
       The tier matches the type: the small badge reads at 12 and stands at 24, the
       medium reads at 14 and stands at 32, which are the tiers those two sizes
       belong to everywhere else in the system. */
    /* The floor these two used to set is now what every badge stands on, so what
       is left here is the rest of the control job. */
    .ds-badge--control.ds-badge--xsmall {
      min-block-size: var(--ds-badge-tier);
    }

    .ds-badge--control.ds-badge--small {
      min-block-size: var(--ds-badge-tier);
    }

    .ds-badge--control.ds-badge--medium {
      min-block-size: var(--ds-badge-tier);
    }

    /* As tall as the line beside it, and not as tall as the font: `1em` is the
       type size, so a 12px badge carried a 12px mark next to a 17px line and the
       mark read as the smaller of two things that are meant to be one. `1lh` is
       the line box the label actually occupies, so the two are the same height at
       both sizes and under every `data-text` setting. */
    .ds-badge__icon {
      display: inline-flex;
      flex: none;
      inline-size: 1lh;
      block-size: 1lh;
    }

    .ds-badge__icon > svg {
      width: 100%;
      height: 100%;
    }

    /* ── A number, not a word ─────────────────────────────────────────────────
       A badge holding one or two digits needs none of the side room a word needs,
       and at the small size the padding for a word made a 23px oval sitting in a
       24px line: it filled the tab it was supposed to annotate.

       Tighter sides, a floor wide enough that a single digit comes out round
       rather than as a slot, and the leading closed up to the digits themselves,
       which have no descenders to leave room for. */
    .ds-badge--count {
      /* `small`, and it was `0.85em`.
         The case for the em was that a count belongs to the name beside it and
         should stay the same fraction of it. What it produced was 11.9px next to a
         14px label and 10.2px next to a 12px one: two sizes no token holds, both
         under the scale's own floor of 12, and both landing off the step of 4 the
         package is built on. A count is one or two digits and does not need a
         ramp; it needs to be readable, which is what the smallest rung is for. */
      --ds-badge-count-face: var(--ds-font-small-face);
      --ds-badge-count-size: var(--ds-font-small-size);
      font-family: var(--ds-badge-count-face);
      font-size: var(--ds-badge-count-size);
      justify-content: center;
      /* A count is as tall as the LINE it stands in, which is the same thing
         the icon slot below reads and for the same reason: a count belongs to a
         name, and what a name occupies is its line box, not its type size and
         not a rung of the control ramp. Off the ramp it came out 32 next to a
         word of 14, a badge wearing the row rather than sitting in it (owner,
         7 September 2026: "in tanti casi vedo questi badge enormi"). Held at
         one fixed rung instead, it was 20 against the 16 of a small control's
         line, so a button carrying a count stood 36 where its tier says 32 and
         the row read as two sizes — which is the same complaint, arriving from
         the other side (owner, same day: "segmented small e button medium").
         The tier's own leading is the number that is right in both directions.
         Round on one digit and a pill on more, without measuring the text: a
         floor equal to the height is a circle until the digits are wider than
         it. */
      --ds-badge-count-floor: var(--ds-badge-leading);
      box-sizing: border-box;
      min-block-size: var(--ds-badge-count-floor);
      block-size: var(--ds-badge-count-floor);
      min-inline-size: var(--ds-badge-count-floor);
      padding-block: 0;
      /* The smallest rung, and it has to be: the floor only makes a circle while
         the digit and its two paddings fit inside it. A 12px digit is 10 wide, so
         at the rung above it came to 18 in a floor of 16 and one digit was an oval
         (measured on the docs, 7 September 2026). Two lots of two leaves 14 in 16,
         and a number of two digits still has room at its sides when it widens the
         pill past the floor. */
      padding-inline: var(--ds-space-0-5);
      border-radius: var(--ds-radius-full);
      /* Digits of one width, as the notify pill's below and the avatar stack's
         count: counts stand in a column, at the end of every row of a list, and
         "13" measured 19.1 against "25" at 21.8 on proportional figures, so the
         column's other edge, where a progress bar ended, moved 3px from row to
         row (owner's screenshot, 10 September 2026). One digit stays a circle
         and two a pill, as the floor decides; what this removes is two pills of
         two digits being two widths. */
      font-variant-numeric: tabular-nums;
      /* The absence of a leading, like the notify pill below it and the
         avatar's initials: a count in a pill is a mark that happens to be a
         digit, and a line from the type scale puts space inside it that nothing
         reads. The floor above is what holds the height. */
      line-height: 1;
    }

    /* The label truncates rather than wraps: a badge on two lines stops reading
       as one object. */
    .ds-badge__label {
      overflow: hidden;
      text-overflow: ellipsis;
    }

    /* ── Tones ────────────────────────────────────────────────────────────────
       Four variables per tone, and every appearance below is a different pick
       from the same four. Neutral is not a status and does not borrow a status
       token: it reads the interactive neutral, the one grey in the system that
       already ships a tint and an edge. */
    .ds-badge--neutral {
      --ds-badge-ink: var(--ds-text-primary-neutral);
      --ds-badge-tint: var(--ds-interactive-tint);
      --ds-badge-edge: var(--ds-interactive-edge);
      --ds-badge-solid: var(--ds-inverse);
      --ds-badge-on-solid: var(--ds-on-inverse);
    }

    .ds-badge--brand {
      --ds-badge-ink: var(--ds-brand-ink);
      --ds-badge-tint: var(--ds-brand-tint);
      --ds-badge-edge: var(--ds-brand-edge);
      --ds-badge-solid: var(--ds-brand);
      --ds-badge-on-solid: var(--ds-on-brand);
    }

    .ds-badge--success {
      --ds-badge-ink: var(--ds-success-ink);
      --ds-badge-tint: var(--ds-success-tint);
      --ds-badge-edge: var(--ds-success-edge);
      --ds-badge-solid: var(--ds-success);
      --ds-badge-on-solid: var(--ds-on-success);
    }

    .ds-badge--warning {
      --ds-badge-ink: var(--ds-warning-ink);
      --ds-badge-tint: var(--ds-warning-tint);
      --ds-badge-edge: var(--ds-warning-edge);
      --ds-badge-solid: var(--ds-warning);
      --ds-badge-on-solid: var(--ds-on-warning);
    }

    .ds-badge--danger {
      --ds-badge-ink: var(--ds-danger-ink);
      --ds-badge-tint: var(--ds-danger-tint);
      --ds-badge-edge: var(--ds-danger-edge);
      --ds-badge-solid: var(--ds-danger);
      --ds-badge-on-solid: var(--ds-on-danger);
    }

    .ds-badge--info {
      --ds-badge-ink: var(--ds-info-ink);
      --ds-badge-tint: var(--ds-info-tint);
      --ds-badge-edge: var(--ds-info-edge);
      --ds-badge-solid: var(--ds-info);
      --ds-badge-on-solid: var(--ds-on-info);
    }

    /* ── The hues ─────────────────────────────────────────────────────────────
       Twelve colours that mean nothing but which one this is, read through the
       same five parts a tone is, so a badge with a hue is the same badge. */
    .ds-badge--hue-red {
      --ds-badge-ink: var(--ds-hue-red-ink);
      --ds-badge-tint: var(--ds-hue-red-tint);
      --ds-badge-edge: var(--ds-hue-red-edge);
      --ds-badge-solid: var(--ds-hue-red);
      --ds-badge-on-solid: var(--ds-on-hue-red);
    }

    .ds-badge--hue-orange {
      --ds-badge-ink: var(--ds-hue-orange-ink);
      --ds-badge-tint: var(--ds-hue-orange-tint);
      --ds-badge-edge: var(--ds-hue-orange-edge);
      --ds-badge-solid: var(--ds-hue-orange);
      --ds-badge-on-solid: var(--ds-on-hue-orange);
    }

    .ds-badge--hue-amber {
      --ds-badge-ink: var(--ds-hue-amber-ink);
      --ds-badge-tint: var(--ds-hue-amber-tint);
      --ds-badge-edge: var(--ds-hue-amber-edge);
      --ds-badge-solid: var(--ds-hue-amber);
      --ds-badge-on-solid: var(--ds-on-hue-amber);
    }

    .ds-badge--hue-yellow {
      --ds-badge-ink: var(--ds-hue-yellow-ink);
      --ds-badge-tint: var(--ds-hue-yellow-tint);
      --ds-badge-edge: var(--ds-hue-yellow-edge);
      --ds-badge-solid: var(--ds-hue-yellow);
      --ds-badge-on-solid: var(--ds-on-hue-yellow);
    }

    .ds-badge--hue-lime {
      --ds-badge-ink: var(--ds-hue-lime-ink);
      --ds-badge-tint: var(--ds-hue-lime-tint);
      --ds-badge-edge: var(--ds-hue-lime-edge);
      --ds-badge-solid: var(--ds-hue-lime);
      --ds-badge-on-solid: var(--ds-on-hue-lime);
    }

    .ds-badge--hue-green {
      --ds-badge-ink: var(--ds-hue-green-ink);
      --ds-badge-tint: var(--ds-hue-green-tint);
      --ds-badge-edge: var(--ds-hue-green-edge);
      --ds-badge-solid: var(--ds-hue-green);
      --ds-badge-on-solid: var(--ds-on-hue-green);
    }

    .ds-badge--hue-teal {
      --ds-badge-ink: var(--ds-hue-teal-ink);
      --ds-badge-tint: var(--ds-hue-teal-tint);
      --ds-badge-edge: var(--ds-hue-teal-edge);
      --ds-badge-solid: var(--ds-hue-teal);
      --ds-badge-on-solid: var(--ds-on-hue-teal);
    }

    .ds-badge--hue-cyan {
      --ds-badge-ink: var(--ds-hue-cyan-ink);
      --ds-badge-tint: var(--ds-hue-cyan-tint);
      --ds-badge-edge: var(--ds-hue-cyan-edge);
      --ds-badge-solid: var(--ds-hue-cyan);
      --ds-badge-on-solid: var(--ds-on-hue-cyan);
    }

    .ds-badge--hue-blue {
      --ds-badge-ink: var(--ds-hue-blue-ink);
      --ds-badge-tint: var(--ds-hue-blue-tint);
      --ds-badge-edge: var(--ds-hue-blue-edge);
      --ds-badge-solid: var(--ds-hue-blue);
      --ds-badge-on-solid: var(--ds-on-hue-blue);
    }

    .ds-badge--hue-indigo {
      --ds-badge-ink: var(--ds-hue-indigo-ink);
      --ds-badge-tint: var(--ds-hue-indigo-tint);
      --ds-badge-edge: var(--ds-hue-indigo-edge);
      --ds-badge-solid: var(--ds-hue-indigo);
      --ds-badge-on-solid: var(--ds-on-hue-indigo);
    }

    .ds-badge--hue-violet {
      --ds-badge-ink: var(--ds-hue-violet-ink);
      --ds-badge-tint: var(--ds-hue-violet-tint);
      --ds-badge-edge: var(--ds-hue-violet-edge);
      --ds-badge-solid: var(--ds-hue-violet);
      --ds-badge-on-solid: var(--ds-on-hue-violet);
    }

    .ds-badge--hue-pink {
      --ds-badge-ink: var(--ds-hue-pink-ink);
      --ds-badge-tint: var(--ds-hue-pink-tint);
      --ds-badge-edge: var(--ds-hue-pink-edge);
      --ds-badge-solid: var(--ds-hue-pink);
      --ds-badge-on-solid: var(--ds-on-hue-pink);
    }

    /* ── Fill ─────────────────────────────────────────────────────────────────
       How loud the badge is, from a block of colour down to a line around it. */
    .ds-badge--fill-solid {
      background-color: var(--ds-badge-solid);
      border-color: var(--ds-badge-solid);
      color: var(--ds-badge-on-solid);
    }

    .ds-badge--fill-soft {
      background-color: var(--ds-badge-tint);
      border-color: var(--ds-badge-edge);
      color: var(--ds-badge-ink);
    }

    .ds-badge--fill-none {
      background-color: transparent;
      border-color: var(--ds-badge-ink);
      color: var(--ds-badge-ink);
    }

    /* ── When it is a control ─────────────────────────────────────────────────
       A badge that filters a list is a button that looks like a badge, and it has
       to behave like one: it reacts to a pointer, it takes focus from a keyboard,
       and when it is a toggle it says whether it is on.

       The reaction is a wash of the tone laid over whatever fill is underneath,
       so one rule covers all three appearances instead of three rules covering
       one each. `currentColor` is the tone in `soft` and `none` and the
       on-colour in `solid`, which is exactly the right thing to lighten by in
       both directions. */
    /* No `font: inherit` here, which is the reflex when a rule turns something
       into a button and the reason this shipped wrong once. The badge IS the
       button in this case, so inheriting the font takes it from the row outside
       and wipes the size, the weight and the uppercase the rules above just set:
       an interactive badge came out 16px lower-case beside a 14px uppercase one.
       Nothing needs resetting, because every one of those properties is already
       declared on `.ds-badge` and beats the user agent on its own. */
    .ds-badge--control {
      cursor: pointer;
      appearance: none;
      transition:
        background-color var(--ds-duration) var(--ds-ease),
        box-shadow var(--ds-duration) var(--ds-ease);
    }

    /* The wash is an inset shadow rather than a gradient laid over the fill. Same
       result, one difference that matters: a gradient does not interpolate, so the
       old one appeared and vanished on the frame the pointer arrived, while a
       shadow fades with everything else. */
    .ds-badge--control:hover {
      box-shadow: inset 0 0 0 100px color-mix(in srgb, currentColor 12%, transparent);
    }

    .ds-badge--control:active {
      box-shadow: inset 0 0 0 100px color-mix(in srgb, currentColor 20%, transparent);
    }

    /* Outside the element, so it is not swallowed by the badge next to it in a
       tight row of filters. */
    .ds-badge--control:focus-visible {
      outline: 2px solid var(--ds-focus);
      outline-offset: 2px;
    }

    /* Selected is a state and not a fourth appearance: it takes the solid look
       whatever the badge was before, so a row of soft filters reads as one
       selected and five not, rather than as six things of slightly different
       weights. Written after the fill rules so it wins without a specificity
       trick. */
    .ds-badge--selected {
      background-color: var(--ds-badge-solid);
      border-color: var(--ds-badge-solid);
      color: var(--ds-badge-on-solid);
    }

    /* The label as a button, which is the arrangement a badge that is BOTH
       clickable and removable has to take: nested buttons are parsed apart by the
       browser, so the cross cannot sit inside the clickable part. Styled to
       disappear, since the badge around it is already the control's skin. */
    .ds-badge__action {
      display: inline-flex;
      align-items: center;
      gap: inherit;
      min-width: 0;
      padding: 0;
      border: 0;
      background: none;
      /* `font: inherit` IS right here, unlike on the badge itself: this button is
         inside the badge, so what it inherits is the badge's own type. The other
         two are not covered by the shorthand and are not inherited either, because
         the user agent sets them on form controls: without them the label came out
         lower-case and untracked inside an uppercase badge. Measured. */
      font: inherit;
      text-transform: inherit;
      letter-spacing: inherit;
      color: inherit;
      cursor: pointer;
    }

    .ds-badge__action:focus-visible {
      outline: 2px solid var(--ds-focus);
      outline-offset: 2px;
      border-radius: var(--ds-radius-1);
    }

    /* ── Remove ───────────────────────────────────────────────────────────────
       The cross marks what pressing does. When removing is all the badge does,
       the whole pill is the button and this is the mark on it: a 17px cross is
       under every target floor there is, and the pill around it was spending 90%
       of itself on nothing. When the badge also has a click of its own, the cross
       goes back to being a button beside it, because then the two halves do two
       different things and only one of them can have the whole shape.

       Nudged into the padding on its own side. Without it the cross floats in the
       middle of 0.7em of space and the badge looks lopsided next to one without
       a cross.

       The DRAWN size and the TARGET size are two different numbers here, and
       conflating them is what made the first version hard to hit: the circle was
       1.15em, which is 16px next to 14px text, and 16px is a target you aim at
       rather than press. WCAG 2.5.8 asks for 24. The circle is now 1.4em and the
       target is pushed out to 24px by a pseudo-element, so the thing you can
       press is larger than the thing you can see and neither the badge nor the
       row around it moves by a pixel. */
    .ds-badge__remove {
      /* A control inherits neither the face nor the size, so this was drawn in the
         user agent's Arial at its 13.33px: measured on the docs pages, four
         classes in the package rendered in a face this system does not have. Worse
         here than it looks, because the box below is in `em` and `em` was counting
         against Arial rather than against the label beside it. */
      font: inherit;
      position: relative;
      display: inline-flex;
      align-items: center;
      justify-content: center;
      flex: none;
      width: 1.4em;
      height: 1.4em;
      margin-inline-end: -0.35em;
      padding: 0;
      border: 0;
      border-radius: var(--ds-radius-full);
      background: transparent;
      color: inherit;
      cursor: pointer;
      opacity: 0.65;
      transition:
        opacity var(--ds-duration) var(--ds-ease),
        background-color var(--ds-duration) var(--ds-ease);
    }

    /* Negative when 24px is the larger of the two, which is every size the badge
       ships. Written as a formula rather than as -4px so it stays correct if the
       drawn size ever changes. */
    .ds-badge__remove::after {
      content: "";
      position: absolute;
      inset: calc((1.4em - max(24px, 1.4em)) / 2);
    }

    .ds-badge__remove > svg {
      width: 0.8em;
      height: 0.8em;
    }

    .ds-badge__remove:hover {
      opacity: 1;
      background-color: color-mix(in srgb, currentColor 18%, transparent);
    }

    .ds-badge__remove:focus-visible {
      opacity: 1;
      outline: 2px solid var(--ds-focus);
      outline-offset: 1px;
    }

    /* ── Notification ─────────────────────────────────────────────────────────
       The count on the corner of a mark, which is a different object from the
       count beside a name and so is a different modifier.

       A count belongs to the word it hangs off and takes that word's size. This
       belongs to nothing: it stands over a drawing, so it carries its own size,
       and it is always the solid fill — soft red on a dark icon on a dark bar is
       three unrelated things at the same brightness.

       The ring is the part everybody forgets. Without it the badge and the mark
       under it touch, and at this size two touching shapes read as one smudge.
       It is drawn in the ground the badge is standing on, which the host names
       with `--ds-badge-ring` when that is not the page's own. */
    .ds-badge--notify {
      /* Border-box, unlike the rest of the badge: the floor below has to be the
         whole thing or it is not a circle. Measured content-box first — a floor of
         16 came out 26 wide and 18 tall. */
      box-sizing: border-box;
      /* Its own, not the label's: nothing here tracks a word. */
      /* `small` as well, and it was `0.6875rem`: an 11px written as a decimal with
         the number in a comment beside it, the only size in the package below the
         scale. */
      --ds-badge-floor-face: var(--ds-font-small-face);
      --ds-badge-floor-size: var(--ds-font-small-size);
      font-family: var(--ds-badge-floor-face);
      font-size: var(--ds-badge-floor-size);
      line-height: 1;
      /* Round on one digit, a pill on more, without the count having to be
         measured: a floor equal to the height is a circle until the text is wider
         than it. */
      min-inline-size: var(--ds-space-5);
      /* Both, or neither holds: the base takes its height from the tier, and a
         `min-block-size` of 32 beats a `block-size` of 20 in silence. This pill
         said 20 and stood at 32, which made the circle an oval. Measured on the
         docs, 7 September 2026, alongside the same fault in the count. */
      min-block-size: var(--ds-space-5);
      block-size: var(--ds-space-5);
      padding-block: 0;
      padding-inline: var(--ds-space-1);
      justify-content: center;
      border-radius: var(--ds-radius-full);
      border-color: transparent;
      text-transform: none;
      letter-spacing: 0;
      font-variant-numeric: tabular-nums;
      box-shadow: 0 0 0 2px var(--ds-badge-ring, var(--ds-surface-0));
    }

    /* Something happened, and the number does not matter or is not known. */
    .ds-badge--dot {
      box-sizing: border-box;
      min-inline-size: var(--ds-space-2);
      inline-size: var(--ds-space-2);
      /* The floor as well as the height, for the reason the pill above gives:
         whatever holds the box has to say both, or the larger minimum wins in
         silence and the dot comes out an oval. */
      min-block-size: var(--ds-space-2);
      block-size: var(--ds-space-2);
      padding: 0;
    }

    /* ==========================================================================
       Breadcrumb

       The CSS behind <Breadcrumb>. Private, like every other component's.

       Where you are, and the way back up.

       ── The separator is drawn, not written ───────────────────────────────────
       It is a `::before` on every crumb but the first, so it is in the stylesheet
       and not in the accessible name. A slash typed between two links is a slash
       a screen reader reads out, once per level, on every page of a site.
       ========================================================================== */

    .ds-breadcrumb {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-breadcrumb-face: var(--ds-font-label-face);
      --ds-breadcrumb-tracking: var(--ds-font-label-tracking);
      --ds-breadcrumb-size: var(--ds-font-label-size);
      --ds-breadcrumb-weight: var(--ds-font-label-weight);
      --ds-breadcrumb-leading: var(--ds-font-label-leading);
      /* This component's own. The role says what the words are; this says that
         THIS component wants a heavier hand than the role has, which is a
         decision the component makes and nobody else. Pointed at the semantic
         rather than holding a number, so moving the system moves every component
         that has not been moved on purpose, and moving one moves one. */
      --ds-breadcrumb-here-weight: var(--ds-emphasis-weight);
      display: flex;
      flex-wrap: wrap;
      align-items: center;
      gap: var(--ds-space-1);
      font-family: var(--ds-breadcrumb-face);
      font-size: var(--ds-breadcrumb-size);
      letter-spacing: var(--ds-breadcrumb-tracking);
      font-weight: var(--ds-breadcrumb-weight);
      line-height: var(--ds-breadcrumb-leading);
    }

    .ds-breadcrumb__list {
      display: contents;
      margin: 0;
      padding: 0;
      list-style: none;
    }

    .ds-breadcrumb__crumb {
      display: flex;
      align-items: center;
      gap: var(--ds-space-1);
      min-inline-size: 0;
    }

    .ds-breadcrumb__crumb + .ds-breadcrumb__crumb::before {
      content: "";
      display: inline-block;
      inline-size: 0.4em;
      block-size: 0.4em;
      /* Its own name, like every ink in the package, so the inspector can say
         whose decision the colour is. */
      --ds-breadcrumb-separator-ink: var(--ds-text-tertiary-neutral);
      border-block-start: 1px solid var(--ds-breadcrumb-separator-ink);
      border-inline-end: 1px solid var(--ds-breadcrumb-separator-ink);
      rotate: 45deg;
      /* Half a space of its own on each side, on top of the row's gap, or the
         chevron sits against the word it follows. */
      margin-inline: var(--ds-space-1);
      flex: none;
    }

    .ds-breadcrumb__link {
      --ds-breadcrumb-link-ink: var(--ds-text-secondary-neutral);
      color: var(--ds-breadcrumb-link-ink);
      text-decoration: none;
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
      /* The target, the checkbox's own fix: padding grows the box the pointer
         can hit and the negative margin gives the room straight back, so nothing
         on the row moves. A bare label line is 16px, under the 24px floor
         `--ds-size-1` names; two spaces of block padding take it to 32, measured,
         with a space each side inline so neighbouring targets stay 11px apart.
         The focus ring follows the padding box and wraps the word with room to
         breathe. */
      padding: var(--ds-space-2) var(--ds-space-1);
      margin: calc(var(--ds-space-2) * -1) calc(var(--ds-space-1) * -1);
      transition: color var(--ds-duration) var(--ds-ease);
    }

    /* The ink stepping up to primary is the whole hover. An underline said the
       same thing a second time, and the breadcrumb is navigation chrome like the
       sidebar and the header, whose links say it once: prose keeps the underline
       because prose links live inside running text, where colour alone cannot
       mark them. Owner's call, 22 August 2026. */
    .ds-breadcrumb__link:hover {
      --ds-breadcrumb-link-ink: var(--ds-text-primary-neutral);
      color: var(--ds-breadcrumb-link-ink);
    }

    .ds-breadcrumb__link:focus-visible {
      outline: 2px solid var(--ds-focus);
      outline-offset: 2px;
      border-radius: var(--ds-radius-1);
    }

    /* The page you are on is not a link, because it goes nowhere. It is the
       loudest thing in the row, since it is the answer to "where am I". */
    .ds-breadcrumb__here {
      --ds-breadcrumb-here-ink: var(--ds-text-primary-neutral);
      color: var(--ds-breadcrumb-here-ink);
      font-weight: var(--ds-breadcrumb-here-weight);
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
    }

    /* ==========================================================================
       Button

       The CSS behind <Button>. Not public: nothing outside this package types
       these names, and they are absent from the exports map for that reason.

       A button does something when you press it. That is the whole test, and it
       is why a link that looks like a button is still a link: the difference is
       whether the page changes or something happens.

       ── Three fills ───────────────────────────────────────────────────────────
       `solid` is a block of colour, for the action a screen is there to perform.
       `outline` is a line around it, for the actions beside that one. `none` is
       the label alone, for a row of them where a line each would be a fence.

       The quiet form is a line because the line is the invitation: a rectangle of
       colour with no edge reads as a state, and a control has to read as a
       control before it reads as anything else.

       Four tones. `neutral` is the default and is the page's own opposite.
       `brand` is for the one action a product is built around, which is rare.
       `success` confirms and completes: the button at the end of a flow.
       `danger` destroys.

       ── Six heights, three text sizes ─────────────────────────────────────────
       The heights are the control ramp in tokens/size.css, which every control in
       the system reads. One ladder is what makes two controls on one row the same
       height by construction instead of by two files agreeing on a number.

       Height and text are two ladders and they are not the same length. Height is
       the target and how much room the control takes; text is reading, and
       reading has three useful sizes down here. So the small end takes 12, the
       middle three take 14, the top two take 16. A 64px button with 14px text in
       it looks like a mistake, and six text sizes to match six heights would be
       five decisions nobody makes the same way twice.
       ========================================================================== */

    .ds-button {
      --ds-button-face: var(--ds-font-body-face);
      /* This component's own. The role says what the words are; this says that
         THIS component wants a heavier hand than the role has, which is a
         decision the component makes and nobody else. Pointed at the semantic
         rather than holding a number, so moving the system moves every component
         that has not been moved on purpose, and moving one moves one. */
      --ds-button-weight: var(--ds-emphasis-weight);
      /* The properties this component sets off the space ramp on purpose, named
         so a tool reads the decision instead of reporting it as a mistake. The
         padding and the gap are in `em` because they track the label rather than
         the box, which is written out below; every tier would otherwise be a
         number the ramp cannot make, and rightly flagged. */
      /* Off `em` and onto the ramp, both of them.
         `gap: 0.5em` was 7px beside a 14px label and 6 beside a 12px one, and the
         inline padding was 12.6, 18.4, 22.4: six numbers no token holds, on a
         package whose every other measurement is a step of 4. The case for `em`
         was that the box should track the label, and the type ramp already tracks
         the label: each tier takes the step of the ramp that suits it, which is
         the same idea said with a name.
         `padding-block` stays a `calc`, because it is not spacing: it is what
         centres the line in the tier, and it is arithmetic on two tokens. */
      --ds-button-gap: var(--ds-space-2);
      /* The block padding is `(tier - leading - 2px) / 2`, the room left over
         once the line is centred in the tier: arithmetic on two tokens, and a
         decision, so each tier names it `--ds-button-pad` and the docs'
         inspector answers with the name. It was declared "on purpose" through
         `--ds-button-intent` until 4 September 2026, which told a reader the
         number was meant and not what it meant; InlineEdit named its pad the
         same day and the two are one way now. `intent` stays for what is
         truly off the scale, a line-height or a gap, in the components that
         declare it. */
      /* Named, not inherited. The user agent gives form controls `border-box` and
         leaves anchors on `content-box`, so `--block` at `inline-size: 100%` came
         out 2px wider as an `<a>` than as a `<button>` and hung over the edge of
         its container. Measured: 926 against 959 in the same slot. */
      box-sizing: border-box;
      display: inline-flex;
      align-items: center;
      justify-content: center;
      gap: var(--ds-button-gap);
      /* Written out, per tier, below. The user agent pads a <button> by 1px top
         and bottom and nothing here ever said so, so a panel reading the box back
         found "1 0": a number the system does not own, and the same undeclared
         pair is what made a 24px segmented strip come out 28.

         What replaces it is not a step of the ramp, because this one is not a free
         choice: the height is the tier, the line box comes from the size, the
         border is a pixel a side, and the room above and below is what is left.
         Each tier writes that subtraction out, so the box is the system's from
         edge to edge and a reader can check it. */
      /* Two of the consumer's hooks, both paid for before they existed: the
         first project's buttons are uppercase with a 2px outline, and with no
         lever for either it wrote the two properties straight into this
         selector, the only system selector it ever wrote inside. `case` is what
         the label wears; `edge-width` is how thick the line is, and the padding
         arithmetic below subtracts it so the tier holds when it moves. Width in
         the name, because `--ds-button-edge` is taken: it is the line's COLOUR,
         and a colour substituted into a border-width invalidates the whole
         declaration, measured as a border of zero the day this hook was born. */
      border: var(--ds-button-edge-width, 1px) solid transparent;
      text-transform: var(--ds-button-case, none);
      /* Step 2, where the badge takes step 4. The same value that reads as a pill
         on something 26px tall reads as inflated on something 40px tall, and the
         ramp is a ramp precisely so two components can sit at different points on
         it and still move together. */
      border-radius: var(--ds-radius-2);
      font-family: var(--ds-button-face);
      font-weight: var(--ds-button-weight);
      text-align: center;
      text-decoration: none;
      white-space: nowrap;
      cursor: pointer;
      /* Under the label, so a long one wins over the fixed width of a short one
         and the button grows instead of clipping. */
      min-inline-size: fit-content;
      transition:
        background-color var(--ds-duration) var(--ds-ease),
        border-color var(--ds-duration) var(--ds-ease);
    }

    /* Sizes. Height in rem so a row of buttons lines up whatever is inside them;
       padding in em so it tracks the label rather than the box. The type is the
       ramp's own, written out in `tokens/size.css`: 12, 14, then 16 the rest of
       the way up. */
    .ds-button--xsmall {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-button-face: var(--ds-font-small-face);
      --ds-button-tracking: var(--ds-font-small-tracking);
      --ds-button-size: var(--ds-font-small-size);
      --ds-button-leading: var(--ds-font-small-leading);
      font-family: var(--ds-button-face);
      font-size: var(--ds-button-size); line-height: var(--ds-button-leading); /* 12 */
      letter-spacing: var(--ds-button-tracking);
      min-block-size: var(--ds-size-1);
      --ds-button-pad: calc((var(--ds-size-1) - var(--ds-font-small-leading) - var(--ds-button-edge-width, 1px) * 2) / 2);
      padding-block: var(--ds-button-pad);
      padding-inline: var(--ds-space-2);
    }

    .ds-button--small {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-button-face: var(--ds-font-label-face);
      --ds-button-tracking: var(--ds-font-label-tracking);
      --ds-button-size: var(--ds-font-label-size);
      --ds-button-leading: var(--ds-font-label-leading);
      font-family: var(--ds-button-face);
      font-size: var(--ds-button-size); line-height: var(--ds-button-leading); /* 14 */
      letter-spacing: var(--ds-button-tracking);
      min-block-size: var(--ds-size-2);
      --ds-button-pad: calc((var(--ds-size-2) - var(--ds-font-label-leading) - var(--ds-button-edge-width, 1px) * 2) / 2);
      padding-block: var(--ds-button-pad);
      padding-inline: var(--ds-space-3);
    }

    .ds-button--medium {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-button-face: var(--ds-font-body-face);
      --ds-button-tracking: var(--ds-font-body-tracking);
      --ds-button-size: var(--ds-font-body-size);
      --ds-button-leading: var(--ds-font-body-leading);
      font-family: var(--ds-button-face);
      font-size: var(--ds-button-size); line-height: var(--ds-button-leading); /* 16 */
      letter-spacing: var(--ds-button-tracking);
      min-block-size: var(--ds-size-3);
      --ds-button-pad: calc((var(--ds-size-3) - var(--ds-font-body-leading) - var(--ds-button-edge-width, 1px) * 2) / 2);
      padding-block: var(--ds-button-pad);
      padding-inline: var(--ds-space-4);
    }

    .ds-button--large {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-button-face: var(--ds-font-body-face);
      --ds-button-tracking: var(--ds-font-body-tracking);
      --ds-button-size: var(--ds-font-body-size);
      --ds-button-leading: var(--ds-font-body-leading);
      font-family: var(--ds-button-face);
      font-size: var(--ds-button-size); line-height: var(--ds-button-leading); /* 16 */
      letter-spacing: var(--ds-button-tracking);
      min-block-size: var(--ds-size-4);
      --ds-button-pad: calc((var(--ds-size-4) - var(--ds-font-body-leading) - var(--ds-button-edge-width, 1px) * 2) / 2);
      padding-block: var(--ds-button-pad);
      padding-inline: var(--ds-space-5);
    }

    .ds-button--xlarge {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-button-face: var(--ds-font-body-face);
      --ds-button-tracking: var(--ds-font-body-tracking);
      --ds-button-size: var(--ds-font-body-size);
      --ds-button-leading: var(--ds-font-body-leading);
      font-family: var(--ds-button-face);
      font-size: var(--ds-button-size); line-height: var(--ds-button-leading); /* 16 */
      letter-spacing: var(--ds-button-tracking);
      min-block-size: var(--ds-size-5);
      --ds-button-pad: calc((var(--ds-size-5) - var(--ds-font-body-leading) - var(--ds-button-edge-width, 1px) * 2) / 2);
      padding-block: var(--ds-button-pad);
      padding-inline: var(--ds-space-6);
    }

    .ds-button--xxlarge {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-button-face: var(--ds-font-body-face);
      --ds-button-tracking: var(--ds-font-body-tracking);
      --ds-button-size: var(--ds-font-body-size);
      --ds-button-leading: var(--ds-font-body-leading);
      font-family: var(--ds-button-face);
      font-size: var(--ds-button-size); line-height: var(--ds-button-leading); /* 16 */
      letter-spacing: var(--ds-button-tracking);
      min-block-size: var(--ds-size-6);
      --ds-button-pad: calc((var(--ds-size-6) - var(--ds-font-body-leading) - var(--ds-button-edge-width, 1px) * 2) / 2);
      padding-block: var(--ds-button-pad);
      padding-inline: var(--ds-space-8);
    }

    /* Nothing but a mark. Square on its own tier, and the room at the sides goes:
       a button holding one glyph and a label's worth of padding either side is a
       pill, it is never as wide as it is tall, and it cannot line up with anything
       else that stands on the ramp. A toolbar of them, or a bar over a column of
       links, is where that shows.

       The tier is the width as well as the height, so the six of them stay square
       at every step and under every text setting. */
    .ds-button--icon {
      padding-inline: 0;
    }

    .ds-button--icon.ds-button--xsmall { inline-size: var(--ds-size-1); }
    .ds-button--icon.ds-button--small { inline-size: var(--ds-size-2); }
    .ds-button--icon.ds-button--medium { inline-size: var(--ds-size-3); }
    .ds-button--icon.ds-button--large { inline-size: var(--ds-size-4); }
    .ds-button--icon.ds-button--xlarge { inline-size: var(--ds-size-5); }
    .ds-button--icon.ds-button--xxlarge { inline-size: var(--ds-size-6); }

    /* Full width, for a form on a phone where the button is the only thing on its
       line and reaching the middle of the screen is the whole job. */
    .ds-button--block {
      display: flex;
      inline-size: 100%;
    }

    /* The icon slots, sized by the component so a button handed a 24px icon does
       not grow to fit it. */
    .ds-button__icon {
      display: inline-flex;
      flex: none;
      inline-size: 1lh;
      block-size: 1lh;
    }

    .ds-button__icon > svg {
      inline-size: 100%;
      block-size: 100%;
    }

    /* ── Tones ────────────────────────────────────────────────────────────────
       Each tone names the colours it uses, including the ones for being hovered
       and being pressed. Those are real colours out of the palette, one step and
       two steps along from the resting one: darker in light and lighter in dark,
       because the way to say "you are on it" is towards the page's opposite.

       An earlier version mixed the reaction out of `currentColor` instead. It was
       one rule for all three fills and it was a computed film over a colour
       rather than a colour, so nobody could see what it would be, no token
       carried it, and a brand override changed the resting state and not the
       pressed one. */
    .ds-button--inverse {
      --ds-button-ink: var(--ds-text-primary-neutral);
      --ds-button-tint: var(--ds-interactive-tint);
      --ds-button-tint-hover: var(--ds-interactive-tint-hover);
      /* The edge is the fill, drawn as a line. An outlined button is the solid
         one with its middle taken out, so the two read as the same button at two
         volumes: white on a dark theme, near-black on a light one, and the flip
         comes free with the tone. The control outline token is for a field, which
         is a container and not an action. */
      --ds-button-edge: var(--ds-inverse);
      /* The page quoted back at itself: near-black on a light theme, near-white on
         a dark one, and the label flips with it. That flip is what the tone IS,
         and it is why the tone is called what it is. It answered to `neutral` for
         a while, which is the one thing it is not: a neutral is a grey that keeps
         out of the way, and this is the loudest a button gets without spending the
         brand. The grey took the name back and this took its own. */
      --ds-button-solid: var(--ds-inverse);
      --ds-button-solid-hover: var(--ds-inverse-hover);
      --ds-button-solid-active: var(--ds-inverse-active);
      --ds-button-solid-edge: var(--ds-inverse);
      --ds-button-on-solid: var(--ds-on-inverse);
    }

    /* The grey one, and the tone the system had no word for. A control that reads
       as a control and asks for nothing: a tool in a strip, a second action beside
       a first, a row of equals where none of them wins.

       Solid takes surface 1, the sheet: a neutral button is a small card you
       press, sitting on the page with a line round it. Pressing walks it down the
       stack, 1 to 2 to 3, which is the same movement every filled thing makes:
       away from the page and towards contact.

       The line is the resting one and not the strong one. It was the strong one
       for a revision, on the argument that an outline which identifies a control
       owes 3:1 — but that is the checkbox's argument, and a checkbox is a shape
       with nothing written in it. A button always carries its label, and the
       guidance is explicit that a control with visible content needs no boundary
       contrast to be found. The same reading that exempts a field, and a button
       carries the words itself rather than borrowing them from a label above.

       The wash it gains when pointed at is the neutral one, the same four per cent
       of the page's own ink every quiet row and card takes. */
    .ds-button--neutral {
      --ds-button-ink: var(--ds-text-primary-neutral);
      --ds-button-tint: var(--ds-surface-hover);
      --ds-button-tint-hover: var(--ds-surface-active);
      --ds-button-edge: var(--ds-border);
      --ds-button-solid: var(--ds-surface-1);
      --ds-button-solid-hover: var(--ds-surface-2);
      --ds-button-solid-active: var(--ds-surface-3);
      /* No line on the filled one. The other tones set this to their own fill,
         which is the same thing said the long way: a solid neutral is a sheet and
         a sheet does not need drawing round. The line belongs to the outlined
         fill, where it is the whole control. */
      --ds-button-solid-edge: transparent;
      --ds-button-on-solid: var(--ds-text-primary-neutral);
    }

    .ds-button--brand {
      --ds-button-ink: var(--ds-brand-ink);
      --ds-button-tint: var(--ds-brand-tint);
      --ds-button-tint-hover: var(--ds-brand-tint-hover);
      --ds-button-edge: var(--ds-brand);
      --ds-button-solid: var(--ds-brand);
      --ds-button-solid-hover: var(--ds-brand-hover);
      --ds-button-solid-active: var(--ds-brand-active);
      --ds-button-solid-edge: var(--ds-brand);
      --ds-button-on-solid: var(--ds-on-brand);
    }

    .ds-button--danger {
      --ds-button-ink: var(--ds-danger-ink);
      --ds-button-tint: var(--ds-danger-tint);
      --ds-button-tint-hover: var(--ds-danger-tint-hover);
      --ds-button-edge: var(--ds-danger);
      --ds-button-solid: var(--ds-danger);
      --ds-button-solid-hover: var(--ds-danger-hover);
      --ds-button-solid-active: var(--ds-danger-active);
      --ds-button-solid-edge: var(--ds-danger);
      --ds-button-on-solid: var(--ds-on-danger);
    }

    .ds-button--success {
      --ds-button-ink: var(--ds-success-ink);
      --ds-button-tint: var(--ds-success-tint);
      --ds-button-tint-hover: var(--ds-success-tint-hover);
      --ds-button-edge: var(--ds-success);
      --ds-button-solid: var(--ds-success);
      --ds-button-solid-hover: var(--ds-success-hover);
      --ds-button-solid-active: var(--ds-success-active);
      --ds-button-solid-edge: var(--ds-success);
      --ds-button-on-solid: var(--ds-on-success);
    }

    /* ── Fill ─────────────────────────────────────────────────────────────── */
    /* The edge is its own variable rather than the fill repeated, so a tone whose
       fill sits close to the page can carry a real line without every other tone
       growing one. Today all three set it to their own fill. */
    .ds-button--fill-solid {
      background-color: var(--ds-button-solid);
      border-color: var(--ds-button-solid-edge);
      color: var(--ds-button-on-solid);
    }

    /* The edge is the tone's own colour and not its 20% wash, because here the
       line is the whole control: a badge's edge sits around a tint that is
       already visible, and this one has nothing behind it. All three measure
       above the 3:1 WCAG 1.4.11 asks of anything that identifies a control. */
    .ds-button--fill-outline {
      background-color: transparent;
      border-color: var(--ds-button-edge);
      color: var(--ds-button-ink);
    }

    .ds-button--fill-none {
      background-color: transparent;
      border-color: transparent;
      color: var(--ds-button-ink);
    }

    /* ── Reacting ─────────────────────────────────────────────────────────────
       Each fill moves to the colour named for the state, which means a reader can
       look up what a pressed danger button is instead of computing it. */
    .ds-button--fill-solid:hover:not(:disabled) {
      background-color: var(--ds-button-solid-hover);
      border-color: var(--ds-button-solid-hover);
    }

    .ds-button--fill-solid:active:not(:disabled) {
      background-color: var(--ds-button-solid-active);
      border-color: var(--ds-button-solid-active);
    }

    /* The quiet fills gain a wash and then a deeper one, which is the same
       movement the solid makes: towards being filled. */
    /* A button that stays on. The one that opens a panel is not doing something
       and finishing, it is holding something open, and a fill says "pressed once"
       where a lit edge says "still on": the outline takes the active colour and
       the label comes up to the reading weight. */
    .ds-button--fill-none[aria-pressed="true"]:not(:disabled),
    .ds-button--fill-outline[aria-pressed="true"]:not(:disabled) {
      --ds-button-edge: var(--ds-border-active);
      /* Written twice on purpose: the variable is what the rest of the file
         reads, and the property is what wins if a later rule in this file has
         already resolved it. */
      border-color: var(--ds-border-active);
      --ds-button-ink: var(--ds-text-primary-neutral);
      color: var(--ds-button-ink);
    }

    /* The quiet one has no edge to light, so its ground says it instead, and it
       says it in the button's own tint rather than a surface token: the tone
       already declares that variable, so it resolves wherever the button is, and
       a property whose value cannot resolve is not a quiet failure but the
       initial value, which for a background is transparent. */
    .ds-button--fill-none[aria-pressed="true"]:not(:disabled) {
      background-color: var(--ds-button-tint-hover);
    }

    /* ── On the inverse ground ────────────────────────────────────────────────
       A button standing on `--ds-inverse` is standing on the other theme's page,
       and the page's own inks vanish into it. The container that paints that
       ground says so with `data-ground="inverse"`, state like `data-open`, and
       this rule answers it: the ink, the edge and the two washes move to the
       inverse-tuned values. Born for the toast's cross on 22 August 2026; any
       inverse banner gets it for free. Not inside a popover, a dialog or a
       drawer the ground opened: those stand on the page, and a Cancel drawn
       with the ground's inks on the page's surface was a button nobody could
       see (owner, 11 September 2026). */
    [data-ground="inverse"] .ds-button:not(:is(.ds-popover, .ds-dialog, .ds-drawer) .ds-button) {
      --ds-button-ink: var(--ds-on-inverse);
      --ds-button-edge: var(--ds-secondary-on-inverse);
      --ds-button-tint: var(--ds-inverse-hover);
      --ds-button-tint-hover: var(--ds-inverse-active);
    }

    /* The solid, for the inverse TONE only: on its own ground this button's fill
       is invisible by definition, the block quoting itself, so it takes the
       quote family the scope declares and comes out as the page. The toned
       solids stay their own colour, because a brand action on a banner is still
       the brand's. */
    [data-ground="inverse"] .ds-button--inverse:not(:is(.ds-popover, .ds-dialog, .ds-drawer) .ds-button) {
      --ds-button-solid: var(--ds-quote);
      --ds-button-solid-hover: var(--ds-quote-hover);
      --ds-button-solid-active: var(--ds-quote-active);
      --ds-button-solid-edge: var(--ds-quote);
      --ds-button-on-solid: var(--ds-on-quote);
    }

    .ds-button--fill-outline:hover:not(:disabled),
    .ds-button--fill-none:hover:not(:disabled) {
      background-color: var(--ds-button-tint);
    }

    .ds-button--fill-outline:active:not(:disabled),
    .ds-button--fill-none:active:not(:disabled) {
      background-color: var(--ds-button-tint-hover);
    }

    /* ── Off ──────────────────────────────────────────────────────────────────
       Its own colours, not a faded version of the live ones. A disabled button
       should look wrong: the tone is gone, which is the point, because a control
       that keeps its colour while refusing to work is a control that still looks
       like the thing to press.

       Fill, border and label all move, so every appearance lands on the same
       grey and a row of three disabled buttons reads as one dead row rather than
       three tinted ones. */
    .ds-button:disabled {
      background-color: var(--ds-surface-disabled);
      border-color: var(--ds-border-disabled);
      --ds-button-ink: var(--ds-text-disabled);
      color: var(--ds-button-ink);
      cursor: not-allowed;
    }

    /* The label of a button that is carrying an icon, dropped where the room runs
       out. Opt in with `ds-button--terse`, because a button whose words vanish
       is only readable to somebody who knows the mark: right for a filter or an
       export in a strip over a table, wrong for the one that says Delete. The
       name stays in the accessible tree either way, since it is the button's own
       content and not a title on the icon. */
    /* The TOOLBAR it stands in, by name, and not the window.
       It was a media query, so a toolbar in a panel or a split view kept its
       labels while a toolbar in a wide window lost them, which is backwards.
       Named rather than nearest: `terse` is a toolbar's idea — the comment above
       says as much — and the nearest container to a button could be a header or a
       shell, neither of which has an opinion about a button's words. A button
       standing in no toolbar keeps its label, which is the safe way round. */
    @container ds-toolbar (width <= 30rem) {
      .ds-button--terse .ds-button__label {
        position: absolute;
        inline-size: 1px;
        block-size: 1px;
        margin: -1px;
        overflow: hidden;
        clip-path: inset(50%);
        white-space: nowrap;
      }
    }

    /* ── Quiet in a strip ─────────────────────────────────────────────────────
       A button in a toolbar is quiet, whatever fill it was handed. A row of
       outlines over a table draws a box round every tool and competes with the
       thing the tools are for, and that is not a judgement each call site should
       be making again: the strip knows what it is for.

       The rule lives here and not in the toolbar's file, though the toolbar is
       what triggers it, because what changes is the button's own wiring and a
       component may only declare the variables that carry its name. The check
       said so out loud when it was written the other way round.

       The edge goes; hover, press and the on state still answer, which is the
       whole of what a strip like this has to show. The solid fill is left alone:
       that is not habit, it is somebody saying "this one is the action".

       The neutral tone is left alone too, and it is the reason this rule can stay
       narrow. What it was written against was the inverse outline, a near-black
       line round every tool. A neutral outline is the resting line, which is the
       quietest thing that still says "this is a control", and taking it away is
       taking away the only thing that did. */
    .ds-toolbar__start .ds-button--fill-outline:not(.ds-button--neutral),
    .ds-toolbar__end .ds-button--fill-outline:not(.ds-button--neutral) {
      --ds-button-edge: transparent;
    }

    /* ==========================================================================
       Spinner

       The CSS behind <Spinner>. Not public: nothing outside this package types
       these names, and they are absent from the exports map for that reason.

       A ring with one segment brighter than the rest, turning. It says the same
       thing everywhere it appears: this has been asked for and the answer is not
       back yet.

       It exists as its own component because everything that waits wants the same
       one. The button drew its own for a day, and the second copy is where two of
       them start turning at different speeds.

       ── Everything comes off one variable ─────────────────────────────────────
       `--ds-spinner-size` is set by the component from its `size` prop, and the
       ring, the thickness and the box all derive from it. A thickness of 2px is
       right at 1em and looks like a hair at 48px, so it is an eighth of the size
       and stays right at both.

       ── The colour is currentColor ────────────────────────────────────────────
       Deliberately not a token, which is the opposite of the rule everywhere else
       in this package. A spinner has no colour of its own: inside a button it is
       the label's colour, inside a panel it is the text's, and any token would be
       wrong in one of those. The track is the same colour at a quarter strength,
       which is the one case a mix is the honest answer rather than a dodge.
       ========================================================================== */

    /* `size` means the box, so the ring is drawn inside it. Without this the
       default content-box added the two borders on top and `size={16}` came out
       20px wide, which is the kind of number that quietly stops a spinner from
       lining up with the 16px icon beside it. */
    .ds-spinner {
      box-sizing: border-box;
      display: inline-block;
      flex: none;
      inline-size: var(--ds-spinner-size);
      block-size: var(--ds-spinner-size);
      border: calc(var(--ds-spinner-size) / 8) solid
        color-mix(in srgb, currentColor 25%, transparent);
      border-block-start-color: currentColor;
      border-radius: var(--ds-radius-full);
      /* Reduced motion is not handled here. The duration comes from the motion
         tokens, which go to zero under the preference, so this becomes a still
         ring on its own. A still ring still reads as unfinished, because it is
         not the thing that was there before. */
      animation: ds-spinner-turn var(--ds-duration-loop) linear infinite;
    }

    @keyframes ds-spinner-turn {
      to {
        transform: rotate(1turn);
      }
    }

    /* ==========================================================================
       Stat

       The CSS behind <Stat>. Private, like every other component's.

       A number and what it counts.

       ── The number is the loud thing and nothing else is ─────────────────────
       No colour on it. A dashboard where every figure is green or amber is a
       dashboard where colour has stopped meaning anything, and the one number
       that IS a warning has nowhere left to say so. Colour lives on the change
       beside it, which is the only part with a direction.

       ── Tabular figures ──────────────────────────────────────────────────────
       A column of numbers whose digits are different widths does not line up, and
       a dashboard is a column of numbers.
       ========================================================================== */

    .ds-stat {
      --ds-stat-face: var(--ds-font-body-face);
      display: flex;
      flex-direction: column;
      gap: var(--ds-space-1);
      min-inline-size: 0;
      font-family: var(--ds-stat-face);
    }

    .ds-stat__label {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-stat-label-face: var(--ds-font-label-face);
      --ds-stat-label-tracking: var(--ds-font-label-tracking);
      --ds-stat-label-size: var(--ds-font-label-size);
      --ds-stat-label-weight: var(--ds-font-label-weight);
      --ds-stat-label-leading: var(--ds-font-label-leading);
      order: -1;
      font-family: var(--ds-stat-label-face);
      font-size: var(--ds-stat-label-size);
      letter-spacing: var(--ds-stat-label-tracking);
      font-weight: var(--ds-stat-label-weight);
      line-height: var(--ds-stat-label-leading);
      --ds-stat-label-ink: var(--ds-text-secondary-neutral);
      color: var(--ds-stat-label-ink);
    }

    .ds-stat__value {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-stat-value-face: var(--ds-font-heading-3-face);
      --ds-stat-value-size: var(--ds-font-heading-3-size);
      --ds-stat-value-leading: var(--ds-font-heading-3-leading);
      --ds-stat-value-tracking: var(--ds-font-heading-3-tracking);
      font-family: var(--ds-stat-value-face);
      font-size: var(--ds-stat-value-size);
      line-height: var(--ds-stat-value-leading);
      font-weight: var(--ds-font-heading-3-weight);
      letter-spacing: var(--ds-stat-value-tracking);
      --ds-stat-value-ink: var(--ds-text-primary-neutral);
      color: var(--ds-stat-value-ink);
      font-variant-numeric: tabular-nums;
    }

    .ds-stat--small .ds-stat__value {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it.

         Tracking comes with the rung. This block moved the value to heading 5 and
         left the tracking token inherited, so a 20px value wore heading 3's
         -0.02em where heading 5 declares 0: measured -0.4px on /stat before the
         line below. The weight stays the block's on purpose, which is the badge's
         lesson. */
      --ds-stat-value-face: var(--ds-font-heading-5-face);
      --ds-stat-value-size: var(--ds-font-heading-5-size);
      --ds-stat-value-leading: var(--ds-font-heading-5-leading);
      --ds-stat-value-tracking: var(--ds-font-heading-5-tracking);
      font-family: var(--ds-stat-value-face);
      font-size: var(--ds-stat-value-size);
      letter-spacing: var(--ds-stat-value-tracking);
      line-height: var(--ds-stat-value-leading);
    }

    .ds-stat--large .ds-stat__value {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it.

         Heading 2 and not 1: heading 1 is the rung the page's own title stands
         on, and a number wearing it competes with the page's name. One rung above
         medium keeps the number the loudest thing in its section and leaves the
         hero to the hero. Tracking comes with the rung, because a display size
         wearing a smaller rung's tracking reads as badly spaced type. */
      --ds-stat-value-face: var(--ds-font-heading-2-face);
      --ds-stat-value-size: var(--ds-font-heading-2-size);
      --ds-stat-value-leading: var(--ds-font-heading-2-leading);
      --ds-stat-value-tracking: var(--ds-font-heading-2-tracking);
      font-family: var(--ds-stat-value-face);
      font-size: var(--ds-stat-value-size);
      letter-spacing: var(--ds-stat-value-tracking);
      line-height: var(--ds-stat-value-leading);
    }

    .ds-stat__note {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-stat-note-face: var(--ds-font-small-face);
      --ds-stat-note-tracking: var(--ds-font-small-tracking);
      --ds-stat-note-size: var(--ds-font-small-size);
      --ds-stat-note-weight: var(--ds-font-small-weight);
      --ds-stat-note-leading: var(--ds-font-small-leading);
      font-family: var(--ds-stat-note-face);
      font-size: var(--ds-stat-note-size);
      letter-spacing: var(--ds-stat-note-tracking);
      font-weight: var(--ds-stat-note-weight);
      line-height: var(--ds-stat-note-leading);
      --ds-stat-note-ink: var(--ds-text-tertiary-neutral);
      color: var(--ds-stat-note-ink);
    }

    /* The change is the one part with a direction, so it is the one part with a
       colour. The arrow is a character rather than a mark from the set: it sits
       inside a line of text and has to grow with it. */
    .ds-stat__change {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-stat-change-face: var(--ds-font-small-face);
      --ds-stat-change-tracking: var(--ds-font-small-tracking);
      --ds-stat-change-size: var(--ds-font-small-size);
      --ds-stat-change-weight: var(--ds-font-small-weight);
      --ds-stat-change-leading: var(--ds-font-small-leading);
      display: inline-flex;
      align-items: baseline;
      gap: var(--ds-space-1);
      font-family: var(--ds-stat-change-face);
      font-size: var(--ds-stat-change-size);
      letter-spacing: var(--ds-stat-change-tracking);
      font-weight: var(--ds-stat-change-weight);
      line-height: var(--ds-stat-change-leading);
      font-variant-numeric: tabular-nums;
      --ds-stat-change-ink: var(--ds-text-secondary-neutral);
      color: var(--ds-stat-change-ink);
    }

    .ds-stat__change--up { color: var(--ds-success-ink); }
    .ds-stat__change--down { color: var(--ds-danger-ink); }

    /* ==========================================================================
       Feedback

       The row at the foot of a page that asks whether it helped, and the comment
       box that follows a No.

       ── The stage is the page's, not the component's ─────────────────────────
       An answer has to be posted somewhere, so the page already knows what was
       said and what to show next. A widget holding that on its own says "thanks"
       to a reader whose answer nobody received. Same ruling as the list of
       headings beside it: the renderer with no state cannot hold it, and a
       component that held it in React and not in PHP would be two components
       wearing one name.

       ── Two answers of equal standing ────────────────────────────────────────
       The consent bar's ruling, and the same reason: a Yes drawn louder than a
       No is a question asked with the answer already in it. Both are the button's
       outline fill and neither carries a tone.
       ========================================================================== */

    .ds-feedback {
      display: flex;
      flex-wrap: wrap;
      align-items: center;
      gap: var(--ds-space-3) var(--ds-space-4);
    }

    .ds-feedback__question {
      --ds-feedback-question-face: var(--ds-font-label-face);
      --ds-feedback-question-size: var(--ds-font-label-size);
      --ds-feedback-question-weight: var(--ds-font-label-weight);
      --ds-feedback-question-leading: var(--ds-font-label-leading);
      --ds-feedback-question-tracking: var(--ds-font-label-tracking);
      display: block;
      margin: 0;
      font-family: var(--ds-feedback-question-face);
      font-size: var(--ds-feedback-question-size);
      font-weight: var(--ds-feedback-question-weight);
      line-height: var(--ds-feedback-question-leading);
      letter-spacing: var(--ds-feedback-question-tracking);
    }

    .ds-feedback__answers {
      display: flex;
      align-items: center;
      gap: var(--ds-space-2);
    }

    /* The comment takes the row to itself: a box the width of a sentence beside a
       question is a box nobody writes two lines in. */
    .ds-feedback__comment {
      display: flex;
      flex-direction: column;
      gap: var(--ds-space-3);
      flex-basis: 100%;
      min-inline-size: 0;
    }

    .ds-feedback__prompt {
      --ds-feedback-prompt-face: var(--ds-font-label-face);
      --ds-feedback-prompt-size: var(--ds-font-label-size);
      --ds-feedback-prompt-weight: var(--ds-font-label-weight);
      --ds-feedback-prompt-leading: var(--ds-font-label-leading);
      --ds-feedback-prompt-tracking: var(--ds-font-label-tracking);
      --ds-feedback-prompt-ink: var(--ds-text-secondary-neutral);
      display: block;
      margin: 0;
      font-family: var(--ds-feedback-prompt-face);
      font-size: var(--ds-feedback-prompt-size);
      font-weight: var(--ds-feedback-prompt-weight);
      line-height: var(--ds-feedback-prompt-leading);
      letter-spacing: var(--ds-feedback-prompt-tracking);
      color: var(--ds-feedback-prompt-ink);
    }

    .ds-feedback__send {
      display: flex;
      justify-content: flex-end;
    }

    /* What is left when the exchange is over. It replaces the question rather
       than standing under it: a reader who has answered is not being asked
       again. */
    .ds-feedback__thanks {
      --ds-feedback-thanks-face: var(--ds-font-label-face);
      --ds-feedback-thanks-size: var(--ds-font-label-size);
      --ds-feedback-thanks-weight: var(--ds-font-label-weight);
      --ds-feedback-thanks-leading: var(--ds-font-label-leading);
      --ds-feedback-thanks-tracking: var(--ds-font-label-tracking);
      --ds-feedback-thanks-ink: var(--ds-text-secondary-neutral);
      display: flex;
      align-items: center;
      gap: var(--ds-space-2);
      margin: 0;
      font-family: var(--ds-feedback-thanks-face);
      font-size: var(--ds-feedback-thanks-size);
      font-weight: var(--ds-feedback-thanks-weight);
      line-height: var(--ds-feedback-thanks-leading);
      letter-spacing: var(--ds-feedback-thanks-tracking);
      color: var(--ds-feedback-thanks-ink);
    }

    .ds-feedback__tick {
      display: flex;
      flex: none;
      color: var(--ds-success);
    }

    /* ==========================================================================
       The form-control skin

       Worn by <InputField> and <Textarea>, which are one label, one control and
       one message each, and differ only in what you type into. One file, because
       two copies of a focus ring is two focus rings that drift.

       Not public: nothing outside this package types these names, and they are
       absent from the exports map for that reason.

       ── Six sizes off the control ramp ────────────────────────────────────────
       tokens/size.css, which every control in the system reads. One ladder is
       what makes two controls on one row the same height by construction rather
       than by two files agreeing on a number, and one list of names is what stops
       anybody having to remember which components stop where.

       ── The border does the work ──────────────────────────────────────────────
       Resting, it is `--ds-border`, the line round a thing at rest, and not
       a control and holds 3:1 against every surface. Focused, it is the focus
       ring plus a heavier line, so the state reads for someone who cannot see the
       colour change. Invalid, it is danger, and the message underneath says the
       same thing in words.
       ========================================================================== */

    .ds-field {
      /* This component's own. The role says what the words are; this says that
         THIS component wants a heavier hand than the role has, which is a
         decision the component makes and nobody else. Pointed at the semantic
         rather than holding a number, so moving the system moves every component
         that has not been moved on purpose, and moving one moves one. */
      /* The one property this component sets off the space ramp on purpose, named
         so a tool reads the decision instead of reporting it as a mistake: the line
         box comes from the size, which is what the type map pairs.

         The room inside the frame and the space beside a mark used to be in `em`
         too, on the grounds that they track the label. What they actually tracked
         was the font size, so a field at 14 had a 7px gap and 10.5px of inset:
         three tiers, six numbers, and not one of them on the step everything else
         in the system is measured in. They are on the ramp now. */
      --ds-field-intent: line-height;
      /* The step between the label, the control and the message: this
         component's own, and a rung of the space ramp rather than the line
         rhythm it read before. At 4px the three parts read as one block and every
         form came out "schiacciato" (owner, 11 September 2026); at 8 the label is
         a label and the message a message. */
      --ds-field-step: var(--ds-space-2);
      display: flex;
      flex-direction: column;
      gap: var(--ds-field-step);
      min-inline-size: 0;
    }

    .ds-field__label {
      --ds-field-label-weight: var(--ds-emphasis-weight);
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-field-label-face: var(--ds-font-label-face);
      --ds-field-label-tracking: var(--ds-font-label-tracking);
      --ds-field-label-size: var(--ds-font-label-size);
      --ds-field-label-leading: var(--ds-font-label-leading);
      font-family: var(--ds-field-label-face);
      font-size: var(--ds-field-label-size);
      letter-spacing: var(--ds-field-label-tracking);
      line-height: var(--ds-field-label-leading);
      font-weight: var(--ds-field-label-weight);
      line-height: var(--ds-field-label-leading);
      --ds-field-label-ink: var(--ds-text-primary-neutral);
      color: var(--ds-field-label-ink);
    }

    /* Off the screen and still in the accessible tree, which is the one way of
       hiding something that a screen reader still reads. Not `display: none`, not
       `visibility: hidden`, not zero opacity: the first two take it out of the
       tree with the picture and the third leaves a box that pushes the control
       down by the height of the label it is not showing.
       The clip and the single pixel are what keep it out of the layout, and the
       absolute position is what stops it from being one. */
    .ds-field__label--hidden {
      position: absolute;
      inline-size: 1px;
      block-size: 1px;
      margin: -1px;
      padding: 0;
      overflow: hidden;
      clip-path: inset(50%);
      white-space: nowrap;
    }

    /* The asterisk is not the label of anything, so it is hidden and the word is
       said in the accessible name instead. A screen reader reading "Email star"
       is a reader guessing. */
    .ds-field__required {
      color: var(--ds-danger-ink);
    }

    /* The frame, so the icon slots sit inside the outline rather than beside it.
       The input itself carries no border: two borders in one control is how a
       focus ring ends up drawn around a box that is not the one being typed in. */
    .ds-field__frame {
      box-sizing: border-box;
      display: flex;
      align-items: center;
      gap: var(--ds-field-gap);
      min-inline-size: 0;
      padding-inline: var(--ds-field-inset);
      /* Its own leading, and this is not a detail. The page sets a line-height in
         rem, which inherits as an absolute 24px into everything under it, so a
         12px input sat in a 24px line box and the smallest field came out 26px
         tall against a tier of 24. It was a ratio for a while, which fixed the
         height and put the line box off the 4px step; the tiers below set the
         leading paired with the size each of them uses, which fixes both. */
      background-color: var(--ds-surface-1);
      /* A field is a container, not an action: the line says where the box is and
         nothing more. What declares a field is its ground, its placeholder and
         its label, and an edge as loud as a button's put a row of boxes on every
         form. The strong step stays for the checkbox, which has nothing inside it
         to say what it is. */
      border: 1px solid var(--ds-border);
      border-radius: var(--ds-radius-2);
      --ds-field-frame-ink: var(--ds-text-primary-neutral);
      color: var(--ds-field-frame-ink);
      transition:
        border-color var(--ds-duration) var(--ds-ease);
    }

    /* Il FIGLIO e non il discendente, in tutte le righe qui sotto.
       Un campo dentro un campo esiste da quando l'editor porta la finestra di una
       fotografia dentro il proprio: li' `.ds-field--medium .ds-field__frame`
       pescava anche la cornice del campo piccolo annidato, e `--multiline` — che
       l'editor porta per se' — le toglieva l'altezza. Risultato: una casella alta
       novanta pixel dentro una finestra che chiedeva il tier piccolo. Il tier di
       un campo e' del campo, non di chi lo contiene. */
    .ds-field--xsmall > .ds-field__frame {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-field-frame-face: var(--ds-font-small-face);
      --ds-field-frame-tracking: var(--ds-font-small-tracking);
      --ds-field-frame-size: var(--ds-font-small-size);
      --ds-field-frame-weight: var(--ds-font-small-weight);
      --ds-field-frame-leading: var(--ds-font-small-leading);
      --ds-field-gap: var(--ds-space-1);
      --ds-field-inset: var(--ds-space-2);
      min-block-size: var(--ds-size-1);
      font-family: var(--ds-field-frame-face);
      font-size: var(--ds-field-frame-size);
      letter-spacing: var(--ds-field-frame-tracking);
      font-weight: var(--ds-field-frame-weight);
      line-height: var(--ds-field-frame-leading);
    }

    .ds-field--small > .ds-field__frame {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-field-frame-face: var(--ds-font-label-face);
      --ds-field-frame-tracking: var(--ds-font-label-tracking);
      --ds-field-frame-size: var(--ds-font-label-size);
      --ds-field-frame-weight: var(--ds-font-label-weight);
      --ds-field-frame-leading: var(--ds-font-label-leading);
      --ds-field-gap: var(--ds-space-1);
      --ds-field-inset: var(--ds-space-3);
      min-block-size: var(--ds-size-2);
      font-family: var(--ds-field-frame-face);
      font-size: var(--ds-field-frame-size);
      letter-spacing: var(--ds-field-frame-tracking);
      font-weight: var(--ds-field-frame-weight);
      line-height: var(--ds-field-frame-leading);
    }

    .ds-field--medium > .ds-field__frame {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-field-frame-face: var(--ds-font-body-face);
      --ds-field-frame-tracking: var(--ds-font-body-tracking);
      --ds-field-frame-size: var(--ds-font-body-size);
      --ds-field-frame-weight: var(--ds-font-body-weight);
      --ds-field-frame-leading: var(--ds-font-body-leading);
      --ds-field-gap: var(--ds-space-2);
      --ds-field-inset: var(--ds-space-3);
      min-block-size: var(--ds-size-3);
      font-family: var(--ds-field-frame-face);
      font-size: var(--ds-field-frame-size);
      letter-spacing: var(--ds-field-frame-tracking);
      font-weight: var(--ds-field-frame-weight);
      line-height: var(--ds-field-frame-leading);
    }

    .ds-field--large > .ds-field__frame {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-field-frame-face: var(--ds-font-body-face);
      --ds-field-frame-tracking: var(--ds-font-body-tracking);
      --ds-field-frame-size: var(--ds-font-body-size);
      --ds-field-frame-weight: var(--ds-font-body-weight);
      --ds-field-frame-leading: var(--ds-font-body-leading);
      --ds-field-gap: var(--ds-space-2);
      --ds-field-inset: var(--ds-space-4);
      min-block-size: var(--ds-size-4);
      font-family: var(--ds-field-frame-face);
      font-size: var(--ds-field-frame-size);
      letter-spacing: var(--ds-field-frame-tracking);
      font-weight: var(--ds-field-frame-weight);
      line-height: var(--ds-field-frame-leading);
    }

    .ds-field--xlarge > .ds-field__frame {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-field-frame-face: var(--ds-font-body-face);
      --ds-field-frame-tracking: var(--ds-font-body-tracking);
      --ds-field-frame-size: var(--ds-font-body-size);
      --ds-field-frame-weight: var(--ds-font-body-weight);
      --ds-field-frame-leading: var(--ds-font-body-leading);
      --ds-field-gap: var(--ds-space-2);
      --ds-field-inset: var(--ds-space-4);
      min-block-size: var(--ds-size-5);
      font-family: var(--ds-field-frame-face);
      font-size: var(--ds-field-frame-size);
      letter-spacing: var(--ds-field-frame-tracking);
      font-weight: var(--ds-field-frame-weight);
      line-height: var(--ds-field-frame-leading);
    }

    /* The sixth tier. It was missing, which left the control ramp with six steps
       and the fields naming five of them: a height that exists, that a button of
       the same name already stands at, and that anybody needing it would have had
       to write by hand. A 64px field is rare and is a real thing, the search box
       that is the whole of a landing page.

       It read at the level above body for a revision, which is fluid on the web
       surface: 18px growing to 20 as the window widens, inside a frame fixed at
       64. The ramp stops at body, here and everywhere else on it. */
    .ds-field--xxlarge > .ds-field__frame {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-field-frame-face: var(--ds-font-body-face);
      --ds-field-frame-tracking: var(--ds-font-body-tracking);
      --ds-field-frame-size: var(--ds-font-body-size);
      --ds-field-frame-weight: var(--ds-font-body-weight);
      --ds-field-frame-leading: var(--ds-font-body-leading);
      --ds-field-gap: var(--ds-space-2);
      --ds-field-inset: var(--ds-space-5);
      min-block-size: var(--ds-size-6);
      font-family: var(--ds-field-frame-face);
      font-size: var(--ds-field-frame-size);
      letter-spacing: var(--ds-field-frame-tracking);
      font-weight: var(--ds-field-frame-weight);
      line-height: var(--ds-field-frame-leading);
    }

    /* A textarea anchors to the top edge, since it grows downwards and a box
       centred against four lines reads as a mistake. Its height comes from its
       rows, so it sets a minimum rather than taking one from the ramp. */
    .ds-field--multiline > .ds-field__frame {
      --ds-field-frame-leading: var(--ds-font-body-leading);
      align-items: flex-start;
      /* The tier already named the room this box keeps at its sides, and a box
         that grows downwards keeps the same room above and below its text. It
         was 0.55em, a ratio nobody decided: 8.8px on a 16px word, so a textarea
         measured 259.59 tall and no height in it landed on the grid (owner,
         7 September 2026). Read from the tier, every side is a rung and every
         height is four lots of the leading plus two of these. */
      padding-block: var(--ds-field-inset);
      min-block-size: 0;
      /* A textarea gets the reading leading back. 1.2 is right for one line in a
         box and wrong for four: this is running text, and its height is counted
         in `lh`, so the leading is also what `rows` measures.
         Its ROLE's leading, and it was `1.5`: 24 on 16, which is a ratio nobody
         decided and which stopped agreeing with the rest of the package when the
         leadings were tightened. A field is body size, so it is body's line. */
      line-height: var(--ds-field-frame-leading);
    }

    .ds-field__control {
      flex: 1;
      /* The full height of the frame, not the height of its own line. A frame
         centres what it holds, so a 17px line in a 32px box left eight pixels of
         dead ground along the top and eight along the bottom: half the box looked
         like the control and was not. */
      align-self: stretch;
      min-inline-size: 0;
      padding: 0;
      border: 0;
      background: none;
      font: inherit;
      color: inherit;
      /* The user agent gives form controls a font of their own and a margin in
         Safari. Both are cleared here rather than in a reset, because this is the
         only place the package renders one. */
      margin: 0;
    }

    .ds-field__control:focus {
      outline: none;
    }

    .ds-field__control::placeholder {
      --ds-field-control-ink: var(--ds-text-tertiary-neutral);
      color: var(--ds-field-control-ink);
    }

    textarea.ds-field__control {
      resize: vertical;
      min-block-size: 4lh;
    }

    /* The icon slots. Sized by the component, so a field handed a 24px icon does
       not grow to fit it. Tertiary, because an icon in a field is a hint about
       what goes in it and never the thing being read. */
    .ds-field__icon {
      display: inline-flex;
      flex: none;
      inline-size: 1lh;
      block-size: 1lh;

      --ds-field-icon-ink: var(--ds-text-tertiary-neutral);
      color: var(--ds-field-icon-ink);
    }

    .ds-field__icon > svg {
      inline-size: 100%;
      block-size: 100%;
    }

    /* ── States ───────────────────────────────────────────────────────────────
       On the frame and not on the input, so the outline moves as one line.

       `:focus-within` and not `:focus-visible`: clicking into a field is how you
       type in it, so the ring belongs there for a pointer too. That is the
       opposite of a button, where a click leaves a ring behind for no reason. */
    /* A wash, the same gesture the button makes, and not the active border. That
       token is the outline of the control that is chosen or focused, and hovering
       is neither: spending it here said "you are in this field" to a pointer that
       was only passing, and left nothing for the click to say.

       Painted over the frame's own surface rather than replacing it, because the
       wash is a translucent overlay and a background-color would drop the surface
       underneath it. */
    .ds-field__frame:hover:not(:has(:disabled)) {
      background-image: linear-gradient(var(--ds-surface-hover), var(--ds-surface-hover));
    }

    /* And the frame whose panel is open, because opening it is what takes the
       focus away: the popover machine moves it onto the chosen line, so
       `:focus-within` died in the same click that opened the thing and the ring
       read as a flicker. The trigger's own `aria-expanded` is the fact both
       renderers already keep current, so it is what the skin reads. */
    /* One line, the frame's own, in the focus colour. It was the line plus a
       1px shadow outside it, which read as a 2px edge beside a button whose
       held state is its 1px edge: two controls in one toolbar saying "active"
       at two weights (owner, 5 September 2026). The colour says focus; the
       weight is the frame's. */
    .ds-field__frame:focus-within,
    .ds-field__frame:has([aria-expanded="true"]) {
      border-color: var(--ds-focus);
    }

    /* No edge and no ground: the field stands where a value stood, and the only
       thing that says it is a field is the wash a hover already paints. Focus
       takes the same wash and not the ring, since a caret in a line of text is
       what focus looks like there; the ring belongs to a box in a form. */
    .ds-field--bare > .ds-field__frame {
      border-color: transparent;
      background-color: transparent;
    }

    .ds-field--bare > .ds-field__frame:focus-within,
    .ds-field--bare > .ds-field__frame:has([aria-expanded="true"]) {
      border-color: transparent;
      background-image: linear-gradient(var(--ds-surface-hover), var(--ds-surface-hover));
    }

    .ds-field--invalid > .ds-field__frame {
      border-color: var(--ds-danger);
    }

    .ds-field--invalid > .ds-field__frame:focus-within,
    .ds-field--invalid > .ds-field__frame:has([aria-expanded="true"]) {
      border-color: var(--ds-danger);
    }

    .ds-field__frame:has(:disabled) {
      background-color: var(--ds-surface-disabled);
      border-color: var(--ds-border-disabled);
      --ds-field-frame-ink: var(--ds-text-disabled);
      color: var(--ds-field-frame-ink);
      cursor: not-allowed;
    }

    /* The control too, and not only the frame around it. A disabled input keeps
       its own cursor, so the frame said "not allowed" over the padding and the
       middle of the field said nothing: the reader learned the answer by moving
       the mouse two millimetres. Measured: `default` on the control against
       `not-allowed` on the frame. */
    .ds-field__control:disabled {
      cursor: not-allowed;
      --ds-field-control-ink: var(--ds-text-disabled);
      color: var(--ds-field-control-ink);
      -webkit-text-fill-color: var(--ds-text-disabled);
      opacity: 1;
    }

    .ds-field__control:disabled::placeholder {
      --ds-field-control-ink: var(--ds-text-disabled);
      color: var(--ds-field-control-ink);
    }

    /* ── The two lines underneath ─────────────────────────────────────────────
       One at a time. An error replaces the hint rather than joining it: two lines
       of guidance under one control is a reader deciding which one is current,
       and the answer is always the error. */
    .ds-field__message {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-field-message-face: var(--ds-font-label-face);
      --ds-field-message-tracking: var(--ds-font-label-tracking);
      --ds-field-message-size: var(--ds-font-label-size);
      --ds-field-message-weight: var(--ds-font-label-weight);
      --ds-field-message-leading: var(--ds-font-label-leading);
      font-family: var(--ds-field-message-face);
      font-size: var(--ds-field-message-size);
      letter-spacing: var(--ds-field-message-tracking);
      font-weight: var(--ds-field-message-weight);
      line-height: var(--ds-field-message-leading);
      line-height: var(--ds-field-message-leading);
      --ds-field-message-ink: var(--ds-text-secondary-neutral);
      color: var(--ds-field-message-ink);
    }

    .ds-field__message--error {
      color: var(--ds-danger-ink);
    }

    /* ==========================================================================
       Choice

       The skin worn by every control you turn on: <Checkbox>, <Radio>, <Switch>.
       Not public, like the field's, and for the same reason: nothing outside this
       package types these names.

       Written after counting. Of sixteen rules in the checkbox, seven were
       byte-identical to the radio's once the block name was swapped, and the two
       that were not differed only because the radio had been given the control
       ramp and the checkbox had not. A third control was about to make it three
       copies and the drift three-cornered.

       ── What belongs here and what does not ───────────────────────────────────
       Here: the row, the tier, the hidden input, the words beside it, the tones,
       the disabled state, the focus ring, and the rule that turns the mark on.
       Not here: the shape of the mark. A square, a circle and a lever are the
       three things these controls do not share, so each keeps its own file for
       exactly that and wears `ds-choice__mark` on the same element, the way a
       search control wears the field's.

       ── The tier is the target ────────────────────────────────────────────────
       The row is held to a tier of the control ramp, so the area a pointer has to
       land in is never smaller than 24px, which is what WCAG 2.5.8 asks. It was
       measured wrong before this existed: a checkbox with a label and no
       description stood 19.59px tall, a band thinner than the square being aimed
       for. Where there is no text the row is only as wide as the mark, so a
       pseudo-element carries the tier outward: drawn small, pressed large.
       ========================================================================== */

    /* Six tiers, and the mark and the words climb with them. Explicit per tier
       rather than derived, because 18px beside 14px text is a drawing decision
       and a ratio would only be an average of six of them. */
    .ds-choice--xsmall  {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-choice-face: var(--ds-font-small-face);
      --ds-choice-tracking: var(--ds-font-small-tracking);
      --ds-choice-size: var(--ds-font-small-size);
      --ds-choice-weight: var(--ds-font-small-weight);
      --ds-choice-leading: var(--ds-font-small-leading);
     --ds-choice-tier: var(--ds-size-1); --ds-choice-mark: 0.875rem; font-size: var(--ds-choice-size); line-height: var(--ds-choice-leading); }
    .ds-choice--small   {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-choice-face: var(--ds-font-label-face);
      --ds-choice-tracking: var(--ds-font-label-tracking);
      --ds-choice-size: var(--ds-font-label-size);
      --ds-choice-weight: var(--ds-font-label-weight);
      --ds-choice-leading: var(--ds-font-label-leading);
     --ds-choice-tier: var(--ds-size-2); --ds-choice-mark: 1rem;     font-size: var(--ds-choice-size); line-height: var(--ds-choice-leading); }
    .ds-choice--medium  {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-choice-face: var(--ds-font-body-face);
      --ds-choice-tracking: var(--ds-font-body-tracking);
      --ds-choice-size: var(--ds-font-body-size);
      --ds-choice-leading: var(--ds-font-body-leading);
     --ds-choice-tier: var(--ds-size-3); --ds-choice-mark: 1.125rem; font-size: var(--ds-choice-size); line-height: var(--ds-choice-leading); }
    .ds-choice--large   {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-choice-face: var(--ds-font-body-face);
      --ds-choice-tracking: var(--ds-font-body-tracking);
      --ds-choice-size: var(--ds-font-body-size);
      --ds-choice-weight: var(--ds-font-body-weight);
      --ds-choice-leading: var(--ds-font-body-leading);
     --ds-choice-tier: var(--ds-size-4); --ds-choice-mark: 1.25rem;  font-size: var(--ds-choice-size); line-height: var(--ds-choice-leading); }
    .ds-choice--xlarge  {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-choice-face: var(--ds-font-body-face);
      --ds-choice-tracking: var(--ds-font-body-tracking);
      --ds-choice-size: var(--ds-font-body-size);
      --ds-choice-weight: var(--ds-font-body-weight);
      --ds-choice-leading: var(--ds-font-body-leading);
     --ds-choice-tier: var(--ds-size-5); --ds-choice-mark: 1.375rem; font-size: var(--ds-choice-size); line-height: var(--ds-choice-leading); }
    .ds-choice--xxlarge {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-choice-face: var(--ds-font-body-face);
      --ds-choice-tracking: var(--ds-font-body-tracking);
      --ds-choice-size: var(--ds-font-body-size);
      --ds-choice-weight: var(--ds-font-body-weight);
      --ds-choice-leading: var(--ds-font-body-leading);
     --ds-choice-tier: var(--ds-size-6); --ds-choice-mark: 1.5rem;   font-size: var(--ds-choice-size); line-height: var(--ds-choice-leading); }

    .ds-choice {
      /* The properties this component sets off the space ramp on purpose, named so
         a tool reads the decision instead of reporting it as a mistake. These are
         in `em`: they track the label rather than the box, which is the whole
         reason a row of one word and a row of five come out proportioned the same. */
      --ds-choice-intent: gap;
      display: inline-grid;
      grid-template-columns: auto 1fr;
      gap: 0.6em;
      align-items: center;
      min-block-size: var(--ds-choice-tier);
      cursor: pointer;
    }

    /* One line sits in the middle of its tier; two lines start at the top. The
       mark answers the label, and centred against a block of two it lands between
       them, pointing at neither. */
    .ds-choice:has(.ds-choice__description) {
      align-items: start;
    }

    /* A box with nothing written beside it is a box and not a row, so it measures
       the mark. Both renderers leave the text out when there is neither a label
       nor a description, but the gap is between the two COLUMNS of the grid and
       an empty track still has one on either side of it: measured 12 September
       2026, a select box on the table page came out 24.4px wide around a 16px
       square, `16px 0px` of template with 8.4px of nothing after the mark. It
       costs no target, because the tier is the mark's own ::after and not this
       column. What it cost was every control set beside such a box: the grip on
       a board card stood 16px from a square it belongs to. */
    .ds-choice:not(:has(.ds-choice__text)) {
      gap: 0;
    }

    /* Hidden the way a control has to be hidden: still focusable, still in the
       accessibility tree, still submitted with the form. `display: none` would
       take all three away, and on a radio it would also take the arrow keys. */
    .ds-choice__input {
      position: absolute;
      inline-size: 1px;
      block-size: 1px;
      margin: -1px;
      padding: 0;
      overflow: hidden;
      clip-path: inset(50%);
      white-space: nowrap;
    }

    /* The slot the shape sits in. Everything here is true of a square, a circle
       and a lever alike; the shape itself is drawn by the component that owns it,
       on this same element. */
    .ds-choice__mark {
      position: relative;
      box-sizing: border-box;
      display: grid;
      place-items: center;
      flex: none;
      inline-size: var(--ds-choice-mark);
      block-size: var(--ds-choice-mark);
      /* The strong step. A shape of eighteen pixels with nothing written in it is
         identified by its outline alone, which is the case the three to one rule
         is written for; a field and a button carry a label. */
      border: 1px solid var(--ds-border-strong);
      background-color: var(--ds-surface-1);
      color: transparent;
      transition:
        background-color var(--ds-duration) var(--ds-ease),
        border-color var(--ds-duration) var(--ds-ease);
    }

    .ds-choice:has(.ds-choice__description) .ds-choice__mark {
      /* Nudged down so the mark sits on the text's cap height rather than on its
         line box, which is what makes it look a pixel high next to a label. */
      margin-block-start: 0.1em;
    }

    /* The tier's worth of target around the mark, whatever the mark measures.
       Sized and centred rather than pushed out with negative insets: an absolute
       box is positioned against its ancestor's PADDING box, so `inset: -3px` on a
       bordered shape lands short of the number you asked for. */
    .ds-choice__mark::after {
      content: "";
      position: absolute;
      inset-block-start: 50%;
      inset-inline-start: 50%;
      inline-size: max(var(--ds-choice-tier), 100%);
      block-size: max(var(--ds-choice-tier), 100%);
      transform: translate(-50%, -50%);
    }

    /* ── States, all off the input ─────────────────────────────────────────── */
    .ds-choice__input:checked + .ds-choice__mark,
    .ds-choice__input:indeterminate + .ds-choice__mark {
      background-color: var(--ds-choice-fill);
      border-color: var(--ds-choice-fill);
      color: var(--ds-choice-ink);
    }

    .ds-choice--inverse {
      --ds-choice-fill: var(--ds-inverse);
      --ds-choice-ink: var(--ds-on-inverse);
    }

    .ds-choice--brand {
      --ds-choice-fill: var(--ds-brand);
      --ds-choice-ink: var(--ds-on-brand);
    }

    .ds-choice:hover .ds-choice__input:not(:disabled) + .ds-choice__mark {
      border-color: var(--ds-border-active);
    }

    .ds-choice__input:focus-visible + .ds-choice__mark {
      outline: 2px solid var(--ds-focus);
      outline-offset: 2px;
    }

    .ds-choice__input:disabled + .ds-choice__mark {
      background-color: var(--ds-surface-disabled);
      border-color: var(--ds-border-disabled);
      --ds-choice-mark-ink: var(--ds-text-disabled);
      color: var(--ds-choice-mark-ink);
    }

    .ds-choice:has(:disabled) {
      cursor: not-allowed;
      --ds-choice-ink: var(--ds-text-disabled);
      color: var(--ds-choice-ink);
    }

    /* ── The words beside it ─────────────────────────────────────────────────
       Two lines at most: what the option is, and what choosing it means. No gap
       between them and a tighter leading on both, because a 14px label sits in a
       20px line box and a 12px description in a 16px one: the step that reads as
       4px on paper arrives as nine on screen, and they have to read as one item. */
    .ds-choice__text {
      display: flex;
      flex-direction: column;
      gap: 0;
      min-inline-size: 0;
    }

    /* Size and leading both come off the tier, which sets them together. Fixed
       here they were a ratio, and a ratio is how every one of these files ended up
       with a line box the 4px step cannot produce: 1.35 on 12 is 16.2. */
    .ds-choice__label {
      --ds-choice-label-face: var(--ds-font-body-face);
      font-family: var(--ds-choice-label-face);
      color: inherit;
    }

    .ds-choice__description {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      /* Lo stesso rung dell'aiuto sotto un campo, e per la stessa ragione: e'
         la stessa cosa. Una frase che spiega il controllo sopra di lei leggeva
         12px sotto una casella e 14 sotto ogni altro campo, e nella stessa
         colonna di form le due si vedevano diverse senza che niente lo
         giustificasse (proprietario, 25 agosto 2026). */
      --ds-choice-description-face: var(--ds-font-label-face);
      --ds-choice-description-tracking: var(--ds-font-label-tracking);
      --ds-choice-description-size: var(--ds-font-label-size);
      --ds-choice-description-weight: var(--ds-font-label-weight);
      --ds-choice-description-leading: var(--ds-font-label-leading);
      font-family: var(--ds-choice-description-face);
      font-size: var(--ds-choice-description-size);
      letter-spacing: var(--ds-choice-description-tracking);
      font-weight: var(--ds-choice-description-weight);
      line-height: var(--ds-choice-description-leading);
      --ds-choice-description-ink: var(--ds-text-secondary-neutral);
      color: var(--ds-choice-description-ink);
    }

    .ds-choice:has(:disabled) .ds-choice__description {
      --ds-choice-description-ink: var(--ds-text-disabled);
      color: var(--ds-choice-description-ink);
    }

    /* ==========================================================================
       Checkbox

       The square, and nothing else. Everything around it, the row and its tier,
       the hidden input, the words, the tones, the states and the target, is the
       choice skin, worn on the same elements: `ds-choice__mark` carries the
       slot and `ds-checkbox__box` draws what goes in it.

       The file held sixteen rules before the skin existed. Seven were identical
       to the radio's once the block name was swapped, and two more differed only
       because the radio had the control ramp and this did not.
       ========================================================================== */

    .ds-checkbox__box {
      border-radius: var(--ds-radius-1);
    }

    /* The tick and the dash. Always rendered, revealed by colour, so the square
       never changes size between states and a column of them never shifts. Sized
       off the mark rather than fixed, so it climbs the ramp with it. */
    .ds-checkbox__box > svg {
      inline-size: calc(var(--ds-choice-mark) * 0.78);
      block-size: calc(var(--ds-choice-mark) * 0.78);
    }

    /* ── Done ─────────────────────────────────────────────────────────────────
       The mark that says a thing is done, told apart from the box that selects
       it: an empty circle at rest, a solid success fill with the tick in white
       once it is. The circle is the radio's shape and the fill the success
       badge's. The first drawing kept a grey tick inside the empty circle, so
       an open task read as half done; the owner saw it in a list of thirty and
       would not have it (10 September 2026). The tick shows only under the hand,
       faint, as the promise of what the press does. */
    .ds-checkbox--done .ds-checkbox__box {
      border-radius: var(--ds-radius-full);
      /* The hint under the hand dissolves in and out the way the fill does. */
      transition:
        background-color var(--ds-duration) var(--ds-ease),
        border-color var(--ds-duration) var(--ds-ease),
        color var(--ds-duration) var(--ds-ease);
    }

    .ds-checkbox--done:hover .ds-choice__input:not(:checked):not(:disabled) + .ds-checkbox__box {
      --ds-checkbox-done-hint: var(--ds-text-disabled);
      color: var(--ds-checkbox-done-hint);
    }

    .ds-checkbox--done .ds-choice__input:checked + .ds-checkbox__box {
      --ds-checkbox-done-fill: var(--ds-success);
      --ds-checkbox-done-on: var(--ds-on-success);
      background-color: var(--ds-checkbox-done-fill);
      border-color: var(--ds-checkbox-done-fill);
      color: var(--ds-checkbox-done-on);
    }

    /* ==========================================================================
       State

       The CSS behind <State>. Not public: nothing outside this package types
       these names, and they are absent from the exports map for that reason.

       The block that stands in for content that is not there: nothing yet,
       nothing left after a filter, still loading, or broken. One component for
       all four, because they are the same object with a different face, and four
       components would be four sets of margins that never quite match.

       ── Three amounts of room ─────────────────────────────────────────────────
       `page` fills the space a page body has and centres in it. `panel` is the
       middle of a card. `inline` is a row inside a table, where the block has to
       sit in the space a few rows would have taken and no more.

       The difference is padding and the size of the mark, and nothing else: the
       same title, the same description, the same action, so a table that grows
       into a page does not change what it says.
       ========================================================================== */

    .ds-state {
      /* This component's own. The role says what the words are; this says that
         THIS component wants a heavier hand than the role has, which is a
         decision the component makes and nobody else. Pointed at the semantic
         rather than holding a number, so moving the system moves every component
         that has not been moved on purpose, and moving one moves one. */
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      text-align: center;
      gap: var(--ds-rhythm-element);
      /* Centred on both axes, so the block sits in the middle of whatever it is
         given rather than at the top of it. */
      inline-size: 100%;
    }

    .ds-state--page {
      padding-block: var(--ds-space-24);
      min-block-size: 60vh;
    }

    .ds-state--panel {
      padding-block: var(--ds-space-16);
    }

    .ds-state--inline {
      padding-block: var(--ds-space-10);
    }

    /* The mark. Tertiary by default because it is scenery: the title is the
       sentence, and an icon that competes with it is an icon that gets read
       first and understood second. */
    .ds-state__mark {
      display: inline-flex;
      --ds-state-mark-ink: var(--ds-text-tertiary-neutral);
      color: var(--ds-state-mark-ink);
    }

    .ds-state--page .ds-state__mark { font-size: var(--ds-size-4); }
    .ds-state--panel .ds-state__mark { font-size: var(--ds-size-3); }
    .ds-state--inline .ds-state__mark { font-size: var(--ds-size-2); }

    .ds-state__mark > svg {
      inline-size: 1em;
      block-size: 1em;
    }

    /* The line stays the system's line at any size. The marks are drawn on a 24
       grid with a stroke of 2, and this is the one place the package blows one
       past that grid: 40px on a panel and 48 on a page, which multiplies the
       stroke to 3.33 and 4 while every other mark in the product draws at 1.3 to
       2. Measured 14 September 2026 in a consumer, after an empty state read as
       heavier than everything around it. `non-scaling-stroke` takes the width
       before the viewBox is scaled, so the drawing grows and its line does not. */
    .ds-state__mark > svg :is(path, circle, rect, line, polyline, polygon) {
      vector-effect: non-scaling-stroke;
    }

    /* An error is the one kind that colours its mark. It is the only one that
       needs to be found from across the page. */
    .ds-state--error .ds-state__mark {
      color: var(--ds-danger-ink);
    }

    .ds-state__text {
      display: flex;
      flex-direction: column;
      gap: var(--ds-rhythm-line);
      align-items: center;
      /* A line of prose stops being readable somewhere past sixty characters, and
         a centred one stops sooner. */
      max-inline-size: 44ch;
    }

    .ds-state__title {
      --ds-state-title-weight: var(--ds-emphasis-weight);
      --ds-state-title-face: var(--ds-font-body-face);
      font-family: var(--ds-state-title-face);
      font-weight: var(--ds-state-title-weight);
      --ds-state-title-ink: var(--ds-text-primary-neutral);
      color: var(--ds-state-title-ink);
    }

    .ds-state--page .ds-state__title {
      --ds-state-title-face: var(--ds-font-heading-5-face);
      --ds-state-title-tracking: var(--ds-font-heading-5-tracking);
      --ds-state-title-size: var(--ds-font-heading-5-size);
      --ds-state-title-weight: var(--ds-font-heading-5-weight);
      font-family: var(--ds-state-title-face);
     font-size: var(--ds-state-title-size); 
      letter-spacing: var(--ds-state-title-tracking);
      font-weight: var(--ds-state-title-weight);}
    .ds-state--panel .ds-state__title {
      --ds-state-title-face: var(--ds-font-heading-6-face);
      --ds-state-title-tracking: var(--ds-font-heading-6-tracking);
      --ds-state-title-size: var(--ds-font-heading-6-size);
      font-family: var(--ds-state-title-face);
     font-size: var(--ds-state-title-size); 
      letter-spacing: var(--ds-state-title-tracking);}
    .ds-state--inline .ds-state__title {
      --ds-state-title-face: var(--ds-font-body-face);
      --ds-state-title-tracking: var(--ds-font-body-tracking);
      --ds-state-title-size: var(--ds-font-body-size);
      font-family: var(--ds-state-title-face);
     font-size: var(--ds-state-title-size); 
      letter-spacing: var(--ds-state-title-tracking);}

    .ds-state__description {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-state-description-face: var(--ds-font-label-face);
      --ds-state-description-tracking: var(--ds-font-label-tracking);
      --ds-state-description-size: var(--ds-font-label-size);
      --ds-state-description-weight: var(--ds-font-label-weight);
      --ds-state-description-leading: var(--ds-font-label-leading);
      font-family: var(--ds-state-description-face);
      font-size: var(--ds-state-description-size);
      letter-spacing: var(--ds-state-description-tracking);
      font-weight: var(--ds-state-description-weight);
      line-height: var(--ds-state-description-leading);
      --ds-state-description-ink: var(--ds-text-secondary-neutral);
      color: var(--ds-state-description-ink);
    }

    /* ==========================================================================
       Alert

       The CSS behind <Alert>. Private, like every other component's.

       A band that says something about the page it is on: what went wrong, what
       worked, what is about to expire, what changed while you were away. It stays
       until it is answered or dismissed, which is what separates it from a thing
       that appears and goes.

       ── The tone is a wash and a line, never a fill ──────────────────────────
       Six of them, and each is the tint of its own colour with the edge of that
       colour down the leading side. A solid block of red under black text is a
       band the eye reads before the sentence it is on, and a page with two of
       them is a page nobody reads at all.

       The line and the mark take the tone's INK rather than its fill. The fill is
       calibrated to carry white text on top of it, so on a dark page it is a dark
       blue on a darker blue: measured, the info mark came out at #0369a1 on a
       near-black wash. The ink is the version of each tone that is meant to be
       read on the page, and it flips with the theme, which is the whole reason it
       exists.
       ========================================================================== */

    .ds-alert {
      /* This component's own. The role says what the words are; this says that
         THIS component wants a heavier hand than the role has, which is a
         decision the component makes and nobody else. Pointed at the semantic
         rather than holding a number, so moving the system moves every component
         that has not been moved on purpose, and moving one moves one. */
      display: flex;
      align-items: start;
      gap: var(--ds-space-3);
      padding: var(--ds-space-4);
      border-radius: var(--ds-radius-2);
      /* The line is a border on the leading side alone, so it follows the writing
         direction rather than sitting on the left of a page that reads right to
         left. */
      border-inline-start: var(--ds-space-1) solid var(--ds-alert-edge);
      background-color: var(--ds-alert-wash);
      --ds-alert-ink: var(--ds-text-primary-neutral);
      color: var(--ds-alert-ink);
    }

    .ds-alert--neutral {
      --ds-alert-edge: var(--ds-border-strong);
      --ds-alert-wash: var(--ds-surface-2);
      --ds-alert-mark: var(--ds-text-secondary-neutral);
    }

    .ds-alert--info {
      --ds-alert-edge: var(--ds-info-ink);
      --ds-alert-wash: var(--ds-info-tint);
      --ds-alert-mark: var(--ds-info-ink);
    }

    .ds-alert--success {
      --ds-alert-edge: var(--ds-success-ink);
      --ds-alert-wash: var(--ds-success-tint);
      --ds-alert-mark: var(--ds-success-ink);
    }

    .ds-alert--warning {
      --ds-alert-edge: var(--ds-warning-ink);
      --ds-alert-wash: var(--ds-warning-tint);
      --ds-alert-mark: var(--ds-warning-ink);
    }

    .ds-alert--danger {
      --ds-alert-edge: var(--ds-danger-ink);
      --ds-alert-wash: var(--ds-danger-tint);
      --ds-alert-mark: var(--ds-danger-ink);
    }

    /* The mark. Its colour is the tone's, because it is the one part read as a
       symbol rather than as text: a coloured glyph is what tells somebody skimming
       which of five this is.

       It is as tall as the line it stands beside, and it has to say WHICH line.
       `1lh` resolves against the element's own leading, and this element is a
       sibling of the words rather than a child of them: it inherited the page's 24
       and stood next to a title reading at 20, which is the same mismatch `1em`
       made everywhere else. The title's own leading is declared here, so the two
       are one number in two places and cannot drift. */
    .ds-alert__mark {
      --ds-alert-mark-leading: var(--ds-font-body-leading);
      display: inline-flex;
      flex: none;
      line-height: var(--ds-alert-mark-leading);
      inline-size: 1lh;
      block-size: 1lh;
      color: var(--ds-alert-mark);
    }

    .ds-alert__mark > svg {
      inline-size: 100%;
      block-size: 100%;
    }

    .ds-alert__text {
      display: flex;
      flex-direction: column;
      gap: var(--ds-space-1);
      min-inline-size: 0;
      flex: 1;
    }

    /* One step over the line under it. At the same size and weight as the sentence
       it introduces, the two read as one paragraph that happens to start in bold:
       the band had a title nobody could tell from its own body. */
    .ds-alert__title {
      --ds-alert-title-weight: var(--ds-emphasis-weight);
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-alert-title-face: var(--ds-font-body-face);
      --ds-alert-title-tracking: var(--ds-font-body-tracking);
      --ds-alert-title-size: var(--ds-font-body-size);
      --ds-alert-title-leading: var(--ds-font-body-leading);
      margin: 0;
      font-family: var(--ds-alert-title-face);
      font-size: var(--ds-alert-title-size);
      letter-spacing: var(--ds-alert-title-tracking);
      line-height: var(--ds-alert-title-leading);
      font-weight: var(--ds-alert-title-weight);
    }

    .ds-alert__body {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-alert-body-face: var(--ds-font-label-face);
      --ds-alert-body-tracking: var(--ds-font-label-tracking);
      --ds-alert-body-size: var(--ds-font-label-size);
      --ds-alert-body-weight: var(--ds-font-label-weight);
      --ds-alert-body-leading: var(--ds-font-label-leading);
      margin: 0;
      font-family: var(--ds-alert-body-face);
      font-size: var(--ds-alert-body-size);
      letter-spacing: var(--ds-alert-body-tracking);
      font-weight: var(--ds-alert-body-weight);
      line-height: var(--ds-alert-body-leading);
      --ds-alert-body-ink: var(--ds-text-secondary-neutral);
      color: var(--ds-alert-body-ink);
    }

    /* What to do about it, under the words. A step of air over them, because they
       are an answer to the sentence rather than the next line of it. */
    .ds-alert__actions {
      display: flex;
      align-items: center;
      flex-wrap: wrap;
      gap: var(--ds-space-2);
      margin-block-start: var(--ds-space-3);
    }

    /* ── Compact ─────────────────────────────────────────────────────────────
       The same band with the answer beside the words rather than under them, for
       a bar over a table where a third row is a third of the screen spent on a
       sentence. The words keep their own stack: the title and the line under it
       are a heading and its sentence, and putting them on one line makes the
       sentence read as the second half of the title. */
    .ds-alert--inline {
      align-items: center;
    }

    .ds-alert--inline .ds-alert__actions {
      margin-block-start: 0;
      margin-inline-start: auto;
    }

    /* The way out, at the end and away from the answer: a cross beside a button
       is a cross somebody presses instead of it. */
    .ds-alert__close {
      flex: none;
      margin-inline-start: auto;
    }

    /* ==========================================================================
       Switch

       The lever, and nothing else. Everything around it is the choice skin, worn
       on the same element: `ds-choice__mark` carries the slot and
       `ds-switch__track` draws what goes in it.

       ── The proportions ───────────────────────────────────────────────────────
       A square and a circle are as wide as they are tall, and the skin sizes the
       slot from one number for that reason. A lever cannot be: it has to show
       that there is somewhere for the knob to go, so it takes 1.75 of that number
       across and the same down.

       The knob is eight tenths of the mark, 14.4 in an 18 track, with a tenth of
       rail all round. Every number here is a share of the mark and none is a fixed
       pixel, so the six tiers keep one drawing rather than six.

       Filling it is also what lets the rail stay quiet. At 16 of 18 the part of
       the rail still showing is a sliver on one side, and the side it is on is
       what a reader finds: the knob does not have to win a contrast fight with
       the rail underneath it, so the rail does not have to be loud and the knob
       does not need a ring or a colour of its own per state.

       The travel is exactly three quarters of the mark and falls out of the
       arithmetic rather than being typed: 1.75 of track, less 0.8 of knob, less a
       tenth of inset on each side, is 0.75. It survived the knob growing, which is what a number
       derived rather than measured is for.

       ── The state is the fill, not the position alone ─────────────────────────
       Somebody who cannot tell the two greys apart still has to read this, and a
       knob that has moved a few pixels is the whole of the difference otherwise.
       On, the track takes the tone's fill the way every other control does, so
       the change is a change of colour and of position at once.
       ========================================================================== */

    .ds-switch__track {
      /* Restated from the skin, which already lays this element out as a grid.
         The check asks a painted box to say how it is laid out, and it asks per
         rule rather than per element, which is the right side to be wrong on: a
         rule that paints and says nothing is how a box ends up somewhere by
         accident. */
      display: grid;
      inline-size: calc(var(--ds-choice-mark) * 1.75);
      border-radius: var(--ds-radius-full);
      /* No border at all, rather than a border painted the colour of the fill.
         A square and a circle are identified by their outline and keep the skin's;
         a rail is identified by being filled, and the honest way to say it has no
         line is to have none. Painting one the colour of the surface was a surface
         token in a border slot, which is the same mistake as a border token in a
         background: a value calibrated for one job doing another.

         The geometry follows: with no border the padding box is the whole 31.5 by
         18, so the knob takes two pixels of inset instead of one and the travel is
         still exactly three quarters of the mark. */
      border: 0;
      /* A rail needs a body. The skin gives a mark the sheet colour, which is
         right for a square and a circle: at 18px an outline is the whole shape,
         and a fill would only be a fill waiting to be turned on. A pill of 31.5
         by 18 is not an outline, it is a room, and given the same colour as the
         sheet it stands on it has no floor: off read as a grey dot behind a
         hairline, on read as a solid black body, and the two looked like two
         objects rather than two states of one. A step down puts the floor in. */
      /* Off is quiet. It was the strong border colour for a revision, which is a
         grey that asserts: a row of settings all switched off read as a row of
         grey pills demanding to be looked at. A surface step says the rail is
         there and says nothing else. What carries the state is the rail filling
         with the tone, not the knob standing out against it. */
      background-color: var(--ds-surface-3);
    }

    /* The knob. Absolutely placed rather than laid out, so the track's height is
       the mark's and nothing about the knob can push it. */
    .ds-switch__track::before {
      content: "";
      position: absolute;
      inset-block-start: 50%;
      inset-inline-start: calc(var(--ds-choice-mark) * 0.1);
      inline-size: calc(var(--ds-choice-mark) * 0.8);
      block-size: calc(var(--ds-choice-mark) * 0.8);
      border-radius: 50%;
      /* The sheet colour, and the same in both states. The knob used to be the
         strong border when off and the tone's ink when on, which made it light on
         a dark rail one way and dark on a light rail the other: two objects
         swapping places rather than one sliding, and a light disc on a dark ground
         paints larger than a dark disc on a light one, so it read as two sizes.
         Proved by forcing one rail colour on both states, where the two measured
         the same. One colour per theme now, so the flip is gone with it. */
      background-color: var(--ds-surface-1);
      translate: 0 -50%;
      transition:
        translate var(--ds-duration) var(--ds-ease),
        background-color var(--ds-duration) var(--ds-ease);
    }

    /* Only the position. The rail carries the state in colour; the knob is the
       handle and a handle does not change what it is when you move it. */
    .ds-choice__input:checked + .ds-switch__track::before {
      translate: calc(var(--ds-choice-mark) * 0.75) -50%;
    }

    /* The handle stays the handle when the control is off limits: the rail greys
       out around it, and a knob the colour of its own rail is a switch with no
       knob at all. */
    .ds-choice__input:disabled + .ds-switch__track::before {
      background-color: var(--ds-text-disabled);
    }

    /* ==========================================================================
       Table

       The CSS behind <Table>. Not public: nothing outside this package types
       these names, and they are absent from the exports map for that reason.

       A real `<table>`, with `<thead>`, `<th scope="col">` and `<caption>`. A grid
       of divs looks the same and is a different thing to anyone not looking at
       it: no row and column announcements, no way to jump a column, no caption.
       Everything below is styling on top of markup that already means something.

       ── The scroller is outside the table ─────────────────────────────────────
       A table is as wide as its columns and the page is not, so the overflow has
       to happen in a box around it. That box is focusable, because a region you
       can only reach by dragging is a region a keyboard cannot reach at all.
       ========================================================================== */

    /* Both axes, because `overflow-x: auto` alone already makes this a scroll
       container vertically as far as sticky is concerned. The header sticks to
       the nearest scrollport, so the nearest scrollport has to be the one with a
       height on it: with the height on an outer div the header scrolled away,
       measured at -199px after a 200px scroll. */

    /* ── The sheet ────────────────────────────────────────────────────────────
       The table is a sheet of its own: the ground and the edge a card has, on
       an element that is not a card. Owner's ruling, 6 September 2026, after
       the table had been put inside a Card to get them and its pinned column
       and sticky header had painted a card's ground on pages that were not
       one. A list is an object of content and stands on its own; what it
       holds, the strip of tools at its head and the pager at its foot, stands
       inside the same edge. The corners clip what scrolls under them. */
    .ds-table {
      display: block;
      background-color: var(--ds-surface-1);
      border: 1px solid var(--ds-border);
      border-radius: var(--ds-radius-3);
      overflow: hidden;
      /* The room around a cell's words, one decision for the header and the
         body so the two stay level. `compact` takes a rung off each axis: a
         list read at length wants more rows in the window, and the words are
         the same size either way. */
      --ds-table-cell-pad-block: var(--ds-space-3);
      --ds-table-cell-pad-inline: var(--ds-space-4);
      /* The narrowest a column may be dragged to, and the step the keyboard
         moves it by. Read by the script on both halves, so the number lives
         here and nowhere else. */
      --ds-table-column-min: var(--ds-size-4);
      --ds-table-resize-step: var(--ds-space-2);
      /* The column of boxes, a rung of the control ramp wide: named, so the
         column pinned beside it knows where it stands. Measured 44 with the box
         and its pad inside; 48 is the rung above. */
      --ds-table-select-width: var(--ds-size-4);
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-table-face: var(--ds-font-label-face);
      --ds-table-tracking: var(--ds-font-label-tracking);
      --ds-table-size: var(--ds-font-label-size);
      --ds-table-leading: var(--ds-font-label-leading);
      /* This component's own. The role says what the words are; this says that
         THIS component wants a heavier hand than the role has, which is a
         decision the component makes and nobody else. Pointed at the semantic
         rather than holding a number, so moving the system moves every component
         that has not been moved on purpose, and moving one moves one. */
    }

    .ds-table__scroll {
      overflow: auto;
      overscroll-behavior-x: contain;
    }

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

    /* The table element itself, under the block: the block is the sheet, this
       is the grid inside it. */
    .ds-table__table {
      /* As wide as its columns ask for, and never narrower than its scroller.
         Under `fixed` below, `max-content` is the SUM OF THE DECLARED WIDTHS and
         nothing else: fixed layout does not read cell content, so this cannot
         leak the way the same value did under `auto`, where it travelled up as a
         minimum through everything that sizes to its content and put 1104px of
         sideways scroll on the page.
         The floor keeps a small table filling its box rather than sitting in the
         corner of it. */
      inline-size: max-content;
      min-inline-size: 100%;
      /* A column's width means that width.
         In `auto` it is a wish: the browser hands it out when there is room and
         takes it back when there is not, and it takes it from the widest column
         first. Measured on a 375 screen: a name column asking 16rem was given
         131px, and at 131 a name wraps onto two lines — while the scroller beside
         it was already scrolling, which is the worst of both, the columns squeezed
         AND the table past the edge.
         `fixed` makes the declared widths binding and stops the browser reading
         every cell to decide: what is declared is kept, what is not declared
         shares the rest, and a table wider than its scroller scrolls, which is
         what the scroller is for.
         The other way out was a `max-content` floor, and it leaks: as a MINIMUM it
         travels up through everything that sizes to its content, and the page
         started scrolling sideways by 1104px. */
      table-layout: fixed;
      border-collapse: separate;
      border-spacing: 0;
      font-family: var(--ds-table-face);
      font-size: var(--ds-table-size);
      letter-spacing: var(--ds-table-tracking);
      /* The leading paired with that size. Declared and not inherited: a table
         that states its size and lets the line come from the page put 14px text
         in a 24px line box, which is the box of a 16px word, and made every row
         four pixels taller than the type in it asked for. */
      line-height: var(--ds-table-leading);
      --ds-table-ink: var(--ds-text-primary-neutral);
      color: var(--ds-table-ink);
    }

    /* Named, not hidden. A caption is how a screen reader says which table this
       is before reading any of it, and visually the page usually says it already
       in a heading, so the default is to hide it from sight and not from the
       tree. */
    .ds-table__caption {
      position: absolute;
      inline-size: 1px;
      block-size: 1px;
      margin: -1px;
      overflow: hidden;
      clip-path: inset(50%);
      white-space: nowrap;
    }

    .ds-table__caption--visible {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-table-caption--visible-face: var(--ds-font-label-face);
      --ds-table-caption--visible-tracking: var(--ds-font-label-tracking);
      --ds-table-caption--visible-size: var(--ds-font-label-size);
      --ds-table-caption--visible-leading: var(--ds-font-label-leading);
      position: static;
      inline-size: auto;
      block-size: auto;
      margin: 0;
      clip-path: none;
      padding-block-end: var(--ds-rhythm-element);
      font-family: var(--ds-table-caption--visible-face);
      font-size: var(--ds-table-caption--visible-size);
      letter-spacing: var(--ds-table-caption--visible-tracking);
      line-height: var(--ds-table-caption--visible-leading);
      --ds-table-caption--visible-ink: var(--ds-text-secondary-neutral);
      color: var(--ds-table-caption--visible-ink);
      text-align: start;
    }

    /* ── Header ──────────────────────────────────────────────────────────────
       One line under it and nothing else. A filled header band competes with the
       selected row, which is the only band that should mean anything. */
    .ds-table__th {
      --ds-table-th-weight: var(--ds-emphasis-weight);
      padding: var(--ds-table-cell-pad-block) var(--ds-table-cell-pad-inline);
      border-block-end: 1px solid var(--ds-border-subtle);
      font-weight: var(--ds-table-th-weight);
      --ds-table-th-ink: var(--ds-text-primary-neutral);
      color: var(--ds-table-th-ink);
      text-align: start;
      white-space: nowrap;
      vertical-align: middle;
    }

    /* Positioned, so a handle at its edge has an edge to sit on; sticky is also
       positioned and wins where the header sticks. */
    .ds-table--resizable .ds-table__th {
      position: relative;
      /* The width the reader sets is the width they see: with the padding
         outside the box, a header written back at its own measured width grew
         by two paddings on every press (32px, measured 5 September 2026). */
      box-sizing: border-box;
    }

    /* The handle: a strip down the header's end edge, as tall as the cell, that
       reads as the edge itself. A line appears under the pointer and on focus,
       in the active colour, since dragging it is what the pointer is about to
       do. Touch is told to leave the strip to the script. */
    .ds-table__resizer {
      position: absolute;
      inset-block: 0;
      inset-inline-end: 0;
      inline-size: var(--ds-space-2);
      cursor: col-resize;
      touch-action: none;
      user-select: none;
    }

    .ds-table__resizer::after {
      /* The line's own ink, nothing at rest: a line that is a box of one pixel
         may take a border token, and the states below change the ink rather
         than the box. */
      --ds-table-resizer-ink: transparent;
      content: "";
      position: absolute;
      inset-block: 0;
      inset-inline-end: 0;
      inline-size: 1px;
      background-color: var(--ds-table-resizer-ink);
      transition: background-color var(--ds-duration) var(--ds-ease);
    }

    .ds-table__resizer:hover::after,
    .ds-table__resizer:focus-visible::after,
    .ds-table__resizer[data-resizing]::after {
      --ds-table-resizer-ink: var(--ds-border-active);
    }

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

    .ds-table--sticky .ds-table__th {
      position: sticky;
      inset-block-start: 0;
      z-index: 1;
      /* Opaque, or the rows read through it as they pass underneath. */
      background-color: var(--ds-surface-1);
    }

    /* The sort button is the words and the arrow, and the press lands on it
       whole. */
    .ds-table__sort {
      display: inline-flex;
      align-items: center;
      gap: var(--ds-space-2);
      padding: 0;
      border: 0;
      background: none;
      font: inherit;
      color: inherit;
      cursor: pointer;
    }

    .ds-table__sort:focus-visible {
      outline: 2px solid var(--ds-focus);
      outline-offset: 2px;
      border-radius: var(--ds-radius-1);
    }

    /* The arrow is drawn on every sortable column and revealed on the sorted one,
       so the header row does not reflow when the sort moves. */
    .ds-table__arrow {
      display: inline-flex;
      inline-size: 1lh;
      block-size: 1lh;
      --ds-table-arrow-ink: var(--ds-text-tertiary-neutral);
      color: var(--ds-table-arrow-ink);
      opacity: 0;
      transition: opacity var(--ds-duration) var(--ds-ease);
    }

    .ds-table__arrow > svg {
      inline-size: 100%;
      block-size: 100%;
    }

    .ds-table__sort:hover .ds-table__arrow,
    .ds-table__sort:focus-visible .ds-table__arrow {
      opacity: 0.5;
    }

    .ds-table__th[aria-sort] .ds-table__arrow {
      opacity: 1;
      --ds-table-arrow-ink: var(--ds-text-primary-neutral);
      color: var(--ds-table-arrow-ink);
    }

    /* ── Moving a column ──────────────────────────────────────────────────────
       The grip drags, and only the grip. The whole cell dragged for a while, on
       the argument that 14px is something you aim at rather than grab; then the
       header gained a sort button, a menu's door and a resize handle, and a
       draggable cell took the press meant for every one of them the moment the
       pointer moved a pixel. Sorting failed for the owner on 5 September 2026
       and that was why. So the cell is where a column lands, and the grip is
       what picks it up, with its target pushed out to 24px.

       The grip stays a button, because dragging is a pointer gesture and a
       keyboard has none, so the arrows move the column too. Its own target is
       pushed out to 24px the way the checkbox does it.

       Before the title and always on show. It was after the words and faded in on
       hover, which hid the one thing that says the columns can be moved from
       everybody who was not already dragging one: an affordance nobody can see is
       a feature nobody has. Tertiary, so a row of them is quiet rather than
       absent. */
    /* ── A quiet header ───────────────────────────────────────────────────────
       The grip and the menu's door are there when the pointer is over the
       header, when the keyboard is on them, or while the door is open, and not
       otherwise: five headers each wearing two marks read as a row of controls
       with the names in between, and the owner called it chaos, which it was.

       The door sits at the far end of its header, pushed there by its own auto
       margin, so it fades in and out of a place nothing else wants and no name
       moves for it.

       The grip is at the head of the name, and that place is not free. It used
       to hold its room whether it was drawn or not, so that the name would not
       shift when the pointer arrived — and the price was every heading standing
       a grip and a gap to the right of the column it names, while the values
       under it started at the cell's own gutter. Five headers indented by
       something invisible, against a body that was not: the owner read the row
       as crooked on 7 September 2026, and it was.

       So the grip takes no room until it is wanted, and the name slides over to
       make it when it comes. The width and the gap move together, over the same
       beat as the fade, which is what turns a jump into a gesture. With no room
       it has nothing to be hit on either, so it stops taking the pointer as
       well: an invisible target lying over the first letters of a heading is
       the sort column not answering.

       Where nothing hovers there is no arriving pointer to animate, so the grip
       is simply there, holding its room like any other mark, and so is the
       door. */
    .ds-table__grip,
    .ds-table__head-menu {
      opacity: 0;
    }

    .ds-table__head-menu {
      transition: opacity var(--ds-duration) var(--ds-ease);
    }

    .ds-table__grip {
      /* Its room, not its size: the box stays a line tall and a line wide, and
         what opens and closes is how much of that room the flex track gives it.
         The negative start margin is the track's own gap, cancelled, so that at
         rest there is nothing at all between the cell's edge and the name.
         What moves is declared once, on the rule below that describes the mark
         itself: a second `transition` here would be a second answer, and the
         later one wins whatever it leaves out. Which is how the fade this
         header was written around had been dead since the day the grip was
         given a colour to change. */
      max-inline-size: 0;
      margin-inline-start: calc(-1 * var(--ds-space-2));
      pointer-events: none;
    }

    .ds-table__th:hover .ds-table__grip,
    .ds-table__grip:focus-visible,
    .ds-table__grip[draggable]:active {
      opacity: 1;
      max-inline-size: 1lh;
      margin-inline-start: 0;
      pointer-events: auto;
    }

    .ds-table__th:hover .ds-table__head-menu,
    .ds-table__head-menu:focus-visible,
    .ds-table__head-menu[aria-expanded="true"] {
      opacity: 1;
    }

    @media (hover: none) {
      .ds-table__grip,
      .ds-table__head-menu {
        opacity: 1;
      }

      .ds-table__grip {
        max-inline-size: 1lh;
        margin-inline-start: 0;
        pointer-events: auto;
      }
    }

    .ds-table__head-cell {
      display: flex;
      align-items: center;
      gap: var(--ds-space-2);
    }

    .ds-table__cell--end .ds-table__head-cell {
      justify-content: flex-end;
    }

    /* The column's menu, at the far end of its header whatever the header's own
       alignment: the words sit where the column's values sit, the door sits at
       the edge. */
    .ds-table__head-menu {
      margin-inline-start: auto;
    }

    .ds-table__cell--end .ds-table__head-menu {
      margin-inline-start: 0;
    }

    .ds-table__grip {
      position: relative;
      display: inline-flex;
      flex: none;
      /* A button inherits neither the face nor the leading, so `1lh` resolves
         against `normal` and the mark comes out 16 whatever the line beside it is.
         Measured 16 in a 24px line before this. */
      font: inherit;
      inline-size: 1lh;
      block-size: 1lh;
      padding: 0;
      border: 0;
      background: none;
      --ds-table-grip-ink: var(--ds-text-tertiary-neutral);
      color: var(--ds-table-grip-ink);
      cursor: grab;
      /* Everything this mark changes, in one place: the ink under the pointer,
         and the room it opens as it arrives. */
      transition:
        color var(--ds-duration) var(--ds-ease),
        opacity var(--ds-duration) var(--ds-ease),
        max-inline-size var(--ds-duration) var(--ds-ease),
        margin-inline-start var(--ds-duration) var(--ds-ease);
    }

    .ds-table__grip:hover,
    .ds-table__grip:focus-visible {
      --ds-table-grip-ink: var(--ds-text-primary-neutral);
      color: var(--ds-table-grip-ink);
    }

    .ds-table__grip > svg {
      inline-size: 100%;
      block-size: 100%;
    }

    .ds-table__grip::after {
      content: "";
      position: absolute;
      inset-block-start: 50%;
      inset-inline-start: 50%;
      inline-size: max(24px, 100%);
      block-size: max(24px, 100%);
      transform: translate(-50%, -50%);
    }

    .ds-table__grip:focus-visible {
      outline: 2px solid var(--ds-focus);
      outline-offset: 2px;
      border-radius: var(--ds-radius-1);
    }


    /* The column being carried, and the gap it would fall into. A line and not a
       shaded cell: the line is where it lands, and a shaded cell says the column
       under the pointer is the one moving. */
    .ds-table__th--dragging {
      opacity: 0.4;
    }

    .ds-table__th--over {
      box-shadow: inset 2px 0 0 var(--ds-focus);
    }

    /* ── Body ─────────────────────────────────────────────────────────────────
       Two questions, asked separately. `lines` is where the rules are drawn:
       between rows, between rows and columns, or nowhere. `zebra` shades every
       other row instead. They are separate props because they answer different
       things and a table can want either, both or neither: a dense numeric grid
       reads best with both, a short list of names with neither. */
    .ds-table__td {
      padding: var(--ds-table-cell-pad-block) var(--ds-table-cell-pad-inline);
      vertical-align: middle;
    }

    .ds-table--lines-rows .ds-table__td,
    .ds-table--lines-grid .ds-table__td {
      border-block-end: 1px solid var(--ds-border-subtle);
    }

    .ds-table--lines-rows .ds-table__row:last-child .ds-table__td,
    .ds-table--lines-grid .ds-table__row:last-child .ds-table__td {
      border-block-end: 0;
    }

    /* Between the columns and not around the table: the outer edge belongs to
       whatever the table is sitting in. */
    .ds-table--lines-grid .ds-table__td + .ds-table__td,
    .ds-table--lines-grid .ds-table__th + .ds-table__th {
      border-inline-start: 1px solid var(--ds-border-subtle);
    }

    /* Under the row states, so a hovered stripe still reads as hovered. */
    .ds-table--zebra .ds-table__row:nth-child(even) {
      background-color: var(--ds-surface-hover);
    }

    .ds-table__row {
      transition: background-color var(--ds-duration) var(--ds-ease);
    }

    /* A row that leads somewhere says so with the hand; the hover wash the rows
       already share does the rest. */
    .ds-table__row--link {
      cursor: pointer;
    }

    .ds-table__row:hover {
      background-color: var(--ds-surface-hover);
    }

    /* The row with the keyboard's focus. Drawn inside its own box, since rows
       touch and a ring outside one would sit on the next. */
    .ds-table__row:focus-visible {
      outline: 2px solid var(--ds-focus);
      outline-offset: -2px;
    }

    /* Selected wins over hover, because a row you chose stays chosen while the
       pointer wanders. */
    /* The line that adds a row where it is typed, at the foot of the rows: a mark
       and a field on the cell's own padding, so what is typed lands where the row
       will stand. The list settled the same way, and this is that line in a
       table's cells. */
    .ds-table__add-line {
      display: flex;
      align-items: center;
      gap: var(--ds-space-2);
      --ds-table-add-ink: var(--ds-text-secondary-neutral);
      color: var(--ds-table-add-ink);
    }

    .ds-table__add-mark {
      display: inline-flex;
      flex: none;
      inline-size: var(--ds-space-4);
      block-size: var(--ds-space-4);
    }

    /* It is not a row to walk or to tick, so the wash the rows share is not its:
       said on the row itself rather than on its hover, since a rule that only
       answers the pointer is a change of state and this is what the line IS. */
    .ds-table__add {
      background-color: transparent;
    }

    .ds-table__row--selected,
    .ds-table__row--selected:hover {
      background-color: var(--ds-surface-active);
    }

    /* ── Alignment ───────────────────────────────────────────────────────────
       Two, and `end` is for numbers and for the actions. A column of numbers
       aligned left is a column you cannot compare down. */
    /* One line, cut where it does not fit.
     *
     * A table is read DOWN its columns, and a cell that wraps pushes its whole row
     * taller than the rest: a column of names where one is long is a table with a
     * step in it (owner, 13 September 2026: "non wrappare ma fai ... quando troppo
     * lungo").
     *
     * The clamp is on the cell and the width is the column's, which is what makes
     * an ellipsis possible at all: a `td` is as wide as the table lets it be, and
     * text can only be cut inside a box that has a width to be cut to.
     *
     * Asked for per column, never given: a cell holding boxes, badges or several
     * controls is laid out rather than written, and one line would crop it. */
    .ds-table__cell--clamp {
      overflow: hidden;
      white-space: nowrap;
      text-overflow: ellipsis;
      /* `max-inline-size` and not `inline-size`, so a column with no width still
         gives the cell somewhere to be cut: without a boundary the cell grows to
         its longest line and there is nothing to cut it against. */
      max-inline-size: 1px;
    }

    .ds-table__cell--start { text-align: start; }
    .ds-table__cell--end { text-align: end; }

    /* The checkbox column is as narrow as its box and never grows. */
    .ds-table__cell--select {
      box-sizing: border-box;
      inline-size: var(--ds-table-select-width);
      padding-inline-end: 0;
      white-space: nowrap;
    }

    /* ── Frozen ───────────────────────────────────────────────────────────────
       The first column stays while the rest scrolls sideways: the name a reader
       keeps their place by. Pinned by sticky, on an opaque ground or the cells
       passing underneath read through it, and the row's own washes painted on
       again, since a cell with a ground of its own no longer shows the row's.
       The column of boxes pins too, at the edge, and the first column stands
       beside it at the boxes' width.

       The opaque parts paint surface 1, the sheet's ground, which is the
       table's own since 6 September 2026: the shell paints it and the edge
       round it, so the pinned column and the sticky header always match what
       they slide over. A token for "whatever is under me" was tried the same
       day and refused by the owner: the palette has the surfaces it needs. */
    .ds-table--frozen .ds-table__cell--frozen {
      position: sticky;
      inset-inline-start: 0;
      z-index: 1;
      background-color: var(--ds-surface-1);
    }

    .ds-table--frozen .ds-table__cell--select.ds-table__cell--frozen + .ds-table__cell--frozen {
      inset-inline-start: var(--ds-table-select-width);
    }

    /* Where the pinned part ends: a line, so the columns sliding under it read
       as sliding under something, and only while something does. A table that
       fits its box has nothing sliding, and the line then was a rule down one
       column that no other column had: the owner asked what it was for, on 5
       September 2026, and there was no answer. The renderers say whether the
       columns overflow, since a stylesheet cannot ask. */
    .ds-table--frozen.ds-table--overflows .ds-table__cell--frozen:not(.ds-table__cell--select) {
      border-inline-end: 1px solid var(--ds-border);
    }

    /* The corner, above both the pinned header and the pinned column. */
    .ds-table--frozen.ds-table--sticky .ds-table__th.ds-table__cell--frozen {
      z-index: 2;
    }

    .ds-table__row:hover .ds-table__cell--frozen,
    .ds-table--zebra .ds-table__row:nth-child(even) .ds-table__cell--frozen {
      background-image: linear-gradient(var(--ds-surface-hover), var(--ds-surface-hover));
    }

    .ds-table__row--selected .ds-table__cell--frozen,
    .ds-table__row--selected:hover .ds-table__cell--frozen {
      background-image: linear-gradient(var(--ds-surface-active), var(--ds-surface-active));
    }

    /* Where the state block goes: one cell across the whole width, so the block
       centres on the table rather than on its first column. */
    .ds-table__state {
      padding: 0;
      border-block-end: 0;
    }

    /* ── Under the rows ───────────────────────────────────────────────────────
       What comes after the table: a pager, a count, a total. Its own line of air
       and a hairline above it, because the last row already has an edge and two
       lines together read as a frame nobody drew. */
    .ds-table__foot {
      display: flex;
      align-items: center;
      gap: var(--ds-space-4);
      /* The sides are the gutter the cells draw, so the count starts where the
         words above it start; it had the block half only once and shipped
         touching the frame. Above and below, a rung less than the sides: the
         band holds small controls, and at the cells' gutter on every side it
         was 65px of which 32 were the pager, which the owner read as empty on
         5 September 2026. The strip at the head of the sheet takes the same
         two rungs, so the two ends of the list are one height. */
      padding: var(--ds-space-3) var(--ds-space-4);
      border-block-start: 1px solid var(--ds-border-subtle);
    }

    /* Whatever is in there takes the width, since a pager is a row of its own
       with two ends. */
    .ds-table__foot > * {
      flex: 1;
      min-inline-size: 0;
    }

    /* Which way the arrow points. `swap.css` says there are two and that the down
       one is hidden; this says when. `aria-sort` is what the header already
       carried, so nothing new goes into the markup for it. */
    .ds-table__th[aria-sort="descending"] .ds-swap__off {
      display: none;
    }

    .ds-table__th[aria-sort="descending"] .ds-swap__on {
      display: inline-flex;
    }

    /* The column of a row's menu: as wide as the button it holds and no wider,
       at the edge the eye leaves from. The width is this component's decision
       and a tier of the control ramp, since what it holds is a control. */
    .ds-table__cell--actions {
      --ds-table-actions-width: var(--ds-size-3);
      inline-size: var(--ds-table-actions-width);
      text-align: end;
    }

    .ds-table--compact {
      --ds-table-cell-pad-block: var(--ds-space-2);
      --ds-table-cell-pad-inline: var(--ds-space-3);
    }

    /* ── Summary ──────────────────────────────────────────────────────────────
       The line under the rows that says what they add up to. Its weight is the
       header's, since it is the other end of the same column; a line above it
       marks it off from the last row. It stays at the foot of the scroller
       while the rows pass, the way the header stays at its head, on an opaque
       ground for the same reason. */
    .ds-table__summary .ds-table__td {
      --ds-table-summary-weight: var(--ds-emphasis-weight);
      position: sticky;
      inset-block-end: 0;
      z-index: 1;
      font-weight: var(--ds-table-summary-weight);
      /* One hairline above, in the grey every line inside the sheet uses, and
         none below: the rows' own rule under it met the foot's line and read
         as a double edge, three lines between the last row and the pager. The
         card draws the sheet's edge; everything inside it is subtle. */
      border-block-start: 1px solid var(--ds-border-subtle);
      border-block-end: 0;
      background-color: var(--ds-surface-1);
    }

    /* The corner of the foot, above the frozen column it shares a cell with. */
    .ds-table--frozen .ds-table__summary .ds-table__cell--frozen {
      z-index: 2;
    }

    /* ── The row's hue ────────────────────────────────────────────────────────
       A row can carry the colour of the record it stands for: the hue of the
       option it holds, as an edge down the leading side.

       An edge and not a ground. Nine tinted rows one under the other is a table
       nobody reads down, and what a reader is after is which rows are in which
       state, which the edge says without touching the words. Drawn as an inset
       shadow so a row that gains a colour gains no width: the first cell stays
       where the header put it, tinted or not.

       Asked for by the first product's view that lets a reader say which field
       lends a record its colour, 12 September 2026. */
    /* Drawn on the row's first cell and not on the row. A cell paints its own
       ground over the row's, so an inset shadow on the `tr` is behind every
       `td` and shows nowhere: measured on the first product's table, 12
       September 2026, the computed value was there and the pixel was not. */
    .ds-table__row[data-hue] > :first-child {
      box-shadow: inset var(--ds-space-1) 0 0 0 var(--ds-table-row-hue);
    }

    .ds-table__row[data-hue="red"] { --ds-table-row-hue: var(--ds-hue-red-edge); }
    .ds-table__row[data-hue="orange"] { --ds-table-row-hue: var(--ds-hue-orange-edge); }
    .ds-table__row[data-hue="amber"] { --ds-table-row-hue: var(--ds-hue-amber-edge); }
    .ds-table__row[data-hue="yellow"] { --ds-table-row-hue: var(--ds-hue-yellow-edge); }
    .ds-table__row[data-hue="lime"] { --ds-table-row-hue: var(--ds-hue-lime-edge); }
    .ds-table__row[data-hue="green"] { --ds-table-row-hue: var(--ds-hue-green-edge); }
    .ds-table__row[data-hue="teal"] { --ds-table-row-hue: var(--ds-hue-teal-edge); }
    .ds-table__row[data-hue="cyan"] { --ds-table-row-hue: var(--ds-hue-cyan-edge); }
    .ds-table__row[data-hue="blue"] { --ds-table-row-hue: var(--ds-hue-blue-edge); }
    .ds-table__row[data-hue="indigo"] { --ds-table-row-hue: var(--ds-hue-indigo-edge); }
    .ds-table__row[data-hue="violet"] { --ds-table-row-hue: var(--ds-hue-violet-edge); }
    .ds-table__row[data-hue="pink"] { --ds-table-row-hue: var(--ds-hue-pink-edge); }

    /* ── Rows arranged by hand ────────────────────────────────────────────────
       A table whose row order is the reader's opens every row with a grip: the
       same mark and the same two arrows the columns and a `Sortable` already
       have, in a column of its own with no header, because what it holds is a
       handle and not a value.

       The grip and not the row, for the reason the list gives: a row holds
       cells a reader selects text in, and `draggable` on the row takes that
       away. */
    .ds-table__cell--grip {
      inline-size: var(--ds-space-8);
      padding-inline-end: 0;
    }

    .ds-table__row-grip {
      display: inline-flex;
      align-items: center;
      justify-content: center;
      font: inherit;
      inline-size: 1lh;
      block-size: 1lh;
      padding: 0;
      border: 0;
      background: none;
      --ds-table-row-grip-ink: var(--ds-text-tertiary-neutral);
      color: var(--ds-table-row-grip-ink);
      cursor: grab;
      transition: color var(--ds-duration) var(--ds-ease);
    }

    .ds-table__row-grip:active {
      cursor: grabbing;
    }

    .ds-table__row-grip:hover,
    .ds-table__row-grip:focus-visible {
      --ds-table-row-grip-ink: var(--ds-text-primary-neutral);
      color: var(--ds-table-row-grip-ink);
    }

    /* The one in the air, and where it lands: the row it is over takes the line
       on its own leading edge, since a `tr` has no gap of its own to draw in and
       a cell paints over anything the row draws under it. */
    .ds-table__row--dragging {
      opacity: 0.4;
    }

    .ds-table__row--over > * {
      box-shadow: inset 0 2px 0 0 var(--ds-border-active);
    }

    /* ── The row that opens ───────────────────────────────────────────────────
       A row can carry a panel under it: the records it points at, a note, a
       preview. The caret stands in a column of its own at the head of the row,
       and the panel is a row of the body rather than a layer over the table, so
       what it holds scrolls, prints and reads in order with everything else.

       The caret turns rather than swapping for another mark: a chevron that
       points down is the same control saying it is open, and two marks would be
       two things to read. */
    .ds-table__cell--caret {
      inline-size: var(--ds-space-8);
      padding-inline-end: 0;
    }

    .ds-table__caret {
      display: inline-flex;
      align-items: center;
      justify-content: center;
      font: inherit;
      inline-size: 1lh;
      block-size: 1lh;
      padding: 0;
      border: 0;
      background: none;
      --ds-table-caret-ink: var(--ds-text-tertiary-neutral);
      color: var(--ds-table-caret-ink);
      cursor: pointer;
      transition:
        color var(--ds-duration) var(--ds-ease),
        rotate var(--ds-duration) var(--ds-ease);
    }

    .ds-table__caret:hover,
    .ds-table__caret:focus-visible {
      --ds-table-caret-ink: var(--ds-text-primary-neutral);
      color: var(--ds-table-caret-ink);
    }

    .ds-table__caret[aria-expanded="true"] {
      rotate: 90deg;
    }

    /* The panel's own cell: the table's padding on the outside and nothing on
       the inside, since what the caller puts there brings its own. */
    .ds-table__detail {
      padding-block: var(--ds-space-3);
    }

    /* ==========================================================================
       Page links

       The pair at the foot of a page: what came before it and what comes next.

       ── Two cards, and either one may be missing ─────────────────────────────
       The first page of a set has no previous and the last has no next, so the
       one that is there keeps its own side rather than sliding into the middle:
       the next card stays right whatever stands beside it. A pair that jumps
       sides between pages is a pair the reader has to re-find on every one.

       ── The direction is a label, not an arrow alone ─────────────────────────
       `Previous` and `Next` are read out; the mark beside them is decorative and
       hidden, because a reader told "left arrow, Opening an account" has been
       given a drawing and a title and has to work out which is the answer.
       ========================================================================== */

    .ds-page-links {
      /* Its own floor rather than the grid's 24rem, which is drawn for a card
         with a picture in it: this one holds a direction and a title, and two of
         them stop fitting side by side well before that. `min(…, 100%)` is what
         stops a single card running past the column it sits in. */
      --ds-page-links-floor: 16rem;
      display: grid;
      grid-template-columns: repeat(
        auto-fit,
        minmax(min(max(var(--ds-page-links-floor), (100% - var(--ds-space-4)) / 2), 100%), 1fr)
      );
      gap: var(--ds-space-4);
    }

    .ds-page-links__link {
      --ds-page-links-ink: var(--ds-text-primary-neutral);
      display: flex;
      flex-direction: column;
      gap: var(--ds-space-1);
      min-inline-size: 0;
      padding: var(--ds-space-4);
      border: 1px solid var(--ds-border);
      border-radius: var(--ds-radius-2);
      color: var(--ds-page-links-ink);
      text-decoration: none;
      transition:
        border-color var(--ds-duration) var(--ds-ease),
        background-color var(--ds-duration) var(--ds-ease);
    }

    .ds-page-links__link:hover {
      --ds-page-links-ink: var(--ds-text-primary-neutral);
      border-color: var(--ds-border-strong);
      background-color: var(--ds-surface-hover);
    }

    .ds-page-links__link:focus-visible {
      outline: 2px solid var(--ds-focus);
      outline-offset: 2px;
    }

    /* The next card keeps the end of the row, and its words line up with the edge
       the reader is travelling towards. */
    .ds-page-links__link--next {
      grid-column: -2;
      text-align: end;
    }

    .ds-page-links__way {
      --ds-page-links-way-face: var(--ds-font-small-face);
      --ds-page-links-way-size: var(--ds-font-small-size);
      --ds-page-links-way-weight: var(--ds-font-small-weight);
      --ds-page-links-way-leading: var(--ds-font-small-leading);
      --ds-page-links-way-tracking: var(--ds-font-small-tracking);
      --ds-page-links-way-ink: var(--ds-text-tertiary-neutral);
      display: flex;
      align-items: center;
      gap: var(--ds-space-1);
      font-family: var(--ds-page-links-way-face);
      font-size: var(--ds-page-links-way-size);
      font-weight: var(--ds-page-links-way-weight);
      line-height: var(--ds-page-links-way-leading);
      letter-spacing: var(--ds-page-links-way-tracking);
      color: var(--ds-page-links-way-ink);
    }

    .ds-page-links__link--next .ds-page-links__way {
      justify-content: flex-end;
    }

    .ds-page-links__mark {
      display: flex;
      flex: none;
      /* The line the label occupies, not the type size: a 12px mark beside a 16px
         line box reads as the smaller of two things meant to be one. */
      inline-size: 1lh;
      block-size: 1lh;
    }

    .ds-page-links__name {
      --ds-page-links-name-face: var(--ds-font-label-face);
      --ds-page-links-name-size: var(--ds-font-label-size);
      --ds-page-links-name-weight: var(--ds-emphasis-weight);
      --ds-page-links-name-leading: var(--ds-font-label-leading);
      --ds-page-links-name-tracking: var(--ds-font-label-tracking);
      font-family: var(--ds-page-links-name-face);
      font-size: var(--ds-page-links-name-size);
      font-weight: var(--ds-page-links-name-weight);
      line-height: var(--ds-page-links-name-leading);
      letter-spacing: var(--ds-page-links-name-tracking);
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
    }

    /* ==========================================================================
       Share

       The block that hands the page on: its address to copy, the device's own
       sheet where there is one, and the networks the consumer names.

       ── Words on every button ────────────────────────────────────────────────
       The networks are named, with the consumer's mark before the name where it
       gives one, or `Brand` does for the seven networks a site links to; the
       system's own set holds no marks for other people's companies, and a
       row of six drawings with no words is a row the reader has to decode. Every
       button is neutral and outlined, because a share is an offer and not the
       page's act; colour here would be six brands shouting on one line.

       ── The address is on the page ───────────────────────────────────────────
       The line shows the address in the mono face and a button copies it. With
       no script the address is still there to select by hand, which is what a
       copy button stands in for.
       ========================================================================== */

    .ds-share {
      display: flex;
      flex-direction: column;
      gap: var(--ds-rhythm-element);
      min-inline-size: 0;
    }

    .ds-share__title {
      --ds-share-title-face: var(--ds-font-label-face);
      --ds-share-title-size: var(--ds-font-label-size);
      --ds-share-title-weight: var(--ds-emphasis-weight);
      --ds-share-title-leading: var(--ds-font-label-leading);
      --ds-share-title-tracking: var(--ds-font-label-tracking);
      --ds-share-title-ink: var(--ds-text-primary-neutral);
      margin: 0;
      font-family: var(--ds-share-title-face);
      font-size: var(--ds-share-title-size);
      font-weight: var(--ds-share-title-weight);
      line-height: var(--ds-share-title-leading);
      letter-spacing: var(--ds-share-title-tracking);
      color: var(--ds-share-title-ink);
    }

    /* The address takes the room the buttons leave and ends in an ellipsis rather
       than wrapping: a long address broken over three lines is three lines of
       noise, and the whole of it is what the copy button carries. */
    .ds-share__url {
      --ds-share-url-face: var(--ds-font-mono-face);
      --ds-share-url-size: var(--ds-font-mono-size);
      --ds-share-url-weight: var(--ds-font-mono-weight);
      --ds-share-url-leading: var(--ds-font-mono-leading);
      --ds-share-url-tracking: var(--ds-font-mono-tracking);
      --ds-share-url-ink: var(--ds-text-secondary-neutral);
      flex: 1 1 12rem;
      min-inline-size: 0;
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
      font-family: var(--ds-share-url-face);
      font-size: var(--ds-share-url-size);
      font-weight: var(--ds-share-url-weight);
      line-height: var(--ds-share-url-leading);
      letter-spacing: var(--ds-share-url-tracking);
      color: var(--ds-share-url-ink);
    }

    /* A shut button stays shut whatever the button's own display says: `hidden`
       loses to any `display` on the class, and this is the button the bundle
       opens only where the device has a sheet. */
    .ds-share__native[hidden] {
      display: none;
    }

    /* ==========================================================================
       Pagination

       The CSS behind <Pagination>. Private, like every other component's.

       The row that moves you through a list too long to show at once. Almost all
       of it is buttons, so almost all of it is already styled: what is left here
       is the arrangement, the page number's square, and the gap.

       ── Three shapes, one job ─────────────────────────────────────────────────
         numbers   the page you are on and the ones you can jump to
         arrows    back and forward only, for a list whose length nobody knows
         more      one button that adds the next slice to what is already there

       ── Why the page number is square ─────────────────────────────────────────
       A button is as wide as its label, so "1" would come out half the width of
       "12" and a third of "100", and a row of them would step in and out as you
       paged through. They take the height of their tier as a minimum width, which
       makes the single digits square and lets the long ones grow.
       ========================================================================== */

    .ds-pagination {
      display: flex;
      align-items: center;
      /* The count and the pages together, at the end, the way a mail client
         writes "1-50 of 234" beside its arrows. They sat at the two ends of the
         row for a while, and under a table a screen wide that was a band with
         two small things a metre apart and nothing between: the owner read it
         as empty on 5 September 2026, and it was. Together they are one
         sentence, where you are and where you can go. */
      justify-content: flex-end;
      /* Wraps rather than scrolls: on a narrow screen the count goes above the
         buttons, which is legible. A row of pages that runs off the side is not. */
      flex-wrap: wrap;
      gap: var(--ds-space-4);
    }

    .ds-pagination__summary {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-pagination-summary-face: var(--ds-font-label-face);
      --ds-pagination-summary-tracking: var(--ds-font-label-tracking);
      --ds-pagination-summary-size: var(--ds-font-label-size);
      --ds-pagination-summary-weight: var(--ds-font-label-weight);
      --ds-pagination-summary-leading: var(--ds-font-label-leading);
      font-family: var(--ds-pagination-summary-face);
      font-size: var(--ds-pagination-summary-size);
      letter-spacing: var(--ds-pagination-summary-tracking);
      font-weight: var(--ds-pagination-summary-weight);
      line-height: var(--ds-pagination-summary-leading);
      --ds-pagination-summary-ink: var(--ds-text-secondary-neutral);
      color: var(--ds-pagination-summary-ink);
      /* Tabular, so the numbers do not shuffle as you move between pages. */
      font-variant-numeric: tabular-nums;
    }

    /* Its own width is what the rules at the foot of this file ask about, not the
       window's: a pagination under a table in a panel is neither. */
    .ds-pagination {
      container-type: inline-size;
    }

    .ds-pagination__pages {
      display: flex;
      align-items: center;
      gap: var(--ds-space-1);
    }

    /* The tier, read again here. Button maps these six to heights and this maps
       the same six to the width a page number cannot go under, which is that
       height: the two have to agree or the squares are not square. */
    .ds-pagination--xsmall { --ds-pagination-tier: var(--ds-size-1); }
    .ds-pagination--small { --ds-pagination-tier: var(--ds-size-2); }
    .ds-pagination--medium { --ds-pagination-tier: var(--ds-size-3); }
    .ds-pagination--large { --ds-pagination-tier: var(--ds-size-4); }
    .ds-pagination--xlarge { --ds-pagination-tier: var(--ds-size-5); }
    .ds-pagination--xxlarge { --ds-pagination-tier: var(--ds-size-6); }

    /* The words follow the tier, the way the buttons in the same row do: 12 at the
       bottom tier, 14 at the next, 16 from the middle up. They read at 14 on all
       six for a revision, so the bottom tier carried two type sizes on one line of
       a 24px control and the top one carried a 14px count beside a 16px button.
       The skipped-pages mark is in here too, and it takes the leading paired with
       its size rather than the 24px line box it was inheriting, which is the line
       box of a 16px word. */
    .ds-pagination--xsmall .ds-pagination__summary,
    .ds-pagination--xsmall .ds-pagination__gap {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-pagination-gap-face: var(--ds-font-small-face);
      --ds-pagination-gap-size: var(--ds-font-small-size);
      --ds-pagination-gap-weight: var(--ds-font-small-weight);
      --ds-pagination-gap-leading: var(--ds-font-small-leading);
      font-family: var(--ds-pagination-gap-face);
     font-size: var(--ds-pagination-gap-size); line-height: var(--ds-pagination-gap-leading); 
      letter-spacing: var(--ds-pagination-gap-tracking);
      font-weight: var(--ds-pagination-gap-weight);}
    .ds-pagination--small .ds-pagination__summary,
    .ds-pagination--small .ds-pagination__gap {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-pagination-gap-face: var(--ds-font-label-face);
      --ds-pagination-gap-size: var(--ds-font-label-size);
      --ds-pagination-gap-weight: var(--ds-font-label-weight);
      --ds-pagination-gap-leading: var(--ds-font-label-leading);
      font-family: var(--ds-pagination-gap-face);
     font-size: var(--ds-pagination-gap-size); line-height: var(--ds-pagination-gap-leading); 
      letter-spacing: var(--ds-pagination-gap-tracking);
      font-weight: var(--ds-pagination-gap-weight);}
    .ds-pagination--medium .ds-pagination__summary,
    .ds-pagination--medium .ds-pagination__gap {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-pagination-gap-face: var(--ds-font-body-face);
      --ds-pagination-gap-size: var(--ds-font-body-size);
      --ds-pagination-gap-weight: var(--ds-font-body-weight);
      --ds-pagination-gap-leading: var(--ds-font-body-leading);
      font-family: var(--ds-pagination-gap-face);
     font-size: var(--ds-pagination-gap-size); line-height: var(--ds-pagination-gap-leading); 
      letter-spacing: var(--ds-pagination-gap-tracking);
      font-weight: var(--ds-pagination-gap-weight);}
    .ds-pagination--large .ds-pagination__summary,
    .ds-pagination--large .ds-pagination__gap {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-pagination-gap-face: var(--ds-font-body-face);
      --ds-pagination-gap-size: var(--ds-font-body-size);
      --ds-pagination-gap-weight: var(--ds-font-body-weight);
      --ds-pagination-gap-leading: var(--ds-font-body-leading);
      font-family: var(--ds-pagination-gap-face);
     font-size: var(--ds-pagination-gap-size); line-height: var(--ds-pagination-gap-leading); 
      letter-spacing: var(--ds-pagination-gap-tracking);
      font-weight: var(--ds-pagination-gap-weight);}
    .ds-pagination--xlarge .ds-pagination__summary,
    .ds-pagination--xlarge .ds-pagination__gap {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-pagination-gap-face: var(--ds-font-body-face);
      --ds-pagination-gap-size: var(--ds-font-body-size);
      --ds-pagination-gap-weight: var(--ds-font-body-weight);
      --ds-pagination-gap-leading: var(--ds-font-body-leading);
      font-family: var(--ds-pagination-gap-face);
     font-size: var(--ds-pagination-gap-size); line-height: var(--ds-pagination-gap-leading); 
      letter-spacing: var(--ds-pagination-gap-tracking);
      font-weight: var(--ds-pagination-gap-weight);}
    .ds-pagination--xxlarge .ds-pagination__summary,
    .ds-pagination--xxlarge .ds-pagination__gap {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-pagination-gap-face: var(--ds-font-body-face);
      --ds-pagination-gap-size: var(--ds-font-body-size);
      --ds-pagination-gap-weight: var(--ds-font-body-weight);
      --ds-pagination-gap-leading: var(--ds-font-body-leading);
      font-family: var(--ds-pagination-gap-face);
     font-size: var(--ds-pagination-gap-size); line-height: var(--ds-pagination-gap-leading); 
      letter-spacing: var(--ds-pagination-gap-tracking);
      font-weight: var(--ds-pagination-gap-weight);}

    .ds-pagination__page {
      min-inline-size: var(--ds-pagination-tier);
      /* Room for a fourth digit to breathe, and none wasted on the first three. */
      padding-inline: var(--ds-space-1);
      font-variant-numeric: tabular-nums;
    }

    /* The skipped pages. Not a button: there is no single page it could take you
       to, and a control that does something unpredictable is worse than a mark
       that does nothing. */
    .ds-pagination__gap {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-pagination-gap-face: var(--ds-font-label-face);
      --ds-pagination-gap-tracking: var(--ds-font-label-tracking);
      --ds-pagination-gap-size: var(--ds-font-label-size);
      --ds-pagination-gap-weight: var(--ds-font-label-weight);
      display: flex;
      align-items: center;
      justify-content: center;
      min-inline-size: var(--ds-pagination-tier);
      --ds-pagination-gap-ink: var(--ds-text-tertiary-neutral);
      color: var(--ds-pagination-gap-ink);
      font-family: var(--ds-pagination-gap-face);
      font-size: var(--ds-pagination-gap-size);
      letter-spacing: var(--ds-pagination-gap-tracking);
      font-weight: var(--ds-pagination-gap-weight);
      /* Nothing to announce: the pages it stands for are reachable through the
         numbers on either side of it. */
      user-select: none;
    }


    /* ── Narrow ───────────────────────────────────────────────────────────────
       The run of numbers becomes one choice. Ten targets of 40px do not fit in
       375 and no arrangement makes them: the row wrapped instead, and a set of
       controls that wraps is a set that has stopped being a row.

       Which page to go to is a question with many answers, which is what a select
       is for. Both are in the markup and this chooses, so the page it sits under
       has nothing to decide. */
    .ds-pagination__choice {
      display: none;
    }

    @container (width <= 30rem) {
      .ds-pagination__number {
        display: none;
      }

      .ds-pagination__choice {
        display: block;
      }
    }

    /* ==========================================================================
       Skeleton

       The CSS behind <Skeleton>. Private, like every other component's.

       The shape of the thing that is coming, standing where it will stand.

       ── It says what is coming ───────────────────────────────────────────────
       A spinner says "wait". A skeleton says "a card with a title, two lines and
       a picture is about to be here", and it says it in the place the card will
       occupy. That is the whole value: nothing moves when the answer arrives.
       A skeleton earns its keep by being the size of what replaces it; anything
       else promises a layout and then breaks it.

       ── It is a shape, and the layout is the page's ──────────────────────────
       A card that is loading is the same Card, the same Stack, the same Grid,
       with the words replaced by these, which is how the two versions stay one
       card and how a page swaps them one piece at a time as each piece arrives.
       The whole arrangement lives with the page, so `<SkeletonCard>` stays
       unwritten on purpose: it would be a second place for it to live.

       ── It measures itself in lines ──────────────────────────────────────────
       `1lh` of whatever it stands in. A skeleton inside a heading is heading-sized
       without being told, and one inside a caption is caption-sized, because both
       read the line they were put on. A pixel height would have to be repeated
       everywhere the type changes, and would be wrong on the surface that makes
       the type denser.
       ========================================================================== */

    .ds-skeleton {
      display: block;
      border-radius: var(--ds-radius-1);
      /* The raised surface, not a grey: it has to be a shade of the page rather
         than a colour, or a hundred of them turn the screen into a chessboard. */
      background-color: var(--ds-surface-2);
      /* The sweep. One gradient moving across, rather than the whole block
         pulsing: a page of pulsing rectangles is a page that flashes, and the
         sweep reads as one thing loading rather than forty. */
      background-image: linear-gradient(
        90deg,
        transparent 0%,
        var(--ds-surface-3) 50%,
        transparent 100%
      );
      background-size: 200% 100%;
      background-repeat: no-repeat;
      /* Twice the loop token, because a pass crosses the whole width where the
         spinner's turn crosses a circle: at one loop the sweep read as frantic.
         Reading the token is also what stops it under reduced motion, which
         motion.css handles once by zeroing the durations. This rule carried a
         hardcoded 1.4s and its own media query until 22 August 2026, the second
         way of doing a thing motion.css already does; the computed duration is
         the same 1400ms it always was. */
      animation: ds-skeleton-sweep calc(var(--ds-duration-loop) * 2) linear infinite;
    }

    @keyframes ds-skeleton-sweep {
      from { background-position: 200% 0; }
      to { background-position: -200% 0; }
    }

    /* ── Text ─────────────────────────────────────────────────────────────── */

    /* A line, at the height of the line it stands in, in the middle of the space
       that line occupies: text does not fill its line box, and a bar that does
       reads as a block rather than as a sentence. */
    .ds-skeleton--text {
      block-size: 0.7lh;
      margin-block: 0.15lh;
    }

    .ds-skeleton__lines {
      display: flex;
      flex-direction: column;
    }

    /* The last line of a paragraph is short, and that one detail is what makes a
       stack of bars read as text at all. */
    .ds-skeleton__lines > .ds-skeleton--text:last-child:not(:only-child) {
      inline-size: 60%;
    }

    /* ── Block and circle ─────────────────────────────────────────────────── */

    /* A picture, a map, a chart: it has no line to read, so it takes the size it
       is given. The ratio is the honest way to ask, since that is what the thing
       arriving has. */
    .ds-skeleton--block {
      inline-size: 100%;
      aspect-ratio: var(--ds-skeleton-ratio, 16 / 9);
      border-radius: var(--ds-radius-2);
    }

    /* An avatar, a mark. Square by construction, so it cannot come out an oval
       the way a percentage-radius rectangle does. */
    .ds-skeleton--circle {
      inline-size: var(--ds-skeleton-size, 1lh);
      block-size: var(--ds-skeleton-size, 1lh);
      flex: none;
      border-radius: var(--ds-radius-full);
    }

    /* ==========================================================================
       Swap: two marks in one slot, and the state chooses

       A control whose mark changes with what it has done. The eye that becomes a
       crossed eye, the copy that becomes a tick, the burger that becomes a cross.

       ── Why it exists ────────────────────────────────────────────────────────
       React writes those as one `if` in the component and swaps the element. The
       PHP renderers cannot: the marks are SVG baked into the markup, and the
       behaviour bundle is not allowed to write HTML, because a script that builds
       markup is a second renderer nobody compares. Decided 21 August 2026, and the
       precedent was already in the package: `Pagination` renders both the numbers
       and the Select and the stylesheet picks between them.

       So both marks are rendered, always, and this hides one. The cost is one
       extra `<span>` and one extra SVG in the markup of the controls that swap.
       What it buys is that the two renderers say the same thing, which is the
       whole promise of the system.

       ── What is here and what is not ─────────────────────────────────────────
       Here: that there are two, and that the second is the hidden one. Which STATE
       flips them is the component's own, written in the component's own file
       against the attribute that component already carries: `data-copied` on the
       snippet, `aria-pressed` on the password eye, `aria-expanded` on the nav
       mark. None of them is a new attribute. The state was already in the markup
       and was already the mechanism; this reads it.
       ========================================================================== */

    /* The layout first and the hiding after, or the order decides against the
       intent: two selectors of the same weight are settled by which is written
       later, and a `display` written second beats a `display: none` written
       first. This file had that bug for the length of one edit. */
    .ds-swap__off,
    .ds-swap__on {
      /* Not `block`: the slot these sit in is a flex line and the mark has to keep
         centring itself in it. */
      display: inline-flex;
      align-items: center;
      justify-content: center;
    }

    /* Shut by default, so a page with no script and no state shows the resting
       mark and never both. */
    .ds-swap__on {
      display: none;
    }

    /* ── Copied, the one state three components share ────────────────────────
       The snippet, the share block and the copy button each drew this rule
       against their own class, the same four lines three times. `data-copied` is
       worn by nothing else in the package and is written by both renderers and by
       the bundle, so the state is the selector: a third copy was the second use,
       and an escape hatch used twice is a defect in the middle (12 September
       2026). Which state flips a swap is still the component's own where the
       state is the component's own — `aria-pressed` on the password eye,
       `aria-expanded` on the nav mark. This one belongs to no component.

       A descendant and not a child: an icon button wraps what it is given in
       `.ds-button__icon`, so the mark is a grandchild of the element that carries
       the state. Written `>` first and caught by rendering the snippet. */
    [data-copied="true"] .ds-swap__off {
      display: none;
    }

    [data-copied="true"] .ds-swap__on {
      display: inline-flex;
    }

    /* ==========================================================================
       Snippet

       The CSS behind <Snippet>. Private, like every other component's.

       A block of code you can read and take. Wherever something is explained by
       showing what to type: documentation, an onboarding step, an API key, the
       command that fixes the error above it.

       ── It paints its own ground ──────────────────────────────────────────────
       Surface 2, always, whatever it is sitting on. The syntax palette is measured
       against that one colour and holds 4.5:1 there in both themes; let the ground
       change with the page and the colours stop being answerable to anything.
       ========================================================================== */

    .ds-snippet {
      position: relative;
      display: flex;
      flex-direction: column;
      background-color: var(--ds-surface-2);
      border: 1px solid var(--ds-border);
      border-radius: var(--ds-radius-3);
      /* The corners cut the code, which would otherwise reach into them on a long
         line. */
      overflow: hidden;
    }

    /* What the code is: a file path, a shell, a language. Sits above the code on
       its own line rather than floating in the corner, because a name and a copy
       button in the same corner is two things fighting for it. */
    .ds-snippet__name {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-snippet-name-tracking: var(--ds-font-mono-tracking);
      --ds-snippet-name-size: var(--ds-font-mono-size);
      --ds-snippet-name-weight: var(--ds-font-mono-weight);
      --ds-snippet-name-leading: var(--ds-font-mono-leading);
      display: flex;
      align-items: center;
      gap: var(--ds-space-2);
      padding: var(--ds-space-2) var(--ds-space-4);
      border-block-end: 1px solid var(--ds-border-subtle);
      --ds-snippet-name-face: var(--ds-font-mono-face);
      font-family: var(--ds-snippet-name-face);
      font-size: var(--ds-snippet-name-size);
      letter-spacing: var(--ds-snippet-name-tracking);
      font-weight: var(--ds-snippet-name-weight);
      line-height: var(--ds-snippet-name-leading);
      --ds-snippet-name-ink: var(--ds-text-secondary-neutral);
      color: var(--ds-snippet-name-ink);
    }

    .ds-snippet__scroll {
      /* A long line scrolls inside the block. Wrapping code changes what it says:
         a shell command on two lines reads as two commands. */
      overflow-x: auto;
      padding: var(--ds-space-4);
      /* Room for the copy button, so a first line that runs long slides under it
         rather than into it. */
      padding-inline-end: var(--ds-space-12);
    }

    .ds-snippet__code {
      --ds-snippet-code-tracking: var(--ds-font-mono-tracking);
      --ds-snippet-code-size: var(--ds-font-mono-size);
      --ds-snippet-code-weight: var(--ds-font-mono-weight);
      display: block;
      margin: 0;
      --ds-snippet-code-face: var(--ds-font-mono-face);
      font-family: var(--ds-snippet-code-face);
      font-size: var(--ds-snippet-code-size);
      letter-spacing: var(--ds-snippet-code-tracking);
      font-weight: var(--ds-snippet-code-weight);
      /* 20 on 12, a multiple of 4 like the rest of the ladder. Code is read line
         by line and wants more air between lines than a paragraph does. */
      line-height: var(--ds-space-5);
      --ds-snippet-code-ink: var(--ds-text-primary-neutral);
      color: var(--ds-snippet-code-ink);
      /* Tabs as four columns rather than the browser's eight, which is a width
         nothing has been written to since terminals had 80 of them. */
      tab-size: 4;
      white-space: pre;
    }

    /* ── The colours ──────────────────────────────────────────────────────────
       Four kinds and everything else in the reading colour. What is left plain is
       deliberate: punctuation and ordinary identifiers are the majority of any
       snippet, and colouring the majority is how a code block turns into a
       fruit salad nobody can scan. */
    .ds-snippet__tok--comment {  --ds-snippet-tok--comment-ink: var(--ds-text-tertiary-neutral);
      color: var(--ds-snippet-tok--comment-ink);}
    .ds-snippet__tok--string { color: var(--ds-syntax-string); }
    .ds-snippet__tok--keyword { color: var(--ds-syntax-keyword); }
    .ds-snippet__tok--number { color: var(--ds-syntax-number); }
    .ds-snippet__tok--name { color: var(--ds-syntax-name); }

    /* ── Copy ─────────────────────────────────────────────────────────────────
       Top right, over the code, and quiet until you are near it. A button that is
       always at full strength competes with the thing it is attached to; one that
       only appears on hover cannot be found by anyone who is not using a mouse,
       so it is dimmed rather than hidden and comes up on hover, on focus, and
       whenever the block is touched. */
    .ds-snippet__copy {
      position: absolute;
      inset-block-start: var(--ds-space-2);
      inset-inline-end: var(--ds-space-2);
      opacity: 0.6;
      transition: opacity var(--ds-duration) var(--ds-ease);
    }

    .ds-snippet:hover .ds-snippet__copy,
    .ds-snippet__copy:focus-visible,
    .ds-snippet__copy[data-copied="true"] {
      opacity: 1;
    }

    /* With a name above it the button sits in that strip instead, which is 32px
       tall and exactly the room a small button needs. */
    .ds-snippet--named .ds-snippet__copy {
      inset-block-start: 0;
    }

    /* ==========================================================================
       Progress

       The CSS behind <Progress>. Not public: nothing outside this package types
       these names, and they are absent from the exports map for that reason.

       One component and three shapes, because they answer the same question and
       differ only in how much room they have to answer it.

         bar     the default. A share of a whole, along a line.
         ring    the same share, in the space of an icon.
         steps   a countable few, where "3 of 5" is the answer and 60% is not.

       ── Known and unknown ─────────────────────────────────────────────────────
       With a value it fills. Without one it sweeps, because a bar that sits at
       zero while something is happening reads as a bar that is stuck.

       The sweep takes its duration from the motion tokens, which go to zero under
       a reduced-motion preference. At zero the animation does not run and the
       element keeps its resting style, which is a short bar parked in the middle:
       still visibly not-a-value, and still.
       ========================================================================== */

    .ds-progress {
      display: flex;
      flex-direction: column;
      gap: var(--ds-rhythm-line);
      min-inline-size: 0;
    }

    /* The row above the bar: what it is on the left, where it is on the right.
       Both optional, and the row disappears with them. */
    .ds-progress__head {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-progress-head-face: var(--ds-font-label-face);
      --ds-progress-head-tracking: var(--ds-font-label-tracking);
      --ds-progress-head-size: var(--ds-font-label-size);
      --ds-progress-head-weight: var(--ds-font-label-weight);
      --ds-progress-head-leading: var(--ds-font-label-leading);
      display: flex;
      align-items: baseline;
      justify-content: space-between;
      gap: var(--ds-space-4);
      font-family: var(--ds-progress-head-face);
      font-size: var(--ds-progress-head-size);
      letter-spacing: var(--ds-progress-head-tracking);
      font-weight: var(--ds-progress-head-weight);
      line-height: var(--ds-progress-head-leading);
    }

    .ds-progress__label {
      --ds-progress-label-ink: var(--ds-text-primary-neutral);
      color: var(--ds-progress-label-ink);
      min-inline-size: 0;
    }

    /* The bar and its number, on one line. The track takes what is left after
       the number, which is why the number is `flex: none` below. */
    .ds-progress__line {
      display: flex;
      align-items: center;
      gap: var(--ds-space-3);
    }

    .ds-progress__line .ds-progress__track {
      flex: 1;
    }

    .ds-progress__value {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-progress-value-face: var(--ds-font-label-face);
      --ds-progress-value-tracking: var(--ds-font-label-tracking);
      --ds-progress-value-size: var(--ds-font-label-size);
      --ds-progress-value-weight: var(--ds-font-label-weight);
      --ds-progress-value-leading: var(--ds-font-label-leading);
      font-family: var(--ds-progress-value-face);
      font-size: var(--ds-progress-value-size);
      letter-spacing: var(--ds-progress-value-tracking);
      font-weight: var(--ds-progress-value-weight);
      line-height: var(--ds-progress-value-leading);
      --ds-progress-value-ink: var(--ds-text-secondary-neutral);
      color: var(--ds-progress-value-ink);
      font-variant-numeric: tabular-nums;
      flex: none;
    }

    /* ── Tones ────────────────────────────────────────────────────────────────
       The fill is the tone's own colour and the track is a wash of the page, so
       the track reads the same under every tone and only the fill carries the
       meaning. */
    .ds-progress--brand { --ds-progress-fill: var(--ds-brand); }
    .ds-progress--success { --ds-progress-fill: var(--ds-success); }
    .ds-progress--warning { --ds-progress-fill: var(--ds-warning); }
    .ds-progress--danger { --ds-progress-fill: var(--ds-danger); }
    .ds-progress--neutral { --ds-progress-fill: var(--ds-inverse); }

    /* ── Bar ─────────────────────────────────────────────────────────────────── */
    /* A span is inline until it is told otherwise, and an inline box ignores every
       size you give it. Without the display below, the track was 0 by 0 with a
       fill 62% of nothing inside it: the value beside it read "62%" and the bar was
       not there, on every page it appeared on. */
    .ds-progress__track {
      display: block;
      position: relative;
      inline-size: 100%;
      block-size: var(--ds-progress-thickness);
      background-color: var(--ds-surface-active);
      border-radius: var(--ds-radius-full);
      overflow: hidden;
    }

    .ds-progress__fill {
      display: block;
      block-size: 100%;
      inline-size: var(--ds-progress-share, 0%);
      background-color: var(--ds-progress-fill);
      border-radius: inherit;
      transition: inline-size var(--ds-duration) var(--ds-ease-out);
    }

    /* Unknown. Parked at rest, sweeping when motion is allowed. */
    .ds-progress--unknown .ds-progress__fill {
      inline-size: 40%;
      margin-inline-start: 30%;
      animation: ds-progress-sweep calc(var(--ds-duration-loop) * 2) var(--ds-ease-in-out) infinite;
    }

    @keyframes ds-progress-sweep {
      0% { margin-inline-start: -40%; }
      100% { margin-inline-start: 100%; }
    }

    .ds-progress--small { --ds-progress-thickness: var(--ds-space-1); }
    .ds-progress--medium { --ds-progress-thickness: var(--ds-space-2); }
    .ds-progress--large { --ds-progress-thickness: var(--ds-space-3); }

    /* ── Ring ─────────────────────────────────────────────────────────────────
       Two circles, one drawn over the other, and the share is a dash pattern
       rather than an arc: an arc has to be recomputed at every value and a dash
       offset is one number. */
    .ds-progress__ring {
      display: block;
      inline-size: var(--ds-progress-diameter);
      block-size: var(--ds-progress-diameter);
      transform: rotate(-90deg);
    }

    .ds-progress__ring-track {
      stroke: var(--ds-surface-active);
    }

    .ds-progress__ring-fill {
      stroke: var(--ds-progress-fill);
      stroke-linecap: round;
      transition: stroke-dashoffset var(--ds-duration) var(--ds-ease-out);
    }

    .ds-progress--unknown .ds-progress__ring {
      animation: ds-progress-turn var(--ds-duration-loop) linear infinite;
    }

    @keyframes ds-progress-turn {
      to { transform: rotate(270deg); }
    }

    .ds-progress--small .ds-progress__ring { --ds-progress-diameter: var(--ds-size-1); }
    .ds-progress--medium .ds-progress__ring { --ds-progress-diameter: var(--ds-size-3); }
    .ds-progress--large .ds-progress__ring { --ds-progress-diameter: var(--ds-size-5); }

    /* ── Steps ───────────────────────────────────────────────────────────────
       Countable, so it is counted. Five segments with a gap, and the ones behind
       you are filled: a percentage across five stages is a number nobody asked
       for. */
    .ds-progress__steps {
      display: flex;
      gap: var(--ds-space-1);
      inline-size: 100%;
    }

    .ds-progress__step {
      flex: 1;
      block-size: var(--ds-progress-thickness);
      background-color: var(--ds-surface-active);
      border-radius: var(--ds-radius-full);
      transition: background-color var(--ds-duration) var(--ds-ease-out);
    }

    .ds-progress__step--done {
      background-color: var(--ds-progress-fill);
    }

    /* ==========================================================================
       Steps

       The CSS behind <Steps>. Not public: nothing outside this package types
       these names, and they are absent from the exports map for that reason.

       The stepper a wizard wears: numbered markers, a tick for every station
       behind the reader, and the line that joins them. A `Progress` answers
       "how much"; this answers "where am I, and what were the stations", which
       is why every marker is drawn rather than summed into a bar.

       The connector belongs to the item that FOLLOWS a station, drawn to its
       left, so the walked part of the line is filled exactly up to where the
       reader stands: item N's line is lit when item N is done or current,
       because reaching N is what lit it.
       ========================================================================== */

    .ds-steps {
      /* This component's own, pointed at the role. The number in the marker and
         the name under it are both labels: what a thing is called, at the small
         size a station can afford. */
      --ds-steps-face: var(--ds-font-label-face);
      --ds-steps-size: var(--ds-font-small-size);
      --ds-steps-weight: var(--ds-font-label-weight);
      --ds-steps-leading: var(--ds-font-small-leading);
      --ds-steps-tracking: var(--ds-font-small-tracking);

      /* The geometry. The marker is the smallest target WCAG allows, because a
         station is a landmark rather than a control: nothing here is pressed. */
      --ds-steps-marker: var(--ds-size-1);
      --ds-steps-line: 2px;
      --ds-steps-gap: var(--ds-space-2);

      /* The two inks: the walked part of the road and the part ahead. */
      --ds-steps-fill: var(--ds-brand);
      --ds-steps-on-fill: var(--ds-on-brand);
      --ds-steps-rest: var(--ds-border);
      --ds-steps-ink: var(--ds-text-primary-neutral);
      --ds-steps-ink-rest: var(--ds-text-secondary-neutral);

      display: flex;
      align-items: flex-start;
      margin: 0;
      padding: 0;
      list-style: none;
      font-family: var(--ds-steps-face);
      font-size: var(--ds-steps-size);
      font-weight: var(--ds-steps-weight);
      line-height: var(--ds-steps-leading);
      letter-spacing: var(--ds-steps-tracking);
    }

    /* Every item shares the row equally, so the markers land on a grid the eye
       can count, whatever the labels weigh. The column centres marker over
       label. */
    .ds-steps__item {
      position: relative;
      flex: 1;
      display: flex;
      flex-direction: column;
      align-items: center;
      gap: var(--ds-steps-gap);
      min-inline-size: 0;
      color: var(--ds-steps-ink-rest);
    }

    /* The connector, on every item but the first, reaching back to the marker
       before it: from this item's centre minus half a marker, back across the
       gap. It sits at the marker's mid-height. */
    .ds-steps__item + .ds-steps__item::before {
      content: "";
      position: absolute;
      inset-block-start: calc((var(--ds-steps-marker) - var(--ds-steps-line)) / 2);
      inset-inline-end: calc(50% + var(--ds-steps-marker) / 2 + var(--ds-steps-gap));
      inline-size: calc(100% - var(--ds-steps-marker) - var(--ds-steps-gap) * 2);
      block-size: var(--ds-steps-line);
      border-radius: var(--ds-radius-full);
      background-color: var(--ds-steps-rest);
    }

    .ds-steps__item--done + .ds-steps__item::before,
    .ds-steps__item--current::before {
      background-color: var(--ds-steps-fill);
    }

    /* The marker: a circle holding the number, or the tick once the station is
       behind. Ahead it is an outline; the one you stand on and the ones done
       take the fill. */
    .ds-steps__marker {
      display: inline-flex;
      align-items: center;
      justify-content: center;
      inline-size: var(--ds-steps-marker);
      block-size: var(--ds-steps-marker);
      border-radius: var(--ds-radius-full);
      border: var(--ds-steps-line) solid var(--ds-steps-rest);
      background-color: transparent;
      transition:
        background-color var(--ds-duration) var(--ds-ease-out),
        border-color var(--ds-duration) var(--ds-ease-out),
        color var(--ds-duration) var(--ds-ease-out);
    }

    .ds-steps__marker > svg {
      inline-size: var(--ds-steps-size);
      block-size: var(--ds-steps-size);
    }

    .ds-steps__item--done .ds-steps__marker,
    .ds-steps__item--current .ds-steps__marker {
      border-color: var(--ds-steps-fill);
      background-color: var(--ds-steps-fill);
      color: var(--ds-steps-on-fill);
    }

    /* The name under the current station carries the reading ink and the
       emphasis weight, because it is the answer to "where am I". */
    .ds-steps__label {
      max-inline-size: 100%;
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
      text-align: center;
    }

    .ds-steps__item--current {
      color: var(--ds-steps-ink);
    }

    .ds-steps__item--current .ds-steps__label {
      font-weight: var(--ds-emphasis-weight);
    }

    /* ==========================================================================
       Timeline

       The CSS behind <Timeline>. Not public: nothing outside this package types
       these names, and they are absent from the exports map for that reason.

       Ordered events down a line: what happened, in the order it happened. The
       neighbours answer different questions and that is why this exists beside
       them. `Steps` says where in a fixed set of stations the reader stands, so
       every station is drawn and the line fills up to one of them. `Agenda` says
       what is coming, grouped by day, so the rail carries a date and the rows
       under it are one day's worth. This has no count known in advance, no end,
       and one entry per moment.

       Three columns in the rail arrangement, two inline, and ONE DOM: the time,
       the marker and the body are written in that order either way and the grid
       decides where they land. A component whose markup changes with a variant is
       a component the contract compares twice and a reader has to read twice.
       ========================================================================== */

    .ds-timeline {
      list-style: none;
      margin: 0;
      padding: 0;
      display: block;

      /* The dot, and the room the line needs around it. Declared here so the
         connector below can be placed off the same number instead of a second
         one that drifts from it. */
      --ds-timeline-dot: 0.625rem;   /* 10 */
      --ds-timeline-lane: 1.25rem;   /* 20: the dot with air either side */
      --ds-timeline-mark: 0.875rem;  /* 14: a mark inside the lane, with air left */
      --ds-timeline-gap: var(--ds-space-3);
      /* The room between one entry and the next, and how far the line stops short
         of a dot at either end. Named because the connector is placed off both:
         a second number written into the calc drifts from this one the first time
         somebody changes the spacing. */
      --ds-timeline-step: var(--ds-space-4);
      --ds-timeline-clear: var(--ds-space-1);
      --ds-timeline-colour: var(--ds-border-strong);
      --ds-timeline-line: var(--ds-border);

      /* This component's own, pointed at the roles. The title is body because an
         entry is a sentence somebody reads; the time and the detail are small
         because they are what the sentence is filed under. */
      --ds-timeline-title-face: var(--ds-font-body-face);
      --ds-timeline-title-size: var(--ds-font-body-size);
      --ds-timeline-title-leading: var(--ds-font-body-leading);
      --ds-timeline-small-face: var(--ds-font-small-face);
      --ds-timeline-small-size: var(--ds-font-small-size);
      --ds-timeline-small-leading: var(--ds-font-small-leading);

      --ds-timeline-ink: var(--ds-text-primary-neutral);
      --ds-timeline-ink-detail: var(--ds-text-secondary-neutral);
      --ds-timeline-ink-time: var(--ds-text-tertiary-neutral);
      --ds-timeline-ink-hover: var(--ds-interactive);
    }

    .ds-timeline__entry {
      display: grid;
      column-gap: var(--ds-timeline-gap);
      /* The row below a marker is the connector's, so an entry with content still
         has the line running past it to the next. */
      padding-block-end: var(--ds-timeline-step);
      position: relative;
    }

    .ds-timeline__entry:last-child {
      padding-block-end: 0;
    }

    /* ── Where the moment sits ────────────────────────────────────────────────
       Rail: a column of its own, ranged right against the line, which is what a
       reader scanning a log for a time needs. Inline: over the title, for a page
       with long entries and few of them.

       `minmax(0, 1fr)` on the body and not `1fr`: a grid item's default minimum
       is its content, so one long unbroken word in an entry pushes the column
       wider than the track and the whole line scrolls sideways. */
    .ds-timeline--rail .ds-timeline__entry {
      grid-template-columns: auto var(--ds-timeline-lane) minmax(0, 1fr);
      /* Declared, so `1 / -1` on the marker has a last line to reach. Without an
         explicit row `-1` resolves to the first one and the marker stays as tall
         as itself: measured 17 September 2026 at 20px against 46px of gap, which
         drew the line as a stub under each dot instead of a line. */
      grid-template-rows: auto;
    }

    .ds-timeline--rail .ds-timeline__time {
      grid-column: 1;
      grid-row: 1;
      text-align: end;
      padding-block-start: 0.125rem;
    }

    .ds-timeline--rail .ds-timeline__marker {
      grid-column: 2;
      grid-row: 1 / -1;
    }

    .ds-timeline--rail .ds-timeline__body {
      grid-column: 3;
      grid-row: 1 / -1;
    }

    .ds-timeline--inline .ds-timeline__entry {
      grid-template-columns: var(--ds-timeline-lane) minmax(0, 1fr);
      /* Two, because inline puts the moment over the title: the marker spans both. */
      grid-template-rows: auto auto;
    }

    .ds-timeline--inline .ds-timeline__marker {
      grid-column: 1;
      grid-row: 1 / -1;
    }

    .ds-timeline--inline .ds-timeline__time {
      grid-column: 2;
      grid-row: 1;
    }

    .ds-timeline--inline .ds-timeline__body {
      grid-column: 2;
    }

    /* ── The marker, and the line it carries ──────────────────────────────────
       The connector belongs to the marker and is drawn from under the dot to the
       bottom of the entry, so it is exactly as long as the entry is tall however
       much that entry carries. The last one draws none: a line running off the
       end into nothing says there is more below when there is not. */
    .ds-timeline__marker {
      position: relative;
      inline-size: var(--ds-timeline-lane);
      /* No block size: the marker stretches to its grid row, which is what gives
         the connector something to run down. The dot keeps its own lane-tall cell
         at the top, so it sits level with the first line of the entry however tall
         the entry is. */
      display: grid;
      grid-template-rows: var(--ds-timeline-lane);
      justify-items: center;
      align-items: center;
      color: var(--ds-timeline-colour);
    }

    .ds-timeline__marker::before {
      content: "";
      display: block;
      inline-size: var(--ds-timeline-dot);
      block-size: var(--ds-timeline-dot);
      border-radius: var(--ds-radius-full);
      background-color: currentcolor;
    }

    /* A mark inside the marker takes the dot's place: the dot says "something
       happened here" and a mark says which kind, so both at once says one of them
       twice. */
    .ds-timeline__marker:has(svg)::before {
      content: none;
    }

    .ds-timeline__marker svg {
      inline-size: var(--ds-timeline-mark);
      block-size: var(--ds-timeline-mark);
    }

    .ds-timeline__entry:not(:last-child) .ds-timeline__marker::after {
      content: "";
      position: absolute;
      /* From clear of the dot to clear of the NEXT dot: the marker ends where its
         row does, and the next dot's centre is a step plus half a lane below that,
         so the line has to run past the entry's own bottom to reach it. */
      inset-block-start: calc((var(--ds-timeline-lane) / 2) + (var(--ds-timeline-dot) / 2) + var(--ds-timeline-clear));
      inset-block-end: calc(-1 * (var(--ds-timeline-step) + (var(--ds-timeline-lane) / 2) - (var(--ds-timeline-dot) / 2) - var(--ds-timeline-clear)));
      inset-inline-start: calc(50% - 0.5px);
      inline-size: 1px;
      background-color: var(--ds-timeline-line);
    }

    /* ── The words ────────────────────────────────────────────────────────── */

    .ds-timeline__body {
      display: flex;
      flex-direction: column;
      gap: var(--ds-space-1);
      min-inline-size: 0;
    }

    .ds-timeline__time {
      font-family: var(--ds-timeline-small-face);
      font-size: var(--ds-timeline-small-size);
      line-height: var(--ds-timeline-small-leading);
      color: var(--ds-timeline-ink-time);
      /* The times line up under each other whatever digits they carry, which is
         the whole reason a rail is readable at a glance. */
      font-variant-numeric: tabular-nums;
      white-space: nowrap;
    }

    .ds-timeline__title {
      font-family: var(--ds-timeline-title-face);
      font-size: var(--ds-timeline-title-size);
      line-height: var(--ds-timeline-title-leading);
      color: var(--ds-timeline-ink);
    }

    /* ── The title that opens ─────────────────────────────────────────────────
       The title IS the control, not a caret beside it: a log line is a sentence,
       and a reader who wants to see what changed presses the sentence. The button
       is reset to the words it holds, keeps the row's alignment, and puts the
       chevron at the end of the line rather than at the end of the column, so a
       long title and a short one both have it against their last word. */
    button.ds-timeline__title {
      appearance: none;
      background: none;
      border: 0;
      padding: 0;
      margin: 0;
      font: inherit;
      /* E il colore, dichiarato: `font` e' una scorciatoia che il colore non lo
         porta, e un `<button>` parte dal `buttontext` del browser. Misurato sul
         consumer il 17 settembre 2026: in tema chiaro il titolo usciva #3e3d46
         dove la stessa classe su uno `<span>` usciva #0a0a0d. `a.ds-timeline__title`
         lo ridichiarava gia' e questo no. */
      color: var(--ds-timeline-ink);
      text-align: start;
      cursor: pointer;
      display: inline-flex;
      align-items: center;
      gap: var(--ds-space-1);
      align-self: start;
      transition: color var(--ds-duration) var(--ds-ease);
    }

    button.ds-timeline__title:hover {
      color: var(--ds-timeline-ink-hover);
    }

    .ds-timeline__chevron {
      display: inline-grid;
      place-items: center;
      color: var(--ds-timeline-ink-time);
      transition: transform var(--ds-duration) var(--ds-ease);
    }

    .ds-timeline__chevron svg {
      inline-size: var(--ds-timeline-mark);
      block-size: var(--ds-timeline-mark);
    }

    button.ds-timeline__title[aria-expanded="true"] .ds-timeline__chevron {
      transform: rotate(180deg);
    }

    a.ds-timeline__title {
      text-decoration: none;
      color: var(--ds-timeline-ink);
      transition: color var(--ds-duration) var(--ds-ease);
    }

    a.ds-timeline__title:hover {
      color: var(--ds-timeline-ink-hover);
    }

    .ds-timeline__detail {
      font-family: var(--ds-timeline-small-face);
      font-size: var(--ds-timeline-small-size);
      line-height: var(--ds-timeline-small-leading);
      color: var(--ds-timeline-ink-detail);
    }

    .ds-timeline__content {
      margin-block-start: var(--ds-space-2);
      min-inline-size: 0;
    }

    /* ── The twelve hues ──────────────────────────────────────────────────────
       The marker's colour only, and never the words: a coloured dot says which
       kind of thing happened and a coloured sentence says the sentence is
       special, which is not what is meant. */
    .ds-timeline__entry--hue-red .ds-timeline__marker { --ds-timeline-colour: var(--ds-hue-red); }
    .ds-timeline__entry--hue-orange .ds-timeline__marker { --ds-timeline-colour: var(--ds-hue-orange); }
    .ds-timeline__entry--hue-amber .ds-timeline__marker { --ds-timeline-colour: var(--ds-hue-amber); }
    .ds-timeline__entry--hue-yellow .ds-timeline__marker { --ds-timeline-colour: var(--ds-hue-yellow); }
    .ds-timeline__entry--hue-lime .ds-timeline__marker { --ds-timeline-colour: var(--ds-hue-lime); }
    .ds-timeline__entry--hue-green .ds-timeline__marker { --ds-timeline-colour: var(--ds-hue-green); }
    .ds-timeline__entry--hue-teal .ds-timeline__marker { --ds-timeline-colour: var(--ds-hue-teal); }
    .ds-timeline__entry--hue-cyan .ds-timeline__marker { --ds-timeline-colour: var(--ds-hue-cyan); }
    .ds-timeline__entry--hue-blue .ds-timeline__marker { --ds-timeline-colour: var(--ds-hue-blue); }
    .ds-timeline__entry--hue-indigo .ds-timeline__marker { --ds-timeline-colour: var(--ds-hue-indigo); }
    .ds-timeline__entry--hue-violet .ds-timeline__marker { --ds-timeline-colour: var(--ds-hue-violet); }
    .ds-timeline__entry--hue-pink .ds-timeline__marker { --ds-timeline-colour: var(--ds-hue-pink); }

    /* ==========================================================================
       Change

       The CSS behind <Change>. Not public: nothing outside this package types
       these names, and they are absent from the exports map for that reason.

       One value that changed: what it was, an arrow, what it became. It is the
       half of a log line that carries the values, and `Timeline` is the half that
       carries the order; a line that needs both puts this in the entry's content.

       The old value is the quieter of the two and the new one carries the page's
       own ink, because "this is what it is now" is the thing being said. NEITHER
       SIDE IS GREEN OR RED: the arrow already says which way round they go, so a
       colour there would be decoration, and the estate's rule is that colour is a
       signal or it is nothing.
       ========================================================================== */

    .ds-change {
      display: inline-flex;
      flex-wrap: wrap;
      align-items: baseline;
      gap: var(--ds-space-2);
      min-inline-size: 0;

      --ds-change-face: var(--ds-font-small-face);
      --ds-change-size: var(--ds-font-small-size);
      --ds-change-leading: var(--ds-font-small-leading);
      --ds-change-was: var(--ds-text-tertiary-neutral);
      --ds-change-now: var(--ds-text-primary-neutral);
      --ds-change-arrow: var(--ds-text-tertiary-neutral);

      font-family: var(--ds-change-face);
      font-size: var(--ds-change-size);
      line-height: var(--ds-change-leading);
    }

    .ds-change__was {
      color: var(--ds-change-was);
      min-inline-size: 0;
    }

    .ds-change__now {
      color: var(--ds-change-now);
      min-inline-size: 0;
    }

    /* A side with nothing in it. It reads as a word rather than a dash, because a
       dash beside a value reads as part of the value; and it is italic rather than
       another colour, since the two colours here already mean old and new. */
    .ds-change__was--empty,
    .ds-change__now--empty {
      font-style: italic;
      color: var(--ds-change-arrow);
    }

    .ds-change__arrow {
      color: var(--ds-change-arrow);
      /* It never ends a line on its own: an arrow alone at the end of a wrapped
         line points at the margin. */
      white-space: nowrap;
    }

    /* ==========================================================================
       Moment

       The CSS behind <Moment>. Not public: nothing outside this package types
       these names, and they are absent from the exports map for that reason.

       A `<time>` and almost nothing else. What it needs is the one thing a
       moment in a column needs: digits that line up under each other, so a rail
       of times reads at a glance instead of sawing left and right. The rest is
       whatever it sits in, because a moment is never the thing on the page: it is
       what the thing on the page is filed under.
       ========================================================================== */

    .ds-moment {
      font-variant-numeric: tabular-nums;
      /* The distance and the date are different lengths, and the text swaps from
         one to the other after the page has arrived. Without this a rail of times
         jumps sideways the moment the script runs. */
      white-space: nowrap;
    }

    /* A moment carrying its absolute reading under the pointer says so, the way
       anything with a title does. The cursor is the only sign there is, since a
       `<time>` is not a control and must not look like one. */
    .ds-moment[title] {
      cursor: help;
    }

    /* ==========================================================================
       Log

       The CSS behind <Log>. Not public: nothing outside this package types these
       names, and they are absent from the exports map for that reason.

       A desk's log: dense rows on one side, and the one you pressed opened on the
       other. THE ARRANGEMENT CAME FROM A DEFECT. The first shape put every event
       on a timeline with its changed values under it, and a changed value is
       sometimes an article: one line of a log grew to a screen of its own and the
       rest of the page went away under it. So a row is one line, always, whatever
       it carries, and what it carries is read in the panel beside it.

       The panel pushes the rows along instead of covering them, so a reader keeps
       their place in the list while reading one line of it.
       ========================================================================== */

    .ds-log {
      display: flex;
      flex-direction: column;
      gap: var(--ds-rhythm-block);
      /* Il contenitore che la domanda qui sotto misura: un componente non conosce
         la finestra, conosce se stesso. */
      container-type: inline-size;

      /* How narrow the rows may get before the panel stops sitting beside them.
         A name and a date and an address in the same line need about this much;
         under it the two stack, which is what a phone gets. */
      --ds-log-rows-min: 34rem;
      --ds-log-panel: 28rem;

      /* Le tre colonne dichiarate. La quarta non c'e' apposta: prende quel che
         resta, ed e' quella che dice di cosa si parla. */
      --ds-log-col-when: 9rem;
      --ds-log-col-what: 11rem;
      --ds-log-col-who: 12rem;

      /* This component's own, pointed at the roles.

         Le quattro colonne si leggono tutte a piena forza, e le due che non lo
         facevano sono state due difetti, non due scelte. L'elemento era
         `secondary` e l'utente `tertiary`, cioe' l'inchiostro di un suggerimento
         sotto un campo: chi ha fatto una cosa e' meta' di quel che un registro
         dice, e lo diceva quasi cancellato. Il proprietario, 18 settembre 2026.

         Il ragionamento sbagliato era «un attore si ripete lungo la pagina,
         quindi conta meno». In un registro non si scorre per leggere una volta
         sola: si scorre CERCANDO un nome, e un nome che si cerca non si sbiadisce
         perche' compare spesso. Il grado minore resta al segno accanto al verbo,
         che e' un ornamento e non un dato. */
      --ds-log-ink-what: var(--ds-text-primary-neutral);
      --ds-log-ink-who: var(--ds-text-primary-neutral);
      --ds-log-ink-mark: var(--ds-text-tertiary-neutral);
      --ds-log-title-face: var(--ds-font-label-face);
      --ds-log-title-size: var(--ds-font-label-size);
      --ds-log-title-weight: var(--ds-font-label-weight);
      --ds-log-title-leading: var(--ds-font-label-leading);
      --ds-log-title-ink: var(--ds-text-primary-neutral);
    }

    /* The bare strip over the EMPTY state only, since 18 September 2026: with
       rows the strip is inside the table's sheet, in its `toolbar`, and this
       selector finds nothing. The strip and what is under it belong together, so
       the gap between them is the one inside a block. */
    .ds-log .ds-toolbar + .ds-log__split {
      margin-block-start: calc(var(--ds-rhythm-element) - var(--ds-rhythm-block));
    }

    /* `--ds-grid-gap` e non solo `gap`, ed e' la ragione per cui questa riga ha un
       commento. Un cassetto in linea CHIUSO e' largo zero ma resta un elemento
       flex, quindi il passo della riga resta dietro di lui e la tabella comincia
       staccata dal bordo senza niente che lo spieghi. `drawer.css` se lo riprende
       con un margine negativo che legge `--ds-grid-gap`, e quella la scrive la
       riga. Questa non e' una `Row`, e' un flex suo: scritto solo `gap`, la
       variabile cadeva sul suo `0px` di riserva e il margine negativo valeva
       zero. Il proprietario, 18 settembre 2026: succede ogni volta che si mette
       un cassetto. `npm run check` lo chiede adesso, a ogni scatola che ne
       contiene uno. */
    .ds-log__split {
      display: flex;
      align-items: start;
      --ds-grid-gap: var(--ds-space-4);
      gap: var(--ds-grid-gap);
      min-inline-size: 0;
    }

    /* `min-inline-size: 0` and not `auto`: a flex item's floor is its content, and
       one long address in a cell would push the table wider than the page instead
       of being cut by it, which is the whole thing this shape is for. */
    .ds-log__rows {
      flex: 1 1 auto;
      min-inline-size: 0;
      display: flex;
      flex-direction: column;
      gap: var(--ds-rhythm-element);
    }

    /* Three names and not one: the filter panel is a `.ds-drawer--inline` and
       `drawer.css` hands the room that is left to every sibling after it that is
       not another inline drawer. This aside is not one, so at two names the two
       rules tie and the cascade decides by file order; at three it does not. The
       trap is written down in CLAUDE.md, measured 5 September 2026 on a row with a
       drawer at each end: the second panel took 408 pixels of the table's room. */
    .ds-log .ds-log__split > .ds-log__panel {
      flex: 0 0 var(--ds-log-panel);
      max-inline-size: 100%;
      min-inline-size: 0;
      background-color: var(--ds-surface-1);
      border: 1px solid var(--ds-border);
      border-radius: var(--ds-radius-3);
      overflow: hidden;
      position: sticky;
      /* Under whatever the product's own header is: the panel follows the reader
         down a long list instead of scrolling away from the row it is about. */
      inset-block-start: var(--ds-space-4);
    }

    /* Under `--ds-break-wide` the two cannot both have their room, so they stack
       and the panel comes first: somebody who pressed a row wants to read it, and
       the list is still under it. The width is one of the five the package
       declares and not a sixth invented here. A container query and not a media
       one, because a component does not know the window. */
    @container (width < 60rem) {
      .ds-log__split {
        flex-direction: column;
        /* E `stretch`, non l'`start` di sopra. In riga `start` tiene il pannello
           alto quanto il suo contenuto invece che quanto la lista; in colonna la
           stessa parola strizza tutt'e due sulla larghezza del loro contenuto, e
           una tabella che taglia le celle ha un contenuto largo zero. Misurato a
           686px: la lista veniva 277 dentro 686, con ogni cella ridotta a tre
           lettere e i puntini. */
        align-items: stretch;
      }

      .ds-log .ds-log__split > .ds-log__panel {
        flex: 1 1 auto;
        inline-size: 100%;
        position: static;
        order: -1;
      }

      /* And the filters, when they are open, take the width too: `inline-size` on
         the open panel is a fixed 20rem, which in a column is a narrow strip with
         the page beside it. Shut it keeps the zero `drawer.css` gives it.

         `auto` and not `100%`: a drawer is `content-box`, so a hundred per cent
         plus its own border measured 688 inside 686 and hung two pixels past the
         rows under it. Left to `auto` the column's own `stretch` sizes the BORDER
         box, which is the number that has to match. */
      .ds-log__split > .ds-drawer--inline[data-open="true"] {
        inline-size: auto;
        max-inline-size: 100%;
      }

      /* E chiuso, il passo che si riprende e' sull'altro asse: in colonna il
         margine inline di `drawer.css` non tocca niente e lo stacco restava
         sopra le righe. */
      .ds-log__split > .ds-drawer--inline[data-open="false"] {
        margin-inline: 0;
        margin-block: 0 calc(var(--ds-grid-gap, 0px) * -1);
      }
    }

    /* Three panes want a desktop, and under one there is no list left between
       them. Measured 18 September 2026 in a 1100px container with both open: the
       filters took 322, the event 450, and the four columns had 296 between them,
       which is four cells cut to three letters each. Above `--ds-break-desktop`
       the same three come to 804 and the rows keep 476.

       The renderer says when there are three, with `--three`, because it holds
       both states: a `:has()` asking the same question cost 1.3 kB of a stylesheet
       with 0.5 left in its budget.

       So under it the three stack, which is the arrangement the narrow case
       already uses: the event first, the filters and the list under it, each with
       the whole width. Nothing is hidden and nothing is taken away — a panel that
       vanished while its own button said it was open would be a button that does
       nothing, which is worse than a list that has moved. */
    @container (width < 80rem) {
      .ds-log__split--three {
        flex-direction: column;
        align-items: stretch;
      }

      .ds-log__split--three > .ds-log__panel {
        flex: 1 1 auto;
        inline-size: 100%;
        position: static;
        order: -1;
      }

      .ds-log__split--three > .ds-drawer--inline[data-open="true"] {
        inline-size: auto;
        max-inline-size: 100%;
      }
    }

    /* ── The cells ────────────────────────────────────────────────────────────
       Every one is a single line, cut where it runs out. That is the rule the
       whole shape rests on, so it is written once here and not per column.

       `fixed` perche' il taglio abbia un limite da cui tagliare: in `auto` la
       tabella allarga le colonne fino al contenuto e non taglia niente, che e'
       il contrario di quel che serve qui. */
    .ds-log .ds-table {
      table-layout: fixed;
      inline-size: 100%;
    }

    .ds-log .ds-table td,
    .ds-log .ds-table th {
      white-space: nowrap;
      overflow: hidden;
      text-overflow: ellipsis;
    }

    .ds-log__kind {
      display: inline-flex;
      align-items: center;
      gap: var(--ds-space-2);
      white-space: nowrap;
    }

    .ds-log__mark {
      display: inline-grid;
      place-items: center;
      inline-size: 1rem;
      block-size: 1rem;
      color: var(--ds-log-ink-mark);
    }

    .ds-log__mark svg {
      inline-size: 0.875rem;
      block-size: 0.875rem;
    }

    .ds-log__what {
      color: var(--ds-log-ink-what);
    }

    .ds-log__who {
      color: var(--ds-log-ink-who);
      white-space: nowrap;
    }

    /* ── The panel's own head ────────────────────────────────────────────────
       The name of what is open, and the way out. The way out is a SLOT and not a
       button of this component's: the panel is open because the address says so,
       so what closes it is whatever takes the address back, which only the
       product knows how to write. */
    .ds-log__panel-head {
      display: flex;
      align-items: center;
      justify-content: space-between;
      gap: var(--ds-space-3);
      padding: var(--ds-space-3) var(--ds-space-4);
      border-block-end: 1px solid var(--ds-border-subtle);
    }

    .ds-log__panel-title {
      font-family: var(--ds-log-title-face);
      font-size: var(--ds-log-title-size);
      font-weight: var(--ds-log-title-weight);
      line-height: var(--ds-log-title-leading);
      color: var(--ds-log-title-ink);
      /* One line: it names one event and a name that wraps pushes the way out
         off the row it belongs on. */
      white-space: nowrap;
      overflow: hidden;
      text-overflow: ellipsis;
    }

    /* What the reader came with, and the one way on from here: when, who, what it
       was about, then the act. Between the head and the body, and OUTSIDE the
       scroller under it, because both are the frame around the values rather than
       the values. The act stood at the foot of the body until 18 September 2026,
       so an event carrying a whole article put it a screen and a half down. */
    .ds-log__panel-summary {
      display: flex;
      flex-direction: column;
      align-items: start;
      gap: var(--ds-rhythm-element);
      padding: var(--ds-space-4);
      border-block-end: 1px solid var(--ds-border-subtle);
    }

    /* The panel is 28rem and a name beside its value wants the room: the pairs
       stack here rather than standing in two columns of nothing. */
    .ds-log__panel-summary .ds-description-list {
      inline-size: 100%;
    }

    .ds-log__panel-body {
      padding: var(--ds-space-4);
      /* Here the long values live, and here they may be long: this is the place
         the rows send them to. It scrolls on its own rather than stretching the
         page, so the list beside it keeps its height. */
      max-block-size: 60vh;
      overflow: auto;
    }

    /* ==========================================================================
       Settings

       The CSS behind <Settings>. Not public: nothing outside this package types
       these names, and they are absent from the exports map for that reason.

       The settings screen: a rail of sections on the start side, the current
       section's page beside it. The rail is LINKS, not tabs, because a settings
       page in a product is one route per section: the server holds which section
       this is and each form posts its own fields, exactly the wizard's ruling
       one screen over. A tablist here would put every unsaved form of every
       hidden panel behind one that is about to post.

       The rail marks where you are the way the sidebar does, `aria-current` on
       the link, because the two answer the same question one level apart and a
       reader should not have to learn two answers.
       ========================================================================== */

    .ds-settings {
      /* The rail's width is this component's own decision, as the sidebar's
         16rem is. 12rem: the longest name a settings rail carries in the wild is
         "Notifications", 13 characters, about 7rem at the label size, and 12
         leaves it room beside a mark with the padding on. The sidebar is wider
         because it carries a product; this carries one page's sections. */
      --ds-settings-rail: 12rem;

      /* The container, so the collapse asks this screen's own width and not the
         window's: this page is shown inside panels and device canvases. */
      container-type: inline-size;
      container-name: ds-settings;
    }

    .ds-settings__body {
      display: grid;
      grid-template-columns: var(--ds-settings-rail) minmax(0, 1fr);
      gap: var(--ds-rhythm-block);
      align-items: start;
    }

    .ds-settings__list {
      display: flex;
      flex-direction: column;
      gap: var(--ds-space-1);
      margin: 0;
      padding: 0;
      list-style: none;
    }

    .ds-settings__link {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-settings-link-face: var(--ds-font-label-face);
      --ds-settings-link-tracking: var(--ds-font-label-tracking);
      --ds-settings-link-size: var(--ds-font-label-size);
      --ds-settings-link-leading: var(--ds-font-label-leading);
      --ds-settings-link-weight: var(--ds-font-label-weight);
      --ds-settings-link-ink: var(--ds-text-secondary-neutral);
      display: flex;
      align-items: center;
      gap: var(--ds-space-2);
      padding: var(--ds-space-1) var(--ds-space-2);
      box-sizing: border-box;
      min-block-size: var(--ds-size-2);
      border-radius: var(--ds-radius-2);
      font-family: var(--ds-settings-link-face);
      font-size: var(--ds-settings-link-size);
      letter-spacing: var(--ds-settings-link-tracking);
      line-height: var(--ds-settings-link-leading);
      font-weight: var(--ds-settings-link-weight);
      color: var(--ds-settings-link-ink);
      text-decoration: none;
      transition:
        background-color var(--ds-duration) var(--ds-ease),
        color var(--ds-duration) var(--ds-ease);
    }

    .ds-settings__link:hover {
      --ds-settings-link-ink: var(--ds-text-primary-neutral);
      color: var(--ds-settings-link-ink);
      background-color: var(--ds-surface-hover);
    }

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

    .ds-settings__link[aria-current] {
      --ds-settings-link-weight-current: var(--ds-current-weight);
      --ds-settings-link-ink: var(--ds-text-primary-neutral);
      background-color: var(--ds-surface-active);
      color: var(--ds-settings-link-ink);
      font-weight: var(--ds-settings-link-weight-current);
    }

    .ds-settings__mark {
      display: inline-flex;
      flex: none;
      inline-size: 1lh;
      block-size: 1lh;
    }

    .ds-settings__mark > svg {
      inline-size: 100%;
      block-size: 100%;
    }

    .ds-settings__page {
      min-inline-size: 0;
    }

    /* Narrow, the rail lies down: a row of the same links across the top,
       scrolling sideways, the page under it. The phone break, because that is
       where a fixed rail plus a form stops leaving the form its floor. */
    @container ds-settings (width <= 40rem) {
      .ds-settings__body {
        grid-template-columns: minmax(0, 1fr);
      }

      .ds-settings__list {
        flex-direction: row;
        gap: var(--ds-space-1);
        overflow-x: auto;
      }

      .ds-settings__link {
        white-space: nowrap;
      }
    }

    /* ==========================================================================
       Error screen

       The CSS behind <ErrorScreen>. Not public: nothing outside this package
       types these names, and they are absent from the exports map for that
       reason.

       The five pages a site shows when it cannot show the page: 404, 403, 500,
       offline, maintenance. One drawing for all five, because they are the same
       moment for the reader, and the code is the only part three of them carry.
       Centred, because the page's content IS the message and the room around it
       is what says "this is not the site, this is the site apologising".
       ========================================================================== */

    .ds-error {
      display: grid;
      place-items: center;
      min-block-size: 100%;
      padding: var(--ds-rhythm-block) var(--ds-space-6);
      text-align: center;
    }

    .ds-error__card {
      display: flex;
      flex-direction: column;
      align-items: center;
      gap: var(--ds-rhythm-line);
      max-inline-size: 36rem;
    }

    .ds-error__brand {
      margin-block-end: var(--ds-rhythm-element);
    }

    /* The code, when the moment has one. Display type at the second heading's
       size, in the tertiary ink: it is a fact and not the sentence, so it stands
       over the title the way an eyebrow does, quieter than what it introduces. */
    .ds-error__code {
      --ds-error-code-face: var(--ds-font-heading-2-face);
      --ds-error-code-size: var(--ds-font-heading-2-size);
      --ds-error-code-weight: var(--ds-font-heading-2-weight);
      --ds-error-code-leading: var(--ds-font-heading-2-leading);
      --ds-error-code-tracking: var(--ds-font-heading-2-tracking);
      --ds-error-code-ink: var(--ds-text-tertiary-neutral);
      margin: 0;
      font-family: var(--ds-error-code-face);
      font-size: var(--ds-error-code-size);
      font-weight: var(--ds-error-code-weight);
      line-height: var(--ds-error-code-leading);
      letter-spacing: var(--ds-error-code-tracking);
      color: var(--ds-error-code-ink);
    }

    .ds-error__title {
      --ds-error-title-face: var(--ds-font-heading-3-face);
      --ds-error-title-size: var(--ds-font-heading-3-size);
      --ds-error-title-weight: var(--ds-font-heading-3-weight);
      --ds-error-title-leading: var(--ds-font-heading-3-leading);
      --ds-error-title-tracking: var(--ds-font-heading-3-tracking);
      margin: 0;
      font-family: var(--ds-error-title-face);
      font-size: var(--ds-error-title-size);
      font-weight: var(--ds-error-title-weight);
      line-height: var(--ds-error-title-leading);
      letter-spacing: var(--ds-error-title-tracking);
      text-wrap: balance;
    }

    .ds-error__lede {
      --ds-error-lede-face: var(--ds-font-body-face);
      --ds-error-lede-size: var(--ds-font-body-size);
      --ds-error-lede-weight: var(--ds-font-body-weight);
      --ds-error-lede-leading: var(--ds-font-body-leading);
      --ds-error-lede-tracking: var(--ds-font-body-tracking);
      --ds-error-lede-ink: var(--ds-text-secondary-neutral);
      margin: 0;
      font-family: var(--ds-error-lede-face);
      font-size: var(--ds-error-lede-size);
      font-weight: var(--ds-error-lede-weight);
      line-height: var(--ds-error-lede-leading);
      letter-spacing: var(--ds-error-lede-tracking);
      color: var(--ds-error-lede-ink);
      max-inline-size: 48ch;
    }

    .ds-error__actions {
      margin-block-start: var(--ds-rhythm-element);
    }

    /* ==========================================================================
       Phone field

       The CSS behind <PhoneField>. Private, like every other component's.

       ── Two controls, one answer, one label ───────────────────────────────────
       The dialling code and the number are useless apart, so they stand together
       under one name and share one message. They keep separate outlines because
       they are separate targets: one opens a list, the other takes typing, and a
       single outline around both says they are one control that they are not.

       ── The picker is the system's Select ─────────────────────────────────────
       Not a native one. A platform menu inside a form of ours is the one control
       on the screen that belongs to somebody else, and Select already carries the
       search a two-hundred-country list needs.

       It shows the dialling code once closed and the country's whole name while
       open: the list is being searched, the control is being glanced at.
       ========================================================================== */

    .ds-phone-field__row {
      display: flex;
      align-items: start;
      gap: var(--ds-space-2);
      min-inline-size: 0;
    }

    /* As wide as the code it holds and no wider. The number is what the reader is
       typing, so it takes the room. */
    .ds-phone-field__country {
      flex: none;
      inline-size: auto;
    }

    .ds-phone-field__country .ds-field__frame {
      inline-size: auto;
    }

    .ds-phone-field__number {
      flex: 1 1 auto;
      min-inline-size: 0;
    }

    /* ==========================================================================
       Popover

       The panel that opens against a control, and the lines inside it. Shared, on
       purpose: Menu and Select are one mechanism and two meanings, so they are one
       skin and two sets of roles. A second panel written next to this one is a
       second set of paddings to keep in step, and they never stay in step.

       ── It lives in the top layer ─────────────────────────────────────────────
       The panel is a native popover, so the browser puts it above everything,
       closes it on Escape and on a click outside, and none of that is code here.
       What the browser does not do is put it anywhere useful: a popover opens in
       the middle of the screen until something positions it, which usePopover
       does against the control that opened it.

       ── No shadow ────────────────────────────────────────────────────────────
       This system has no elevation and this is not the place to invent one. The
       panel is told apart from the page by a border and a surface, the same way
       every other raised thing here is.
       ========================================================================== */

    .ds-popover {
      /* Border and padding inside the width, so `matchWidth` can hand it the
         field's own measurement and get a panel exactly that wide. */
      box-sizing: border-box;
      /* The user agent centres a popover and gives it a margin. Both go: the
         position comes from the trigger. */
      position: fixed;
      inset: auto;
      margin: 0;
      z-index: 1;
      display: flex;
      flex-direction: column;
      gap: 1px;
      min-inline-size: 12rem;
      /* Never taller than the room there is. A menu that runs off the bottom of
         the screen has items nobody can reach. */
      max-block-size: min(24rem, calc(100dvh - var(--ds-space-8)));
      overflow-y: auto;
      padding: var(--ds-space-1);
      border: 1px solid var(--ds-border);
      border-radius: var(--ds-radius-3);
      background-color: var(--ds-surface-1);
      --ds-popover-ink: var(--ds-text-primary-neutral);
      color: var(--ds-popover-ink);
    }

    /* A panel that holds content rather than lines: a small form, a calendar, a
       note. The lines' padding is a rung too small for a field to sit in, and a
       run of fields wants no hairline between them, which is the lines' gap. */
    .ds-popover--dialog {
      gap: 0;
      padding: var(--ds-space-3);
    }

    /* Closed is closed: without this the panel is in the flow of the page until
       the first time it opens. */
    .ds-popover:not(:popover-open) {
      display: none;
    }

    .ds-popover__item {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-popover-item-face: var(--ds-font-label-face);
      --ds-popover-item-tracking: var(--ds-font-label-tracking);
      --ds-popover-item-size: var(--ds-font-label-size);
      --ds-popover-item-weight: var(--ds-font-label-weight);
      --ds-popover-item-leading: var(--ds-font-label-leading);
      display: flex;
      align-items: center;
      gap: var(--ds-space-3);
      /* Its own tier of the control ramp, because a menu item is a target: 32px
         tall, which is the second tier and what a dense list of them wants. */
      min-block-size: var(--ds-size-2);
      inline-size: 100%;
      padding-inline: var(--ds-space-2);
      border: 0;
      border-radius: var(--ds-radius-2);
      background: none;
      font-family: var(--ds-popover-item-face);
      font-size: var(--ds-popover-item-size);
      letter-spacing: var(--ds-popover-item-tracking);
      font-weight: var(--ds-popover-item-weight);
      line-height: var(--ds-popover-item-leading);
      color: inherit;
      text-align: start;
      /* A line here can be a link — the language switcher's are, since every
         language is a different page — and a link in this system is underlined.
         Inside a panel the row is the target and the underline is noise. */
      text-decoration: none;
      cursor: pointer;
      transition: background-color var(--ds-duration) var(--ds-ease);
    }

    /* The row wash, the same one a table row takes, because this is the same
       gesture: pointing at one line of a list. */
    .ds-popover__item:hover {
      background-color: var(--ds-surface-hover);
    }

    /* Focus moves with the arrow keys as well as the pointer, so the item under
       the keyboard is filled rather than ringed: a ring inside a panel this tight
       sits on top of its neighbours. */
    .ds-popover__item:focus {
      outline: none;
      background-color: var(--ds-surface-active);
    }

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

    .ds-popover__item[aria-disabled="true"] {
      --ds-popover-item-ink: var(--ds-text-disabled);
      color: var(--ds-popover-item-ink);
      cursor: default;
    }

    .ds-popover__item[aria-disabled="true"]:hover {
      background-color: transparent;
    }

    /* Danger is the label and the icon, not the ground. A red row in a list of
       grey ones reads as already-happened; the point is that it has not happened
       yet. */
    .ds-popover__item--danger {
      color: var(--ds-danger-ink);
    }

    .ds-popover__icon,
    .ds-popover__check {
      display: flex;
      flex: none;
      inline-size: 1lh;
      block-size: 1lh;
    }

    .ds-popover__icon > svg,
    .ds-popover__check > svg {
      inline-size: 100%;
      block-size: 100%;
    }

    /* The tick sits where an icon would, so a list of choices lines up with a
       list of actions and the two can be in one panel. */
    .ds-popover__check--empty {
      visibility: hidden;
    }

    .ds-popover__label {
      flex: 1;
      min-inline-size: 0;
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
    }

    /* What to press instead. Quiet, at the far end, and never the reason the
       panel is as wide as it is.

       A place, not a treatment. Written out, a shortcut is the run of mono this
       rule draws: "⌘K" as words, which is what a line wants when the keys are
       incidental. Handed `Kbd`s instead, the keys draw themselves — their own
       cap, their own ground, their own ink — and nothing here fights them,
       because every type property below is one Kbd sets on itself. Which of the
       two a line wants is the line's decision and not this file's: keys where
       the reader is being taught the shortcut, words where they are being
       reminded of it. */
    .ds-popover__shortcut {
      --ds-popover-shortcut-tracking: var(--ds-font-mono-tracking);
      --ds-popover-shortcut-size: var(--ds-font-mono-size);
      --ds-popover-shortcut-weight: var(--ds-font-mono-weight);
      flex: none;
      padding-inline-start: var(--ds-space-4);
      --ds-popover-shortcut-face: var(--ds-font-mono-face);
      font-family: var(--ds-popover-shortcut-face);
      font-size: var(--ds-popover-shortcut-size);
      letter-spacing: var(--ds-popover-shortcut-tracking);
      font-weight: var(--ds-popover-shortcut-weight);
      --ds-popover-shortcut-ink: var(--ds-text-tertiary-neutral);
      color: var(--ds-popover-shortcut-ink);
    }

    .ds-popover__heading {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-popover-heading-face: var(--ds-font-small-face);
      --ds-popover-heading-size: var(--ds-font-small-size);
      --ds-popover-heading-weight: var(--ds-font-small-weight);
      --ds-popover-heading-leading: var(--ds-font-small-leading);
      --ds-popover-heading-tracking: var(--ds-font-overline-tracking);
      padding: var(--ds-space-2) var(--ds-space-2) var(--ds-space-1);
      font-family: var(--ds-popover-heading-face);
      font-size: var(--ds-popover-heading-size);
      font-weight: var(--ds-popover-heading-weight);
      line-height: var(--ds-popover-heading-leading);
      letter-spacing: var(--ds-popover-heading-tracking);
      text-transform: uppercase;
      --ds-popover-heading-ink: var(--ds-text-tertiary-neutral);
      color: var(--ds-popover-heading-ink);
    }

    .ds-popover__separator {
      display: block;
      block-size: 1px;
      margin: var(--ds-space-1) 0;
      border: 0;
      background-color: var(--ds-border-subtle);
    }

    /* ── Who the menu is about ────────────────────────────────────────────────
       Above the lines and outside them: an avatar with its name and its email at
       the head of an account menu. The line under it is the separator's, so the
       head reads as the first run and not as a line the arrows skipped. */
    .ds-popover__header {
      display: flex;
      align-items: center;
      padding: var(--ds-space-2) var(--ds-space-2) var(--ds-space-3);
      margin-block-end: var(--ds-space-1);
      border-block-end: 1px solid var(--ds-border-subtle);
    }

    /* What a line's setting currently is: words at the far end, in the quiet
       ink, where the shortcut puts keys. It gives way before the label does. */
    .ds-popover__detail {
      --ds-popover-detail-face: var(--ds-font-small-face);
      --ds-popover-detail-size: var(--ds-font-small-size);
      --ds-popover-detail-weight: var(--ds-font-small-weight);
      --ds-popover-detail-leading: var(--ds-font-small-leading);
      --ds-popover-detail-tracking: var(--ds-font-small-tracking);
      flex: none;
      padding-inline-start: var(--ds-space-4);
      font-family: var(--ds-popover-detail-face);
      font-size: var(--ds-popover-detail-size);
      font-weight: var(--ds-popover-detail-weight);
      line-height: var(--ds-popover-detail-leading);
      letter-spacing: var(--ds-popover-detail-tracking);
      --ds-popover-detail-ink: var(--ds-text-tertiary-neutral);
      color: var(--ds-popover-detail-ink);
    }

    /* ── The line that is a switch ────────────────────────────────────────────
       The lever at the far end of a `menuitemcheckbox`, drawn off the line's own
       `aria-checked`: the line is the control, so the lever is a drawing and
       never a second control inside it. The proportions are the Switch's, so
       the two read as one thing: a track 1.75 marks across, a knob eight tenths
       of the mark with a tenth of rail round it, a travel of three quarters. The
       mark is the choice ramp's at the panel line's own tier, 32px, which is
       1rem there, restated here the way the ramp itself states it: measured 42
       by 24 when it was pointed at the tier instead of the mark. */
    .ds-popover__switch {
      --ds-popover-switch-mark: 1rem;
      --ds-popover-switch-fill: var(--ds-inverse);
      position: relative;
      display: block;
      flex: none;
      margin-inline-start: var(--ds-space-4);
      inline-size: calc(var(--ds-popover-switch-mark) * 1.75);
      block-size: var(--ds-popover-switch-mark);
      border-radius: var(--ds-radius-full);
      background-color: var(--ds-surface-3);
      transition: background-color var(--ds-duration) var(--ds-ease);
    }

    .ds-popover__switch::before {
      content: "";
      position: absolute;
      inset-block-start: 50%;
      inset-inline-start: calc(var(--ds-popover-switch-mark) * 0.1);
      inline-size: calc(var(--ds-popover-switch-mark) * 0.8);
      block-size: calc(var(--ds-popover-switch-mark) * 0.8);
      border-radius: 50%;
      background-color: var(--ds-surface-0);
      translate: 0 -50%;
      transition: translate var(--ds-duration) var(--ds-ease);
    }

    .ds-popover__item[aria-checked="true"] > .ds-popover__switch {
      background-color: var(--ds-popover-switch-fill);
    }

    .ds-popover__item[aria-checked="true"] > .ds-popover__switch::before {
      translate: calc(var(--ds-popover-switch-mark) * 0.75) -50%;
    }

    /* ── The search at the top ────────────────────────────────────────────────
       A long list needs one, and the box that goes there is the Search component,
       because a second search box drawn by hand is a second set of behaviours to
       keep in step: the magnifier, the cross, Escape, the placeholder.

       Sticky, so it stays while the lines scroll under it: a filter that scrolls
       away is a filter you cannot correct without scrolling back.

       The negative margins pull it out to the panel's own edges, over the padding
       the lines sit inside, so the rule under it reaches both sides. */
    .ds-popover__search {
      position: sticky;
      inset-block-start: calc(var(--ds-space-1) * -1);
      z-index: 1;
      margin: calc(var(--ds-space-1) * -1) calc(var(--ds-space-1) * -1) var(--ds-space-1);
      border-block-end: 1px solid var(--ds-border-subtle);
      background-color: var(--ds-surface-1);
    }

    /* The field loses its outline in here. It is already inside a bordered panel,
       and a box within a box two pixels apart is the sort of thing that makes a
       menu look like a form that fell in. */
    .ds-popover__search .ds-field__frame {
      border-color: transparent;
      border-radius: 0;
      background-color: transparent;
    }

    /* And it keeps losing it once the caret is in it: the frame paints its focus
       colour back the moment the caret arrives, and the rule above weighs the same
       as the one that does it. Nothing is lost by taking it: the panel itself is
       the thing with the focus, and the caret is in the only box there is.
       
       It carried a `box-shadow` and an `outline` too, against the halo the frame
       used to draw its ring with. The frame stopped drawing one on 5 September
       2026 — the ring is the border's colour now — so those two were defending a
       state the field can no longer enter, and they are gone. */
    .ds-popover__search .ds-field__frame:focus-within {
      border-color: transparent;
    }

    /* Nothing matched. Says so on the line where an answer would have been,
       rather than leaving an empty panel that reads as broken. */
    .ds-popover__empty {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-popover-empty-face: var(--ds-font-label-face);
      --ds-popover-empty-tracking: var(--ds-font-label-tracking);
      --ds-popover-empty-size: var(--ds-font-label-size);
      padding: var(--ds-space-3) var(--ds-space-2);
      font-family: var(--ds-popover-empty-face);
      font-size: var(--ds-popover-empty-size);
      letter-spacing: var(--ds-popover-empty-tracking);
      --ds-popover-empty-ink: var(--ds-text-tertiary-neutral);
      color: var(--ds-popover-empty-ink);
    }

    /* Which state turns an option's mark over. `swap.css` says there are two and
       that the tick is the hidden one; this says when. `aria-selected` is what the
       option already carried, so nothing new goes into the markup for it. */
    .ds-popover__item[aria-selected="true"] .ds-swap__off {
      display: none;
    }

    .ds-popover__item[aria-selected="true"] .ds-swap__on {
      display: inline-flex;
    }

    /* ── On a phone, a menu is a sheet from the bottom ──────────────────────────
       A menu placed at a pointer or under a button is a shape for a screen with a
       pointer. Under the shell's phone break a panel of menu lines comes up from
       the bottom instead, as wide as the screen. The CSS decides and the machine
       reads it: `--ds-popover-sheet` set here is what tells the placing code to
       write no position, since an inline position would beat this rule. A
       listbox stays a dropdown, because it belongs to the field above it. */
    @container ds-app-shell (width <= 40rem) {
      .ds-popover[role="menu"] {
        --ds-popover-sheet: 1;
        --ds-popover-ceiling: 85dvh;
        inset: auto 0 0;
        inline-size: 100%;
        min-inline-size: 100%;
        max-block-size: var(--ds-popover-ceiling);
        padding: var(--ds-space-2) var(--ds-space-2) calc(var(--ds-space-2) + env(safe-area-inset-bottom, 0px));
        border-inline: 0;
        border-block-end: 0;
        border-radius: var(--ds-radius-4) var(--ds-radius-4) 0 0;
      }
    }

    /* ==========================================================================
       Prose

       The CSS behind <Prose>. Private, like every other component's.

       It dresses the HTML nobody here wrote: an article out of an editor, a
       description out of a database, anything that arrives as a string of tags.
       A role in this system is a class, and a class cannot be put on a `<p>` that
       came out of a row.

       ── The one place bare elements are styled on purpose ─────────────────────
       Everywhere else that is forbidden, and the rule is what keeps the CSS
       governable: an element says what it is and a class says what it looks like.
       This is the exception, and it has a fence around it — inside `.ds-prose`,
       nowhere else, and the selectors never leave.

       ── The measure is the point, and the unit lies ───────────────────────────
       A line of prose that runs the width of a desk is a line nobody finishes.
       `--ds-prose-measure` holds it, in `ch` deliberately: the right line length
       is a number of characters rather than of pixels, so it should follow the
       type instead of fighting it.

       But `ch` is the width of a ZERO, and a zero is far wider than the average
       letter in a proportional face. Measured on this one: 66ch printed 93
       characters, well past the 45-75 anybody reads comfortably. 48ch prints 68,
       which is the number that was wanted, and it is why the value here looks too
       small to anybody who has read the rule and not measured it.

       ── The space above a heading is not the space below it ───────────────────
       A heading belongs to what follows it, so the gap over it is larger than the
       gap under it. Every rich-text block that gets this wrong reads as a list of
       paragraphs with bold lines scattered through it. Both numbers come off the
       rhythm ladder, which is where every other gap in the system comes from.

       ── What the content does not get to decide ───────────────────────────────
       An image 3000px wide, a table wider than the column, a `pre` with one very
       long line. Content nobody here wrote will eventually contain all three, and
       without an answer to each the article breaks the page holding it.
       ========================================================================== */

    .ds-prose {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      /* The prose rung of the copy size, not copy itself: copy's 24px line is
         the UI line a lede shares with the marks beside it, and an article is
         running text with no marks in it. One role feeds this fence and the
         docs' own paragraphs, so an article and a hand-written paragraph read
         identically. */
      --ds-prose-face: var(--ds-font-copy-prose-face);
      --ds-prose-tracking: var(--ds-font-copy-prose-tracking);
      --ds-prose-size: var(--ds-font-copy-prose-size);
      --ds-prose-weight: var(--ds-font-copy-prose-weight);
      --ds-prose-leading: var(--ds-font-copy-prose-leading);
      /* Inline code sits under the surrounding line rather than at it: a
         monospace face at the same size prints visibly larger than the body it
         sits in. `label`, and it was `0.9em`: 14.4px inside a 16px paragraph, a
         number nobody chose and one that misses the step of 4. The rung below
         body is 14, which is the same relationship said with a name. */
      --ds-prose-code-size: var(--ds-font-label-size);
      /* This component's own. The role says what the words are; this says that
         THIS component wants a heavier hand than the role has, which is a
         decision the component makes and nobody else. Pointed at the semantic
         rather than holding a number, so moving the system moves every component
         that has not been moved on purpose, and moving one moves one. */
      /* Intense and not the general emphasis weight. `--ds-emphasis-weight` is
         500, which reads next to a 400 UI label — the head of an accordion, the
         title of an alert — because those sit alone. Inside running text it does
         not: a `<strong>` at 500 among 400 is a word slightly darker than the
         others, and a reader who was told «this part matters» cannot find it.
         Every editor writes bold at 700, so the writer sees one thing and the
         reader gets another. 600 is the step this system has, and it is a step. */
      --ds-prose-emphasis-weight: var(--ds-bold-weight);
      /* The wrapper owns the colour and the parts inherit it, rather than each
         element forcing its own: a selector like `.ds-prose p` outranks any class
         the caller puts on the wrapper, so a project that wants quieter prose
         could not have it. */
      --ds-prose-ink: var(--ds-text-primary-neutral);
      color: var(--ds-prose-ink);
      font-family: var(--ds-prose-face);
      /* The reading size, said here because every consumer needs one and the
         alternative is each of them restating the same three declarations. It
         reads the copy role, so prose follows the surface like everything else. */
      font-size: var(--ds-prose-size);
      letter-spacing: var(--ds-prose-tracking);
      font-weight: var(--ds-prose-weight);
      line-height: var(--ds-prose-leading);
      max-inline-size: var(--ds-prose-measure, 48ch);
      text-wrap: pretty;
      /* The frame a figure asks before standing beside the text: the reading
         column, not the window. A viewport query would answer for the page and
         not for the column, and the same article is a sidebar in one product and
         a page in another. Named, so only what asks for THIS one is answered. */
      container-type: inline-size;
      container-name: ds-prose;
    }

    /* ── The blocks, and the rhythm between them ──────────────────────────── */

    .ds-prose > * {
      margin-block: 0;
    }

    /* Said on the element and not left to inheritance, because base/text.css has
       an element rule for `p` and an element rule beats anything inherited: the
       fence's 28px line lost to the base's from underneath, measured 20px on an
       article paragraph before this rule. */
    .ds-prose p {
      font-size: var(--ds-prose-size);
      line-height: var(--ds-prose-leading);
    }

    .ds-prose > * + * {
      margin-block-start: var(--ds-rhythm-element);
    }

    /* What every heading shares is only the wrapping. The face, the weight and
       the tracking come with each level's rung below, because a rung is five
       halves and three of them used to be heading 3's for all six levels: an h3
       wore heading 4's size at weight 400 and -0.48px where the rung declares
       robust and -0.24px, measured on /prose on 22 August 2026. */
    .ds-prose :is(h1, h2, h3, h4, h5, h6) {
      text-wrap: balance;
    }

    .ds-prose > :is(h1, h2, h3, h4, h5, h6) + * {
      margin-block-start: var(--ds-space-3);
    }

    .ds-prose > * + :is(h1, h2, h3, h4, h5, h6) {
      margin-block-start: var(--ds-rhythm-block);
    }

    /* Nothing above the first thing in the block. A wrapper that opens with a
       heading otherwise pushes itself away from whatever it was put under. */
    .ds-prose > :first-child {
      margin-block-start: 0;
    }

    /* An article's own title is level one and is rarely inside the prose; when it
       is, it is the page's largest. The rest step down the same ladder the docs
       and the components read. */
    .ds-prose h1 {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-prose-h1-face: var(--ds-font-heading-2-face);
      --ds-prose-h1-size: var(--ds-font-heading-2-size);
      --ds-prose-h1-leading: var(--ds-font-heading-2-leading);
      --ds-prose-h1-tracking: var(--ds-font-heading-2-tracking);
      font-family: var(--ds-prose-h1-face);
      font-size: var(--ds-prose-h1-size);
      line-height: var(--ds-prose-h1-leading);
      font-weight: var(--ds-font-heading-2-weight);
      letter-spacing: var(--ds-prose-h1-tracking);
    }

    .ds-prose h2 {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-prose-h2-face: var(--ds-font-heading-3-face);
      --ds-prose-h2-size: var(--ds-font-heading-3-size);
      --ds-prose-h2-leading: var(--ds-font-heading-3-leading);
      --ds-prose-h2-tracking: var(--ds-font-heading-3-tracking);
      font-family: var(--ds-prose-h2-face);
      font-size: var(--ds-prose-h2-size);
      line-height: var(--ds-prose-h2-leading);
      font-weight: var(--ds-font-heading-3-weight);
      letter-spacing: var(--ds-prose-h2-tracking);
    }

    .ds-prose h3 {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-prose-h3-face: var(--ds-font-heading-4-face);
      --ds-prose-h3-size: var(--ds-font-heading-4-size);
      --ds-prose-h3-leading: var(--ds-font-heading-4-leading);
      --ds-prose-h3-tracking: var(--ds-font-heading-4-tracking);
      font-family: var(--ds-prose-h3-face);
      font-size: var(--ds-prose-h3-size);
      line-height: var(--ds-prose-h3-leading);
      font-weight: var(--ds-font-heading-4-weight);
      letter-spacing: var(--ds-prose-h3-tracking);
    }

    .ds-prose :is(h4, h5, h6) {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-prose-h4-face: var(--ds-font-heading-6-face);
      --ds-prose-h4-size: var(--ds-font-heading-6-size);
      --ds-prose-h4-leading: var(--ds-font-heading-6-leading);
      --ds-prose-h4-tracking: var(--ds-font-heading-6-tracking);
      font-family: var(--ds-prose-h4-face);
      font-size: var(--ds-prose-h4-size);
      line-height: var(--ds-prose-h4-leading);
      font-weight: var(--ds-font-heading-6-weight);
      letter-spacing: var(--ds-prose-h4-tracking);
    }

    /* ── The words inside a paragraph ─────────────────────────────────────── */

    .ds-prose a {
      color: inherit;
      text-decoration: underline;
      text-underline-offset: 0.2em;
      text-decoration-thickness: from-font;
    }

    .ds-prose a:hover {
      text-decoration-thickness: 2px;
    }

    .ds-prose a:focus-visible {
      outline: 2px solid var(--ds-focus);
      outline-offset: 2px;
      border-radius: var(--ds-radius-1);
    }

    .ds-prose strong {
      font-weight: var(--ds-prose-emphasis-weight);
    }

    .ds-prose :is(code, kbd, samp) {
      /* Its own name, and it was `--ds-prose-face`, which the block already uses:
         one name for two decisions, and the panel could only ever say one of them.
         The SIZE is declared on the fence with the component's other decisions,
         the way the snippet declares its own, and this rule only reads it. */
      --ds-prose-code-face: var(--ds-font-mono-face);
      font-family: var(--ds-prose-code-face);
      font-size: var(--ds-prose-code-size);
    }

    .ds-prose :not(pre) > code {
      padding: 0.1em 0.3em;
      border-radius: var(--ds-radius-1);
      background-color: var(--ds-surface-2);
    }

    .ds-prose mark {
      padding: 0.1em 0.2em;
      border-radius: var(--ds-radius-1);
      background-color: var(--ds-warning-tint);
      color: inherit;
    }

    /* ── The lists ────────────────────────────────────────────────────────── */

    .ds-prose :is(ul, ol) {
      padding-inline-start: var(--ds-space-6);
    }

    .ds-prose li + li {
      margin-block-start: var(--ds-space-2);
    }

    /* A list inside a list is a continuation, not a new block. */
    .ds-prose li > :is(ul, ol) {
      margin-block-start: var(--ds-space-2);
    }

    .ds-prose :is(dt) {
      font-weight: var(--ds-prose-emphasis-weight);
    }

    .ds-prose dd {
      margin-inline-start: var(--ds-space-6);
    }

    /* Air between the pairs, and none inside one. A term and its value are one
       unit, so the step belongs at the seam where a new term starts.

       Without this rule a definition list is thirty lines with no rhythm at all:
       `.ds-prose > * + *` spaces the prose's own children, and `dt` and `dd` are
       children of the `dl` rather than of the prose, so nothing reaches them.
       Measured 27 August 2026 on a page of fifteen company details, where the
       only thing separating one fact from the next was the 24px indent on the
       value, which at the same size as its term reads as a stray tab rather than
       as a level. */
    .ds-prose dd + dt {
      margin-block-start: var(--ds-space-4);
    }

    /* ── The quote ────────────────────────────────────────────────────────── */

    /* A line at the side and nothing else: no italics, since a long quotation set
       in italics is a long quotation nobody reads, and no oversized punctuation,
       which is a decoration that ages. */
    .ds-prose blockquote {
      padding-inline-start: var(--ds-space-4);
      border-inline-start: 2px solid var(--ds-border);
      --ds-prose-ink: var(--ds-text-secondary-neutral);
      color: var(--ds-prose-ink);
    }

    .ds-prose blockquote > * + * {
      margin-block-start: var(--ds-space-3);
    }

    .ds-prose hr {
      display: block;
      block-size: 1px;
      border: 0;
      background-color: var(--ds-border-subtle);
    }

    /* ── What the content does not get to decide ──────────────────────────── */

    /* Never wider than the column, and holding its own shape while it loads: an
       image with no ratio is a paragraph that jumps down the page when it
       arrives. */
    .ds-prose :is(img, video, svg) {
      max-inline-size: 100%;
      block-size: auto;
      border-radius: var(--ds-radius-2);
    }

    .ds-prose figure {
      margin-inline: 0;
    }

    /* Two things a figure decides about its picture that the picture cannot.

       **A ceiling.** A portrait as wide as the column is a thousand pixels tall:
       a screen and a half of photograph in the middle of an article, which scrolls
       like a hole in the page. Measured on a real one — 1079 by 1462 — the article
       went from 11813 to 15082 pixels the moment the cap was taken off. With both
       measures free and a ceiling on one, the browser narrows the other and the
       proportions stay the picture's own.

       **And the middle.** A picture narrower than the column, pushed against the
       left margin, reads as a mistake in the typesetting rather than a choice;
       centred, a narrow one and a wide one are the same gesture. Only in a figure,
       where the picture is the block: a bare `img` in running text is whatever the
       page meant it to be. */
    .ds-prose figure img {
      display: block;
      /* `auto` and not the tag's number, and this line is the whole of it: with
         the width taken from the attribute, the ceiling clamps the height and the
         width stays where it was — a 1079 by 1462 portrait came out 800 by 512,
         squashed, measured. With the inline size auto the browser derives it from
         the ratio once the ceiling bites, and the same picture comes out 378 by
         512, which is the picture. */
      inline-size: auto;
      max-inline-size: 100%;
      max-block-size: var(--ds-prose-picture-ceiling, 32rem);
      margin-inline: auto;
    }

    /* In a row the cells are already narrow, and a ceiling meant for the column
       would let one tall picture stand twice as high as the others beside it. */
    .ds-prose figure.ds-prose__row img {
      max-block-size: var(--ds-prose-picture-ceiling-row, 18rem);
    }

    .ds-prose figcaption {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-prose-figcaption-size: var(--ds-font-label-size);
      --ds-prose-figcaption-leading: var(--ds-font-label-leading);
      margin-block-start: var(--ds-space-2);
      font-size: var(--ds-prose-figcaption-size);
      line-height: var(--ds-prose-figcaption-leading);
      --ds-prose-ink: var(--ds-text-secondary-neutral);
      color: var(--ds-prose-ink);
      /* Under the picture it belongs to, which stands in the middle of the
         column: a caption flush left under a centred picture is two blocks, and
         it is meant to be one. */
      text-align: center;
    }

    /* A block of code is the one thing here allowed to be wider than the measure,
       because breaking a line of code changes what it says. It scrolls instead. */
    .ds-prose pre {
      overflow-x: auto;
      padding: var(--ds-space-4);
      border: 1px solid var(--ds-border-subtle);
      border-radius: var(--ds-radius-3);
      background-color: var(--ds-surface-1);
    }

    .ds-prose pre code {
      padding: 0;
      background: none;
    }

    /* And a table, for the same reason and with the same answer. The scroller is
       the wrapper the renderer puts around it, since a `table` cannot scroll. */
    .ds-prose__scroll {
      overflow-x: auto;
    }

    .ds-prose table {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-prose-table-size: var(--ds-font-label-size);
      --ds-prose-table-leading: var(--ds-font-label-leading);
      inline-size: 100%;
      border-collapse: collapse;
      font-size: var(--ds-prose-table-size);
      line-height: var(--ds-prose-table-leading);
    }

    .ds-prose :is(th, td) {
      padding: var(--ds-space-2) var(--ds-space-3);
      border-block-end: 1px solid var(--ds-border-subtle);
      text-align: start;
      vertical-align: top;
    }

    .ds-prose th {
      font-weight: var(--ds-prose-emphasis-weight);
    }

    /* ── Wider than the measure, on purpose ───────────────────────────────── */

    /* An editor that can mark a figure as full width needs somewhere to put that,
       and a class on the element is the only thing that survives a database. */
    .ds-prose :is(figure, img, pre, .ds-prose__scroll).ds-prose__wide {
      max-inline-size: none;
      inline-size: 100%;
    }

    /* ── Where else a figure can stand ────────────────────────────────────────
       Four places, and the class is how they survive a database: the column
       (no class, and the common case), wider than the column, beside the text,
       and several in a row.

       The row is ONE class for two pictures, three or seven. `auto-fit` collapses
       the tracks nothing fills, so two share the width in two columns and seven
       wrap into as many rows as they need: the count is the content's business,
       and a class per number would be the same layout wearing three names. */
    .ds-prose figure.ds-prose__row {
      display: flex;
      flex-wrap: wrap;
      gap: var(--ds-space-4);
      /* Hanging from the top, because pictures in a row have different heights
         and aligning them at the bottom makes them float. */
      align-items: flex-start;
    }

    /* `9rem` is the basis and they grow from it, so two share the line in halves
       and three in thirds with one class and no number in its name.

       Growing means a picture can be printed a little larger than it is, which is
       forbidden in the column — a 300px screenshot blown to the measure is a
       pulp, and it is why nothing here sets `inline-size` on an image. In a ROW
       the cell is already small: at this measure two cells are 320 and three are
       208, so the worst case is a 300px picture at 320, seven per cent. The
       alternative was shrinking from the picture's own width, and `flex-wrap`
       wraps before it shrinks: three pictures went onto two lines, measured.

       `min-inline-size: 0` because a flex item's automatic minimum is its
       content, and a picture that refuses to go under its own pixels pushes the
       row out of the column. */
    .ds-prose figure.ds-prose__row > img {
      flex: 1 1 9rem;
      min-inline-size: 0;
    }

    /* The caption is the row's and not a picture's, and a basis of the whole
       line is what puts it under them: it was `grid-column: 1 / -1` on a grid,
       and spanning every track is exactly what stops `auto-fit` collapsing the
       empty ones — three pictures were laid into four columns, measured, with
       the fourth standing empty at the end of the row. */
    .ds-prose figure.ds-prose__row > figcaption {
      flex-basis: 100%;
    }

    /* Beside the text, which runs past it. A float and not a grid: running
       AROUND something is the one thing a grid cannot do.

       Only once the column is wide enough to be worth splitting, and the width is
       `--ds-break-phone`: at 640 of COLUMN a figure at 48 per cent leaves some
       forty characters a line, which still reads. The rung under it, 480, leaves
       thirty-two, which is a ribbon of words and not reading, so under this the
       figure keeps the whole column. */
    @container ds-prose (min-width: 40rem) {
      /* Il riquadro si stringe sulla fotografia, e non il contrario.
         Con una larghezza fissa il box restava 320 e la foto, centrata dentro,
         partiva trentacinque pixel dopo il margine della colonna: l'unica cosa che
         un float promette e' che il suo bordo sia il bordo del testo, e li' non lo
         era. `fit-content` fino al tetto, e dentro la foto non si centra: in una
         colonna il centro e' giusto, a lato il bordo lo e' di piu'. */
      .ds-prose figure:is(.ds-prose__aside, .ds-prose__aside-end) {
        inline-size: fit-content;
        max-inline-size: min(48%, 20rem);
      }

      .ds-prose figure:is(.ds-prose__aside, .ds-prose__aside-end) img {
        margin-inline: 0;
      }

      /* The space under a float is the float's own: clearance moves the next
         block to the float's bottom edge and eats the margin it was carrying, so
         without this a heading after a picture starts against it — measured at
         zero pixels of gap.

         E sopra la stessa misura, per la stessa ragione per cui i due lati ce
         l'hanno: il ritmo generale fra due blocchi da' sedici, e sedici sopra
         contro trentadue a fianco e sotto si vede a occhio — la fotografia sembra
         incollata alla riga che ha appena lasciato. Attorno a uno scatto l'aria
         e' una sola. */
      /* Sotto, il margine e' della figura: la clearance porta il blocco successivo
         al bordo inferiore del float e si mangia il margine che quello portava.

         SOPRA no, e qui sta il punto: le righe che scorrono accanto a un float
         cominciano dal bordo alto del suo MARGINE, non della sua cornice. Con
         trentadue di margine sopra, la prima riga di testo partiva trentadue pixel
         piu' in alto della fotografia — si legge come un disallineamento, ed e'
         quello che si vede per primo. Quindi l'aria sopra la da' il blocco che
         viene prima, e la figura parte a zero: il divario col capoverso sopra
         resta trentadue, e la prima riga di fianco comincia dove comincia lo
         scatto. */
      .ds-prose figure:is(.ds-prose__aside, .ds-prose__aside-end) {
        margin-block-start: 0;
        margin-block-end: var(--ds-rhythm-block);
        /* La mezza interlinea, e non e' un dettaglio: una riga di testo comincia
           sotto meta' del proprio interlinea, la fotografia no. Allineati i due
           bordi delle SCATOLE, l'inchiostro della prima riga restava cinque pixel
           sotto il bordo alto dello scatto — misurato — e a occhio si legge come
           una foto piu' vicina al capoverso di sopra di quanto non lo sia il testo
           che le scorre accanto. Il padding sposta la fotografia dentro la scatola
           e non la scatola, quindi le righe di fianco restano dove sono. Un
           browser che non conosce `lh` la lascia a zero, che e' com'era. */
        padding-block-start: calc((1lh - 1em) / 2);
      }

      .ds-prose > *:has(+ figure:is(.ds-prose__aside, .ds-prose__aside-end)) {
        margin-block-end: var(--ds-rhythm-block);
      }

      /* Il rhythm di BLOCCO e non quello di elemento: accanto a una fotografia
         serve la stessa aria che ha sopra e sotto. A 16 il testo le sta addosso —
         si legge come una colonna incollata al bordo della foto — e sono gli
         stessi 32 che il sito da cui questa disposizione viene usava da sempre. */
      .ds-prose figure.ds-prose__aside {
        float: inline-start;
        margin-inline-end: var(--ds-rhythm-block);
      }

      .ds-prose figure.ds-prose__aside-end {
        float: inline-end;
        margin-inline-start: var(--ds-rhythm-block);
      }

      /* What runs around a figure is the PROSE, and nothing else. A float has no
         height in its parent, so without this the next heading climbs up beside
         the picture and a new section opens halfway down the last one — seen on
         the first render, with «In riga» standing level with the caption above it.
         Paragraphs wrap; a heading, a list, a quote, another figure, a rule and a
         block of code all start under it. */
      .ds-prose :is(h2, h3, ul, ol, blockquote, pre, hr, figure, table, .ds-prose__scroll) {
        clear: both;
      }
    }

    /* ==========================================================================
       Provider button

       The CSS behind <ProviderButton>. Private, like every other component's.

       ── One shape for all of them ─────────────────────────────────────────────
       A column of these is read as a list, not as five separate buttons. Same
       height, same weight, same outline, and the mark in the same place down the
       left. A row where Google is loud and Apple is quiet has picked a favourite
       on the reader's behalf.

       ── The label is centred and the mark is not ──────────────────────────────
       The mark sits where the label starts and the label sits in the middle of the
       button, which is what the reference does and what reads as a stack rather
       than as a ragged list. The mark is taken out of the flow for that, so the
       label is centred on the button and not on what is left of it.

       ── It is an outline, always ──────────────────────────────────────────────
       The form above it is what the product is asking for. These are the other
       ways in, and a solid one of them out-shouts the thing it is standing under.
       ========================================================================== */

    .ds-provider {
      --ds-provider-face: var(--ds-font-body-face);
      position: relative;
      box-sizing: border-box;
      display: flex;
      align-items: center;
      justify-content: center;
      inline-size: 100%;
      min-block-size: var(--ds-provider-tier);
      padding-inline: var(--ds-provider-inset);
      border: 1px solid var(--ds-border);
      border-radius: var(--ds-radius-2);
      background-color: var(--ds-surface-1);
      --ds-provider-ink: var(--ds-text-primary-neutral);
      color: var(--ds-provider-ink);
      font-family: var(--ds-provider-face);
      /* The role's own, where `Button` takes the emphasis weight, and the
         difference is the point: what you read on a provider button is the mark
         and the brand beside it, so the words stay quiet and let the logo be the
         thing you aim at. */
      font-weight: var(--ds-font-body-weight);
      cursor: pointer;
      transition:
        background-color var(--ds-duration) var(--ds-ease),
        border-color var(--ds-duration) var(--ds-ease);
    }

    /* The ramp, and the type the tier map pairs with each rung. The size is set on
       the button rather than aliased into a variable of ours: a variable declared
       here would resolve once and keep the web surface's number on the product
       one, where the same rung carries different type. */
    .ds-provider--xsmall {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-provider-face: var(--ds-font-small-face);
      --ds-provider-tracking: var(--ds-font-small-tracking);
      --ds-provider-size: var(--ds-font-small-size);
      --ds-provider-weight: var(--ds-font-small-weight);
      --ds-provider-leading: var(--ds-font-small-leading);
     --ds-provider-tier: var(--ds-size-1); --ds-provider-inset: var(--ds-space-2); font-size: var(--ds-provider-size); line-height: var(--ds-provider-leading); }
    .ds-provider--small {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-provider-face: var(--ds-font-label-face);
      --ds-provider-tracking: var(--ds-font-label-tracking);
      --ds-provider-size: var(--ds-font-label-size);
      --ds-provider-weight: var(--ds-font-label-weight);
      --ds-provider-leading: var(--ds-font-label-leading);
     --ds-provider-tier: var(--ds-size-2); --ds-provider-inset: var(--ds-space-3); font-size: var(--ds-provider-size); line-height: var(--ds-provider-leading); }
    .ds-provider--medium {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-provider-face: var(--ds-font-body-face);
      --ds-provider-tracking: var(--ds-font-body-tracking);
      --ds-provider-size: var(--ds-font-body-size);
      --ds-provider-weight: var(--ds-font-body-weight);
      --ds-provider-leading: var(--ds-font-body-leading);
     --ds-provider-tier: var(--ds-size-3); --ds-provider-inset: var(--ds-space-4); font-size: var(--ds-provider-size); line-height: var(--ds-provider-leading); }
    .ds-provider--large {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-provider-face: var(--ds-font-body-face);
      --ds-provider-tracking: var(--ds-font-body-tracking);
      --ds-provider-size: var(--ds-font-body-size);
      --ds-provider-weight: var(--ds-font-body-weight);
      --ds-provider-leading: var(--ds-font-body-leading);
     --ds-provider-tier: var(--ds-size-4); --ds-provider-inset: var(--ds-space-4); font-size: var(--ds-provider-size); line-height: var(--ds-provider-leading); }
    .ds-provider--xlarge {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-provider-face: var(--ds-font-body-face);
      --ds-provider-tracking: var(--ds-font-body-tracking);
      --ds-provider-size: var(--ds-font-body-size);
      --ds-provider-weight: var(--ds-font-body-weight);
      --ds-provider-leading: var(--ds-font-body-leading);
     --ds-provider-tier: var(--ds-size-5); --ds-provider-inset: var(--ds-space-5); font-size: var(--ds-provider-size); line-height: var(--ds-provider-leading); }
    .ds-provider--xxlarge {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-provider-face: var(--ds-font-body-face);
      --ds-provider-tracking: var(--ds-font-body-tracking);
      --ds-provider-size: var(--ds-font-body-size);
      --ds-provider-weight: var(--ds-font-body-weight);
      --ds-provider-leading: var(--ds-font-body-leading);
     --ds-provider-tier: var(--ds-size-6); --ds-provider-inset: var(--ds-space-6); font-size: var(--ds-provider-size); line-height: var(--ds-provider-leading); }

    /* Out of the flow, so the label is centred on the button rather than on the
       room the mark left behind. */
    .ds-provider__mark {
      position: absolute;
      inset-inline-start: var(--ds-provider-inset);
      display: inline-flex;
      align-items: center;
      justify-content: center;
      inline-size: 1lh;
      block-size: 1lh;
    }

    .ds-provider__mark > svg {
      inline-size: 100%;
      block-size: 100%;
    }

    /* Room for the mark on both sides, so a long name never runs under it: the
       slot it is standing out of, plus the space between them.

       This only reads as a number because the tiers above declare their leading.
       Without it `1lh` resolved against `normal` and the reserved room came out
       29.5 — a measurement nothing in the system could name. */
    .ds-provider__label {
      padding-inline: calc(1lh + var(--ds-space-2));
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
    }

    .ds-provider:hover:not(:disabled) {
      background-image: linear-gradient(var(--ds-surface-hover), var(--ds-surface-hover));
    }

    .ds-provider:active:not(:disabled) {
      background-image: linear-gradient(var(--ds-surface-active), var(--ds-surface-active));
    }

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

    .ds-provider:disabled {
      background-color: var(--ds-surface-disabled);
      --ds-provider-ink: var(--ds-text-disabled);
      color: var(--ds-provider-ink);
      cursor: not-allowed;
    }

    /* ==========================================================================
       Toolbar

       The CSS behind <Toolbar>. Private, like every other component's.

       The strip over a list: what narrows it on the left, what looks through it in
       the middle, what acts on the whole of it on the right. Filters, a search
       box, a view picker, an export.

       ── The strip, and the strip alone ───────────────────────────────────────
       The bar that counts ticked rows and acts on them is its own component.
       Holding both in this file made the second replace the first: the strip you
       were using disappeared the moment you used it.
       ========================================================================== */

    .ds-toolbar {
      display: flex;
      align-items: center;
      gap: var(--ds-space-3);
      /* Wraps rather than squeezing: the search box has a floor and the controls
         at either end have their labels. */
      flex-wrap: wrap;
      /* A rung less above and below than at the sides. It was half as much once,
         8 against 16, which read as a strip pushed against the top of whatever
         holds it; then the same on all four, 64px tall for a row of small
         controls. Twelve and sixteen is the strip sitting inside the sheet at
         the height its foot takes, so the two ends of a list are one band. */
      padding-block: var(--ds-space-3);
      /* Its own gutter, the same one a table cell draws, so the first control in
         the strip stands over the first column's first word rather than over the
         table's edge. The strip had none and took whatever its container gave it:
         in a panel that pads, fine; over a table, which is flush because the cells
         pad themselves, its first button sat one pixel off the edge while the text
         below it started at seventeen. A strip that filters a list has to line up
         with the list. */
      /* A strip that filters a list has to line up with the list, and where the
         list is not in a sheet that gutter is zero. `--ds-toolbar-gutter` is how
         a screen says so: the inbox stands its strip on the page and flushes it
         with the first card's edge. */
      padding-inline: var(--ds-toolbar-gutter, var(--ds-space-4));
    }

    /* A floor, and only when something stands on it. The strip and the list under
       it are two things: without a line the tools sit in the table's own head and
       the first column heading reads as one more control. A strip with nothing
       below it is a band on its own and draws none. */
    .ds-toolbar:not(:last-child) {
      border-block-end: 1px solid var(--ds-border-subtle);
    }

    /* ── The strip on its own ─────────────────────────────────────────────────
       Inside a table or a list the strip is the sheet's head band, padded to the
       sheet's gutter with the line under it, and that is the default. Standing
       alone over a column of cards, a grid or a board it is `bare`: no padding,
       no line, the controls flush with the edge of what stands under them. For
       half a day it had a sheet of its own instead, surface, border and corner,
       so it would stand level with the filter panel's head; the owner, 10
       September 2026: it dirties the design, "via immediatamente il foglio",
       and standing alone it wants no padding at all. */
    .ds-toolbar--bare {
      padding: 0;
    }

    .ds-toolbar--bare:not(:last-child) {
      border-block-end: 0;
    }

    /* Above a table that scrolls, the strip is the one part that has to stay: the
       filters are what you reach for after reading the fortieth row. */
    .ds-toolbar--sticky {
      position: sticky;
      inset-block-start: 0;
      z-index: 1;
      background-color: var(--ds-surface-1);
    }

    /* The middle takes what is left. Its floor is what stops the strip collapsing
       into a column the moment two chips are added at the ends. */
    .ds-toolbar__search {
      flex: 1;
      min-inline-size: 12rem;
    }

    .ds-toolbar__start,
    .ds-toolbar__end {
      display: flex;
      align-items: center;
      gap: var(--ds-space-2);
      flex: none;
    }

    /* Only when there is no search between them, since the search already pushes
       the two ends apart and a margin as well would leave the end floating. */
    .ds-toolbar--split .ds-toolbar__end {
      margin-inline-start: auto;
    }

    /* ── Narrow ───────────────────────────────────────────────────────────────
       One row, always. A strip of tools that wraps to two lines pushes the thing
       it is a strip over down the screen, and on a phone that is most of what
       there was to see.

       Asked of the strip's own width and not the window's. A media query is the
       wrong instrument here twice over: a toolbar inside a drawer, a split view
       or a device canvas is narrow while the window is not, and the window is the
       only thing a media query can see. The strip measures itself.

       The words go and the marks stay, guarded on `:has(.ds-button__icon)`,
       which is the whole safety of it: a button with no mark keeps its words
       rather than becoming an empty box. The name stays in the accessible tree
       either way, since it is the button's own content and not a title hung on
       an icon. And once the words are gone the row fits, which is why nothing
       here says `nowrap`: the wrapping was the symptom.

       The search is untouched but its floor comes down. A field that becomes a
       magnifying glass is a field you have to open before you can use it, and it
       is the one thing in the row somebody types into.

       The rule lives here, on the toolbar, and not on each button, because a rule
       you have to remember at every call site is a rule that holds until the
       second toolbar. */
    .ds-toolbar {
      container-type: inline-size;
      container-name: ds-toolbar;
    }

    @container ds-toolbar (max-width: 30rem) {
      .ds-toolbar__start .ds-button:has(.ds-button__icon) .ds-button__label,
      .ds-toolbar__end .ds-button:has(.ds-button__icon) .ds-button__label {
        position: absolute;
        inline-size: 1px;
        block-size: 1px;
        margin: -1px;
        overflow: hidden;
        clip-path: inset(50%);
        white-space: nowrap;
      }

      .ds-toolbar__search {
        min-inline-size: 5rem;
      }
    }

    /* ==========================================================================
       Radio

       The circle, and nothing else. Everything around it is the choice skin,
       worn on the same elements: `ds-choice__mark` carries the slot and
       `ds-radio__dot` draws what goes in it.

       ── Why a group has an element of its own ─────────────────────────────────
       A radio on its own cannot be turned off, so one of them alone is a control
       nobody can undo. The group is the control: it carries the name a reader
       hears, and the shared `name` on the inputs is what makes the arrow keys and
       the reader's count work. It draws no title of its own, because a question
       written over a set of controls is a heading with a line under it, which is
       a section, and a section is written by whoever composes the page.
       ========================================================================== */

    .ds-radio__dot {
      border-radius: 50%;
    }

    /* The mark, drawn rather than fetched: a dot is a circle inside a circle, and
       asking the icon set for one would put a mark in the closed set that only
       this file could ever use.

       Always present and revealed by colour, so the circle never changes size
       between states. `currentColor`, so the one rule that turns the control on
       sets the fill, the border and the dot together. */
    .ds-radio__dot::before {
      content: "";
      display: block;
      inline-size: calc(var(--ds-choice-mark) * 0.44);
      block-size: calc(var(--ds-choice-mark) * 0.44);
      border-radius: 50%;
      background-color: currentColor;
    }

    /* ── The group ───────────────────────────────────────────────────────────
       It holds the answers and nothing else. */
    .ds-radio-group {
      display: flex;
      flex-direction: column;
      min-inline-size: 0;
    }

    .ds-radio-group__options {
      display: flex;
      flex-direction: column;
      gap: var(--ds-space-3);
    }

    /* Side by side, for two or three short answers on one line. It wraps, because
       a row of options that runs off the edge is a row with answers nobody can
       reach. */
    .ds-radio-group--row .ds-radio-group__options {
      flex-direction: row;
      flex-wrap: wrap;
      column-gap: var(--ds-space-6);
    }

    /* ==========================================================================
       Search

       The CSS behind <Search>. Private, like every other component's.

       Almost all of it is the field skin, which is the point: a search box that
       is a different height or a different radius from the fields around it reads
       as something bolted on. What is here is the one part a field does not have,
       the button that empties it.
       ========================================================================== */

    /* Sits inside the frame, at the end, and only exists while there is something
       to clear. Its target is pushed out to the 24px WCAG 2.5.8 asks for, the
       same way the badge's cross is, so what you can press is larger than what
       you can see and the frame does not grow to hold it. */
    .ds-search__clear {
      position: relative;
      display: flex;
      align-items: center;
      justify-content: center;
      flex: none;
      inline-size: 1.25em;
      block-size: 1.25em;
      padding: 0;
      border: 0;
      border-radius: var(--ds-radius-full);
      background: none;
      --ds-search-clear-ink: var(--ds-text-tertiary-neutral);
      color: var(--ds-search-clear-ink);
      cursor: pointer;
      transition:
        color var(--ds-duration) var(--ds-ease),
        background-color var(--ds-duration) var(--ds-ease);
    }

    .ds-search__clear::after {
      content: "";
      position: absolute;
      inset: calc((1.25em - max(24px, 1.25em)) / 2);
    }

    .ds-search__clear > svg {
      inline-size: 0.85em;
      block-size: 0.85em;
    }

    .ds-search__clear:hover {
      --ds-search-clear-ink: var(--ds-text-primary-neutral);
      color: var(--ds-search-clear-ink);
      background-color: var(--ds-surface-hover);
    }

    .ds-search__clear:focus-visible {
      outline: 2px solid var(--ds-focus);
      outline-offset: 1px;
      --ds-search-clear-ink: var(--ds-text-primary-neutral);
      color: var(--ds-search-clear-ink);
    }

    /* The browser draws its own cross inside a search input, and on WebKit it is a
       different mark in a different colour sitting right next to ours: two
       crosses, one of them blue. Ours is the one that puts the caret back in the
       box afterwards, so the native one goes.

       The selector reaches into the field skin on purpose. This is the one place
       where the skin's control has to know it is being used as a search box, and
       the alternative is a type-specific rule in field.css for a component that
       file has never heard of. */
    .ds-field__control[type="search"]::-webkit-search-cancel-button,
    .ds-field__control[type="search"]::-webkit-search-decoration {
      -webkit-appearance: none;
      appearance: none;
    }

    /* What stands at the inner end while the box is empty: the key that opens it,
       usually. Quiet, and never the reason the box is as wide as it is — the
       words being typed own the room, and this is standing in what is left.

       It shares its place with the cross, and never its moment: the cross only
       exists while there is something to clear, and this only while there is not.
       A hint over a box being typed in is something to read that is no longer
       true, and two things in one corner is a corner nobody can press. */
    .ds-search__end {
      display: inline-flex;
      align-items: center;
      gap: var(--ds-space-1);
      flex: none;
      --ds-search-end-ink: var(--ds-text-tertiary-neutral);
      color: var(--ds-search-end-ink);
      /* It is read and never pressed: the key does the thing, and the picture of
         the key in a box is a label. */
      pointer-events: none;
    }

    /* ==========================================================================
       Select

       The CSS behind <Select>. Private, like every other component's.

       The panel is the shared one in popover.css, because a select and a menu are
       the same panel with different roles. What is here is the control that opens
       it, and that control is a FIELD: the same frame, the same height, the same
       focus ring and the same error outline as an input, because it stands in a
       form beside them and anything else reads as bolted on.
       ========================================================================== */

    /* A button wearing the field's control. `ds-field__control` gives it the
       type and the room; this gives it the two things a button needs and an input
       does not. */
    .ds-select__control {
      /* The padding this component sets off the space ramp on purpose, named so a
         tool reads the decision instead of reporting it as a mistake. It is in
         `em`: it tracks the label rather than the box. */
      --ds-select-intent: padding;
      display: flex;
      align-items: center;
      gap: var(--ds-space-2);
      text-align: start;
      cursor: pointer;
    }

    /* The button reaches the sides of the frame as well, and hands the padding
       back on the inside, so every pixel of the box opens the list. A field with
       an input is a caret and clicking beside it does something reasonable; a
       select is a button, and a button with a dead border is somebody clicking
       twice and wondering. The frame keeps its padding for the cases with an icon
       in the slot, and only the button that has the frame to itself reaches over
       it. */
    .ds-field__frame:not(:has(.ds-field__icon)) > .ds-select__control {
      margin-inline: -0.75em;
      padding-inline: 0.75em;
    }

    .ds-field--xsmall .ds-field__frame:not(:has(.ds-field__icon)) > .ds-select__control {
      margin-inline: -0.5em;
      padding-inline: 0.5em;
    }

    /* The value takes the room and the arrow never moves: a long name truncates
       rather than pushing the arrow out of the frame. */
    .ds-select__value {
      flex: 1;
      min-inline-size: 0;
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
    }

    /* Nothing chosen yet. The same colour a placeholder has, because that is what
       it is. */
    .ds-select__value--empty {
      --ds-select-value--empty-ink: var(--ds-text-tertiary-neutral);
      color: var(--ds-select-value--empty-ink);
    }

    /* Secondary and not tertiary, which is the hint colour. The arrow is not a
       hint about what goes in the control, it is the whole of what says the
       control opens, and at the tertiary step it was quieter than the line drawn
       round the box: 4.1 against the 7.8 of the border in the dark theme, so the
       loudest thing in the control was its own edge. */
    .ds-select__arrow {
      display: flex;
      flex: none;
      inline-size: 1lh;
      block-size: 1lh;
      --ds-select-arrow-ink: var(--ds-text-secondary-neutral);
      color: var(--ds-select-arrow-ink);
      transition: transform var(--ds-duration-fast) var(--ds-ease);
    }

    .ds-select__arrow > svg {
      inline-size: 100%;
      block-size: 100%;
    }

    /* Turned over while the panel is open, which is the one bit of state the
       closed control cannot show by itself. */
    .ds-select__control[aria-expanded="true"] .ds-select__arrow {
      transform: rotate(180deg);
    }

    .ds-select__control:disabled {
      cursor: not-allowed;
    }

    /* The chosen option's mark, kept in the control after the panel closes. The
       slot is the line beside it, like every other mark in the system. */
    .ds-select__mark {
      display: inline-flex;
      align-items: center;
      justify-content: center;
      flex: none;
      inline-size: 1lh;
      block-size: 1lh;
    }

    .ds-select__mark > svg,
    .ds-select__mark > img {
      inline-size: 100%;
      block-size: 100%;
      object-fit: contain;
    }

    /* ==========================================================================
       Segmented control

       The CSS behind <SegmentedControl>. Private, like every other component's.

       A row of two to five answers, all of them on screen at once, one of them
       on. Filtering a list, switching a unit, picking a mode. It chooses a VALUE
       and the page around it stays where it is: what changes is what the value
       feeds, one field, one list, one number.

       ── The track and the pill ────────────────────────────────────────────────
       A sunken track with one raised segment in it. The raised one is a surface
       and a border rather than a shadow, because this system has no elevation and
       the panel next to it is told apart the same way.
       ========================================================================== */

    .ds-segmented {
      /* This component's own. The role says what the words are; this says that
         THIS component wants a heavier hand than the role has, which is a
         decision the component makes and nobody else. Pointed at the semantic
         rather than holding a number, so moving the system moves every component
         that has not been moved on purpose, and moving one moves one. */
      /* The surround the track keeps around its segments. It is named because the
         segment's height is the tier less twice this number: one piece of
         arithmetic in two places, and the two have to move together. Only the
         bottom tier overrides it, further down, and the note there says why. */
      --ds-segmented-inset: var(--ds-space-1);

      display: inline-flex;
      align-items: center;
      gap: var(--ds-space-1);
      padding: var(--ds-segmented-inset);
      border-radius: var(--ds-radius-3);
      background-color: var(--ds-surface-2);
      /* Wraps rather than scrolls: five short words on a phone belong on two
         lines, and a row that runs off the side hides an answer. */
      flex-wrap: wrap;
    }

    /* Filling the line, for the two-answer question in a form. Each segment then
       takes an equal share, so the control does not lurch as the words change. */
    .ds-segmented--block {
      display: flex;
    }

    /* ── The ramp, read again ─────────────────────────────────────────────────
       The same six tiers a button and a field stand on, because a segmented
       control sits on a row beside them and two things on one row have to be
       exactly as tall as each other. Two of its own would line up until the day
       somebody put one next to a button.

       The type follows the tier rather than the name, and it follows it the way
       every control here does: 12 at the bottom tier, 14 at the next, 16 from the
       middle up. Four levels for six tiers, and the ramp stops at 16 rather than
       reaching for the level above, which is fluid on the web surface and would
       put a word that changes size inside a box that does not.
       The room at the sides follows it too, in the button's own em steps,
       because one fixed inset for all six tiers is a pill that gets taller and
       never wider: at the top of the ramp it came out as a tall slot with the
       word squeezed into it.

       Both are written onto the element that uses them rather than parked in a
       variable, because a font size varies with the surface and an alias of a
       mode token resolves where it is declared. The check enforces that, and
       caught this on the way in. */
    /* The bottom tier is the one place the arithmetic runs out, and it does so by
       4px: the strip measured 28 where the ramp says 24. A segment carrying a 12px
       word is 20px tall and cannot be any less. Sixteen of leading, which comes off
       the token paired with the size and is not negotiable; plus the 1px a user
       agent pads a <button> with, top and bottom, which nobody in this file
       declared; plus the 1px border it wears when it is the one that is on. Twenty
       inside twenty-four leaves two a side, so the bottom tier is the only one that
       keeps a 2px surround. The other five have room to spare and keep 4: their
       segments are held up by the tier, not by what is written in them. */
    .ds-segmented--xsmall {
      --ds-segmented-tier: var(--ds-size-1);
      --ds-segmented-inset: var(--ds-space-0-5);
    }
    .ds-segmented--xsmall /* An option can be an anchor, and an anchor underlines: the control read as a
       row of links rather than as one control, which is the opposite of what a
       segmented control is for. The rule is here and not on the consumer, because
       the day an option is a link is the day this component decided it could be
       (owner, 13 September 2026, on the pilot's plan). */
    .ds-segmented__option {
      text-decoration: none;
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-segmented-option-face: var(--ds-font-small-face);
      --ds-segmented-option-tracking: var(--ds-font-small-tracking);
      --ds-segmented-option-size: var(--ds-font-small-size);
      --ds-segmented-option-leading: var(--ds-font-small-leading);
     padding-inline: 0.75em; font-size: var(--ds-segmented-option-size); line-height: var(--ds-segmented-option-leading); }
    .ds-segmented--small { --ds-segmented-tier: var(--ds-size-2); }
    .ds-segmented--small /* An option can be an anchor, and an anchor underlines: the control read as a
       row of links rather than as one control, which is the opposite of what a
       segmented control is for. The rule is here and not on the consumer, because
       the day an option is a link is the day this component decided it could be
       (owner, 13 September 2026, on the pilot's plan). */
    .ds-segmented__option {
      text-decoration: none;
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-segmented-option-face: var(--ds-font-label-face);
      --ds-segmented-option-tracking: var(--ds-font-label-tracking);
      --ds-segmented-option-size: var(--ds-font-label-size);
      --ds-segmented-option-leading: var(--ds-font-label-leading);
     padding-inline: 0.9em; font-size: var(--ds-segmented-option-size); line-height: var(--ds-segmented-option-leading); }
    .ds-segmented--medium { --ds-segmented-tier: var(--ds-size-3); }
    .ds-segmented--medium /* An option can be an anchor, and an anchor underlines: the control read as a
       row of links rather than as one control, which is the opposite of what a
       segmented control is for. The rule is here and not on the consumer, because
       the day an option is a link is the day this component decided it could be
       (owner, 13 September 2026, on the pilot's plan). */
    .ds-segmented__option {
      text-decoration: none;
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-segmented-option-face: var(--ds-font-body-face);
      --ds-segmented-option-tracking: var(--ds-font-body-tracking);
      --ds-segmented-option-size: var(--ds-font-body-size);
      --ds-segmented-option-leading: var(--ds-font-body-leading);
     padding-inline: 1.15em; font-size: var(--ds-segmented-option-size); line-height: var(--ds-segmented-option-leading); }
    .ds-segmented--large { --ds-segmented-tier: var(--ds-size-4); }
    .ds-segmented--large /* An option can be an anchor, and an anchor underlines: the control read as a
       row of links rather than as one control, which is the opposite of what a
       segmented control is for. The rule is here and not on the consumer, because
       the day an option is a link is the day this component decided it could be
       (owner, 13 September 2026, on the pilot's plan). */
    .ds-segmented__option {
      text-decoration: none;
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-segmented-option-face: var(--ds-font-body-face);
      --ds-segmented-option-tracking: var(--ds-font-body-tracking);
      --ds-segmented-option-size: var(--ds-font-body-size);
      --ds-segmented-option-leading: var(--ds-font-body-leading);
     padding-inline: 1.4em; font-size: var(--ds-segmented-option-size); line-height: var(--ds-segmented-option-leading); }
    .ds-segmented--xlarge { --ds-segmented-tier: var(--ds-size-5); }
    .ds-segmented--xlarge /* An option can be an anchor, and an anchor underlines: the control read as a
       row of links rather than as one control, which is the opposite of what a
       segmented control is for. The rule is here and not on the consumer, because
       the day an option is a link is the day this component decided it could be
       (owner, 13 September 2026, on the pilot's plan). */
    .ds-segmented__option {
      text-decoration: none;
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-segmented-option-face: var(--ds-font-body-face);
      --ds-segmented-option-tracking: var(--ds-font-body-tracking);
      --ds-segmented-option-size: var(--ds-font-body-size);
      --ds-segmented-option-leading: var(--ds-font-body-leading);
     padding-inline: 1.5em; font-size: var(--ds-segmented-option-size); line-height: var(--ds-segmented-option-leading); }
    .ds-segmented--xxlarge { --ds-segmented-tier: var(--ds-size-6); }
    .ds-segmented--xxlarge /* An option can be an anchor, and an anchor underlines: the control read as a
       row of links rather than as one control, which is the opposite of what a
       segmented control is for. The rule is here and not on the consumer, because
       the day an option is a link is the day this component decided it could be
       (owner, 13 September 2026, on the pilot's plan). */
    .ds-segmented__option {
      text-decoration: none;
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-segmented-option-face: var(--ds-font-body-face);
      --ds-segmented-option-tracking: var(--ds-font-body-tracking);
      --ds-segmented-option-size: var(--ds-font-body-size);
      --ds-segmented-option-leading: var(--ds-font-body-leading);
     padding-inline: 1.75em; font-size: var(--ds-segmented-option-size); line-height: var(--ds-segmented-option-leading); }

    .ds-segmented--block /* An option can be an anchor, and an anchor underlines: the control read as a
       row of links rather than as one control, which is the opposite of what a
       segmented control is for. The rule is here and not on the consumer, because
       the day an option is a link is the day this component decided it could be
       (owner, 13 September 2026, on the pilot's plan). */
    .ds-segmented__option {
      text-decoration: none;
      flex: 1;
    }

    /* An option can be an anchor, and an anchor underlines: the control read as a
       row of links rather than as one control, which is the opposite of what a
       segmented control is for. The rule is here and not on the consumer, because
       the day an option is a link is the day this component decided it could be
       (owner, 13 September 2026, on the pilot's plan). */
    .ds-segmented__option {
      text-decoration: none;
      --ds-segmented-option-weight: var(--ds-emphasis-weight);
      --ds-segmented-option-face: var(--ds-font-body-face);
      display: inline-flex;
      align-items: center;
      justify-content: center;
      gap: var(--ds-space-2);
      /* The tier less the strip's own surround, top and bottom, so what stands on
         the tier is the CONTROL and not the option inside it. It was the option's
         height for a revision, and the strip came out a whole step taller than the
         name it was given: medium measured 48 beside a 40px button, and xxlarge
         measured 72, which is not on the ramp at all. Found by putting every
         control that reads the ramp in one row and looking. */
      min-block-size: calc(var(--ds-segmented-tier) - var(--ds-segmented-inset) * 2);
      border: 1px solid transparent;
      border-radius: var(--ds-radius-2);
      background: none;
      font-family: var(--ds-segmented-option-face);
      font-weight: var(--ds-segmented-option-weight);
      --ds-segmented-option-ink: var(--ds-text-secondary-neutral);
      color: var(--ds-segmented-option-ink);
      white-space: nowrap;
      cursor: pointer;
      transition:
        background-color var(--ds-duration) var(--ds-ease),
        color var(--ds-duration) var(--ds-ease);
    }

    /* The one that is on. A surface with an edge, which is the same way a card is
       lifted off the page here, and the reading colour, which is the other half
       of the answer: the ones that are off are secondary. */
    .ds-segmented__option[aria-checked="true"] {
      background-color: var(--ds-surface-1);
      border-color: var(--ds-border);
      --ds-segmented-option-ink: var(--ds-text-primary-neutral);
      color: var(--ds-segmented-option-ink);
    }

    .ds-segmented__option:hover:not([aria-checked="true"]):not(:disabled) {
      --ds-segmented-option-ink: var(--ds-text-primary-neutral);
      color: var(--ds-segmented-option-ink);
      background-color: var(--ds-surface-hover);
    }

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

    .ds-segmented__option:disabled {
      --ds-segmented-option-ink: var(--ds-text-disabled);
      color: var(--ds-segmented-option-ink);
      cursor: not-allowed;
    }

    /* Sized here so a segment handed a 24px icon does not grow to fit it. */
    .ds-segmented__icon {
      display: inline-flex;
      flex: none;
      inline-size: 1lh;
      block-size: 1lh;
    }

    .ds-segmented__icon > svg {
      inline-size: 100%;
      block-size: 100%;
    }

    /* Nothing but the icon. Square, so a row of them reads as a row of buttons
       rather than as a row of narrow pills, and the label lives in aria-label.

       Square means as wide as the segment is TALL, which is the tier less the
       surround, so the strip lands on the tier on both axes. It measured neither:
       a medium segment came out 40 by 32, because the width was the whole tier,
       and the room at the sides it was supposed to give up was still 16.1px,
       because one class cannot outrank the two that set the padding per tier.
       Hence the block in front of the modifier: same weight, later, so it wins. */
    .ds-segmented .ds-segmented__option--iconic {
      padding-inline: 0;
      inline-size: calc(var(--ds-segmented-tier) - var(--ds-segmented-inset) * 2);
    }

    /* ==========================================================================
       Range

       The CSS behind <Range>. Private, like every other component's.

       A quantity chosen by dragging: how full, how far, how much. One handle
       answers "at least this much" and two answer "between these two", and they
       are the same drawing because they are the same question asked once or
       twice. The handle is what a reader reads first: a grip on a bar is the part
       that says the number under it is theirs to move.

       ── The row is the control, the rail is not ──────────────────────────────
       What stands on the tier is the ROW, not the line inside it. A 4px rail on a
       40px tier would be a control 4px tall in a form beside a field of 40, and
       the pointer would have 4px of anything to aim at. So the row takes the tier,
       the rail is drawn down the middle of it, and everything from the first pixel
       of the row down to the last is pressable.

       ── The handle is half its tier, and the row carries the floor ───────────
       Exactly half: 12, 16, 20, 24, 28, 32, which lands on the 4px step at every
       tier because the ramp itself does. A disc that is the same size at four
       tiers out of six is a control that says its tier and then does not wear it.

       It was written as a max() against 24px for a while, on the grounds that WCAG
       2.5.8 asks that of a target, and that is what flattened the bottom four. The
       floor is real and it is kept, one floor up: what a pointer hits here is the
       ROW, which is pressable end to end and is exactly as tall as the tier, and
       the tier starts at 24 for this very reason. The disc is where the value is
       drawn, not the only place it can be taken.
       ========================================================================== */

    .ds-range {
      display: flex;
      flex-direction: column;
      gap: var(--ds-rhythm-line);
      min-inline-size: 0;
    }

    /* The row above the rail: what it is on the left, where it is on the right.
       The same arrangement Progress uses over the same shape, so a page carrying
       both does not read as two different objects. */
    .ds-range__head {
      --ds-range-head-face: var(--ds-font-body-face);
      display: flex;
      align-items: baseline;
      justify-content: space-between;
      gap: var(--ds-space-4);
      font-family: var(--ds-range-head-face);
    }

    .ds-range__label {
      --ds-range-label-ink: var(--ds-text-primary-neutral);
      color: var(--ds-range-label-ink);
      min-inline-size: 0;
    }

    /* Off the page and still said, the way a field hides its own label. The name
       is required either way: what is optional is whether it is drawn. */
    .ds-range__label--hidden {
      position: absolute;
      inline-size: 1px;
      block-size: 1px;
      margin: -1px;
      padding: 0;
      overflow: hidden;
      clip-path: inset(50%);
      white-space: nowrap;
    }

    .ds-range__value {
      --ds-range-value-ink: var(--ds-text-secondary-neutral);
      color: var(--ds-range-value-ink);
      /* Tabular, so the number does not shuffle its neighbours while it is being
         dragged, which is the whole time anybody is reading it. */
      font-variant-numeric: tabular-nums;
      flex: none;
    }

    /* ── The ramp, read again ─────────────────────────────────────────────────
       The six tiers every control stands on, because a range in a filter panel
       sits above a field and beside a button. The type comes off the same map:
       12 at the bottom tier, 14 at the next, 16 from the middle up, written onto
       the element that wears it because a font token varies with the surface. */
    .ds-range--xsmall { --ds-range-tier: var(--ds-size-1); --ds-range-rail: var(--ds-space-1); }
    .ds-range--xsmall .ds-range__head {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-range-head-face: var(--ds-font-small-face);
      --ds-range-head-tracking: var(--ds-font-small-tracking);
      --ds-range-head-size: var(--ds-font-small-size);
      --ds-range-head-weight: var(--ds-font-small-weight);
      --ds-range-head-leading: var(--ds-font-small-leading);
      font-family: var(--ds-range-head-face);
     font-size: var(--ds-range-head-size); line-height: var(--ds-range-head-leading); 
      letter-spacing: var(--ds-range-head-tracking);
      font-weight: var(--ds-range-head-weight);}
    .ds-range--small { --ds-range-tier: var(--ds-size-2); --ds-range-rail: var(--ds-space-1); }
    .ds-range--small .ds-range__head {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-range-head-face: var(--ds-font-label-face);
      --ds-range-head-tracking: var(--ds-font-label-tracking);
      --ds-range-head-size: var(--ds-font-label-size);
      --ds-range-head-weight: var(--ds-font-label-weight);
      --ds-range-head-leading: var(--ds-font-label-leading);
      font-family: var(--ds-range-head-face);
     font-size: var(--ds-range-head-size); line-height: var(--ds-range-head-leading); 
      letter-spacing: var(--ds-range-head-tracking);
      font-weight: var(--ds-range-head-weight);}
    .ds-range--medium { --ds-range-tier: var(--ds-size-3); --ds-range-rail: var(--ds-space-2); }
    .ds-range--medium .ds-range__head {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-range-head-face: var(--ds-font-body-face);
      --ds-range-head-tracking: var(--ds-font-body-tracking);
      --ds-range-head-size: var(--ds-font-body-size);
      --ds-range-head-weight: var(--ds-font-body-weight);
      --ds-range-head-leading: var(--ds-font-body-leading);
      font-family: var(--ds-range-head-face);
     font-size: var(--ds-range-head-size); line-height: var(--ds-range-head-leading); 
      letter-spacing: var(--ds-range-head-tracking);
      font-weight: var(--ds-range-head-weight);}
    .ds-range--large { --ds-range-tier: var(--ds-size-4); --ds-range-rail: var(--ds-space-2); }
    .ds-range--large .ds-range__head {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-range-head-face: var(--ds-font-body-face);
      --ds-range-head-tracking: var(--ds-font-body-tracking);
      --ds-range-head-size: var(--ds-font-body-size);
      --ds-range-head-weight: var(--ds-font-body-weight);
      --ds-range-head-leading: var(--ds-font-body-leading);
      font-family: var(--ds-range-head-face);
     font-size: var(--ds-range-head-size); line-height: var(--ds-range-head-leading); 
      letter-spacing: var(--ds-range-head-tracking);
      font-weight: var(--ds-range-head-weight);}
    .ds-range--xlarge { --ds-range-tier: var(--ds-size-5); --ds-range-rail: var(--ds-space-2); }
    .ds-range--xlarge .ds-range__head {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-range-head-face: var(--ds-font-body-face);
      --ds-range-head-tracking: var(--ds-font-body-tracking);
      --ds-range-head-size: var(--ds-font-body-size);
      --ds-range-head-weight: var(--ds-font-body-weight);
      --ds-range-head-leading: var(--ds-font-body-leading);
      font-family: var(--ds-range-head-face);
     font-size: var(--ds-range-head-size); line-height: var(--ds-range-head-leading); 
      letter-spacing: var(--ds-range-head-tracking);
      font-weight: var(--ds-range-head-weight);}
    .ds-range--xxlarge { --ds-range-tier: var(--ds-size-6); --ds-range-rail: var(--ds-space-2); }
    .ds-range--xxlarge .ds-range__head {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-range-head-face: var(--ds-font-body-face);
      --ds-range-head-tracking: var(--ds-font-body-tracking);
      --ds-range-head-size: var(--ds-font-body-size);
      --ds-range-head-weight: var(--ds-font-body-weight);
      --ds-range-head-leading: var(--ds-font-body-leading);
      font-family: var(--ds-range-head-face);
     font-size: var(--ds-range-head-size); line-height: var(--ds-range-head-leading); 
      letter-spacing: var(--ds-range-head-tracking);
      font-weight: var(--ds-range-head-weight);}

    /* The row. Its whole height is the target, which is why the rail inside it is
       a child and not this element's own background. */
    .ds-range__row {
      position: relative;
      display: flex;
      align-items: center;
      min-block-size: var(--ds-range-tier);
      /* Half a handle at each end, so the handle at either extreme is inside the
         component rather than hanging over the thing next to it. The rail gives up
         the same room, which is what keeps the ends of the rail under the ends of
         the travel. */
      padding-inline: calc(var(--ds-range-handle) / 2);
      touch-action: none;
      cursor: pointer;
      --ds-range-handle: calc(var(--ds-range-tier) / 2);
    }

    .ds-range__rail {
      /* A span is inline until it is told otherwise, and an inline box ignores
         every size it is given: without this the rail is a 4px line of nothing
         with two handles hung off it. The check asks any painted box to say how it
         lays out, and it asked this one on the way in. */
      display: block;
      position: relative;
      inline-size: 100%;
      block-size: var(--ds-range-rail);
      /* The part not chosen is a step of the surface ladder, which is the deepest
         the stack goes and is what a well is made of here. It was the wash that
         marks a row as SELECTED, which is a state and not a ground: a token
         calibrated for one job doing another, and one that lands differently
         depending on what the rail happens to sit on, so the same control came out
         paler in a panel than on the page. */
      background-color: var(--ds-surface-3);
      border-radius: var(--ds-radius-full);
      transition: block-size var(--ds-duration) var(--ds-ease-out);
    }

    /* Held, the whole thing swells and not only the grip: the bar is the object
       being touched, and a disc that grows over a line that does not reads as a
       bead popping off a wire. Twice the rail, which stays on the 4px step because
       the resting thickness is on it, and it is drawn INSIDE the row: the row is
       the tier and does not move, so nothing under the control shifts down while a
       finger is on it. */
    .ds-range--held .ds-range__rail {
      block-size: calc(var(--ds-range-rail) * 2);
    }

    /* What is chosen, between the two handles or from the start to the one. */
    .ds-range__fill {
      position: absolute;
      inset-block: 0;
      inset-inline-start: var(--ds-range-from);
      inline-size: calc(var(--ds-range-to) - var(--ds-range-from));
      background-color: var(--ds-inverse);
      border-radius: inherit;
    }

    /* Placed by its own share of the travel. No transition on the position: a
       handle that eases toward the pointer is a handle that is not where the
       pointer is, and every measurement taken of one mid-drag reads the number it
       is leaving rather than the number it was given. Colour transitions, which
       nothing is dragging. */
    .ds-range__handle {
      position: absolute;
      inset-inline-start: var(--ds-range-at);
      /* Centred on the rail across the block axis. An absolutely positioned box
         given no block offset keeps its static one, which here is the TOP of a
         4px rail, so the handle hung under the line by half itself: visible on the
         page and invisible in a width. Half of it back up, and the pair of margins
         below is the whole of how it is centred, on both axes. */
      inset-block-start: 50%;
      margin-block-start: calc(var(--ds-range-handle) / -2);
      inline-size: var(--ds-range-handle);
      block-size: var(--ds-range-handle);
      /* Centred on its own value by a logical margin rather than by a translate:
         a translate is written in physical x and would put the handle on the wrong
         side of its number the moment the page runs right to left. */
      margin-inline-start: calc(var(--ds-range-handle) / -2);
      padding: 0;
      /* Filled with the page's own opposite, the same colour as the part of the
         rail it ends: white on a dark page, near-black on a light one, so the
         chosen part and the grip on it read as one object.
         Which is exactly why the line is here. A disc the same colour as what it
         stands on has no edge where the two meet, and the handle stands on its own
         fill for the whole of its travel. `--ds-border` is the control boundary
         of the system and it runs the other way from the fill by construction:
         light grey on the light theme, dark grey on the dark one. It costs no
         room, since everything with a border here is border-box, so the target is
         still the 24px the ramp promises. */
      border: 1px solid var(--ds-border);
      border-radius: var(--ds-radius-full);
      background-color: var(--ds-inverse);
      cursor: grab;
      transition:
        background-color var(--ds-duration) var(--ds-ease),
        /* The same length the rail swells over, because the two are one movement
           and a grip that arrives before its bar is two. It ran over the fast
           token first, which is the length of a small correction rather than of
           something growing in the hand, and it read as a snap. */
        scale var(--ds-duration) var(--ds-ease-out);
    }

    .ds-range__handle:hover {
      background-color: var(--ds-inverse-hover);
    }

    /* Held. Half again as big, under the finger and for as long as the finger is
       down, so the disc is not the thing the hand is covering while it is being
       aimed. It scales rather than resizing: a bigger box would move the value it
       is centred on, and the target the ramp promises is the box, not the paint.
       The class is here because the press starts anywhere on the row, so `:active`
       is on whatever was pressed and not on the handle that answered. */
    .ds-range__handle--held,
    .ds-range__handle:active {
      background-color: var(--ds-inverse-active);
      scale: 1.5;
      cursor: grabbing;
    }

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

    /* Off, and saying so on every part that carried a colour: the rail loses its
       fill, the handle loses its edge, the numbers go quiet. The row keeps the
       cursor, because the cursor is the answer to a pointer that is already on
       the way. */
    .ds-range--disabled .ds-range__row {
      cursor: not-allowed;
    }

    /* The fill of a control that is switched off, which is a token of its own and
       not a wash borrowed from somewhere else. */
    .ds-range--disabled .ds-range__fill {
      background-color: var(--ds-surface-disabled);
    }

    /* The handle stays a handle when the control is off limits: everything around
       it greys, and a disc the colour of its own rail is a range with no grip on
       it at all. */
    .ds-range--disabled .ds-range__handle {
      background-color: var(--ds-text-disabled);
      border-color: var(--ds-border-disabled);
      cursor: not-allowed;
    }

    .ds-range--disabled .ds-range__label,
    .ds-range--disabled .ds-range__value {
      --ds-range-value-ink: var(--ds-text-disabled);
      color: var(--ds-range-value-ink);
    }

    /* ==========================================================================
       Date field

       The CSS behind <DateField>. Private, like every other component's.

       A field you type a date into, with a month to point at when typing is not
       how somebody has the date in their head. Both ways in, always: a reader who
       knows the date types four digits faster than anybody can page a calendar to
       1987, and a reader choosing "the Thursday after next" has no date to type.

       The frame, the label, the message and the tiers are the field's own skin,
       worn on the same element. What is here is the button that opens the month,
       and the month.
       ========================================================================== */

    /* The mark that opens the panel, inside the frame at the end of it. A button
       and not an icon, because it is pressed: an icon that opens something and
       cannot be tabbed to is a control for a pointer and for nobody else. */
    .ds-date-field__open {
      display: inline-flex;
      align-items: center;
      justify-content: center;
      flex: none;
      /* Square on the field's own inner height, so it fills the end of the frame
         without pushing it taller. */
      inline-size: 1.5em;
      block-size: 1.5em;
      padding: 0;
      border: 0;
      border-radius: var(--ds-radius-1);
      background: none;
      --ds-date-field-open-ink: var(--ds-text-tertiary-neutral);
      color: var(--ds-date-field-open-ink);
      cursor: pointer;
      transition: color var(--ds-duration) var(--ds-ease);
    }

    .ds-date-field__open > svg {
      inline-size: 100%;
      block-size: 100%;
    }

    .ds-date-field__open:hover:not(:disabled) {
      --ds-date-field-open-ink: var(--ds-text-primary-neutral);
      color: var(--ds-date-field-open-ink);
    }

    .ds-date-field__open:focus-visible {
      outline: 2px solid var(--ds-focus);
      outline-offset: 1px;
    }

    .ds-date-field__open:disabled {
      --ds-date-field-open-ink: var(--ds-text-disabled);
      color: var(--ds-date-field-open-ink);
      cursor: not-allowed;
    }

    /* ── The month ────────────────────────────────────────────────────────────
       The panel is the popover's, so opening, placing, flipping and closing are
       the system's and not this component's. What is below is the month inside
       it. */
    .ds-date-field__panel {
      padding: var(--ds-space-3);
    }

    /* Which month, and the two ways out of it. The name is in the middle and the
       arrows sit at the ends, so the eye lands on the word rather than hunting
       between two identical marks. */
    .ds-date-field__head {
      display: flex;
      align-items: center;
      justify-content: space-between;
      gap: var(--ds-space-2);
      padding-block-end: var(--ds-space-3);
    }

    .ds-date-field__month {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-date-field-month-face: var(--ds-font-label-face);
      --ds-date-field-month-tracking: var(--ds-font-label-tracking);
      --ds-date-field-month-size: var(--ds-font-label-size);
      --ds-date-field-month-leading: var(--ds-font-label-leading);
      /* This component's own. The role says what the words are; this says that
         THIS component wants a heavier hand than the role has, which is a
         decision the component makes and nobody else. Pointed at the semantic
         rather than holding a number, so moving the system moves every component
         that has not been moved on purpose, and moving one moves one. */
      --ds-date-field-weight: var(--ds-emphasis-weight);
      font-family: var(--ds-date-field-month-face);
      font-size: var(--ds-date-field-month-size);
      letter-spacing: var(--ds-date-field-month-tracking);
      line-height: var(--ds-date-field-month-leading);
      font-weight: var(--ds-date-field-weight);
      --ds-date-field-month-ink: var(--ds-text-primary-neutral);
      color: var(--ds-date-field-month-ink);
    }

    .ds-date-field__step {
      display: inline-flex;
      align-items: center;
      justify-content: center;
      flex: none;
      /* A tier of the ramp rather than the floor of it: this is a target somebody
         presses twelve times to cross a year, and the chevron in it is the easiest
         thing in a calendar to miss. */
      inline-size: var(--ds-size-2);
      block-size: var(--ds-size-2);
      padding: 0;
      border: 0;
      /* Round, and standing on a step of the surface ladder rather than on
         nothing. Two bare chevrons at the ends of a line read as decoration on the
         month name; a disc reads as a thing to press, which is what they are and
         what they are pressed twelve times to do. */
      border-radius: var(--ds-radius-full);
      background-color: var(--ds-surface-2);
      --ds-date-field-step-ink: var(--ds-text-secondary-neutral);
      color: var(--ds-date-field-step-ink);
      cursor: pointer;
      transition:
        background-color var(--ds-duration) var(--ds-ease),
        color var(--ds-duration) var(--ds-ease);
    }

    .ds-date-field__step > svg {
      /* The height of the line beside it, like every other mark in the package,
         rather than a fixed rem that stays 16 while the field grows. */
      inline-size: 1lh;
      block-size: 1lh;
    }

    .ds-date-field__step:hover:not(:disabled) {
      background-color: var(--ds-surface-3);
      --ds-date-field-step-ink: var(--ds-text-primary-neutral);
      color: var(--ds-date-field-step-ink);
    }

    .ds-date-field__step:focus-visible {
      outline: 2px solid var(--ds-focus);
      outline-offset: 2px;
    }

    .ds-date-field__step:disabled {
      --ds-date-field-step-ink: var(--ds-text-disabled);
      color: var(--ds-date-field-step-ink);
      cursor: not-allowed;
    }

    /* ── What is chosen, when two dates are ──────────────────────────────────
       The pair, drawn as the two ends and the run between them. The band is the
       cell's own background rather than a bar behind the grid: a bar would have to
       know where the gaps are, and the gaps are the gutter of a grid that changes
       width with the language of its weekday names. */
    .ds-date-field__day--within {
      background-color: var(--ds-surface-active);
      border-radius: 0;
    }

    /* The ends keep the round they always had, on the side that faces out. */
    .ds-date-field__day--from {
      border-start-end-radius: 0;
      border-end-end-radius: 0;
    }

    .ds-date-field__day--to {
      border-start-start-radius: 0;
      border-end-start-radius: 0;
    }

    /* ── The shortcuts ───────────────────────────────────────────────────────
       A row of answers somebody is likely to want: tonight, tomorrow, this week.
       They are the chips the rest of the system uses, so a reader who has pressed
       one anywhere else knows what these are. */
    .ds-date-field__presets {
      display: flex;
      flex-wrap: wrap;
      gap: var(--ds-space-1);
      padding-block-end: var(--ds-space-3);
    }

    /* ── The line under the number ───────────────────────────────────────────
       A price, a count, a dot: whatever the caller has for that day. It is given
       the room whether it is filled or not, so a month with one price in it does
       not have one row taller than the other five. */
    .ds-date-field__note {
      display: block;
      /* The `small` role, and this is a fix in two places. It was `0.625rem`, a
         10px no token holds and the only size in the package below the scale's
         floor of 12; and its leading was `--ds-space-3`, a SPACE token standing in
         a line's slot, which is the one thing this system says a token may never
         do. Reads 12 on 16 now, where it read 10 on 12. */
      --ds-date-field-note-face: var(--ds-font-small-face);
      --ds-date-field-note-size: var(--ds-font-small-size);
      --ds-date-field-note-leading: var(--ds-font-small-leading);
      --ds-date-field-note-weight: var(--ds-font-small-weight);
      --ds-date-field-note-tracking: var(--ds-font-small-tracking);
      font-family: var(--ds-date-field-note-face);
      font-size: var(--ds-date-field-note-size);
      line-height: var(--ds-date-field-note-leading);
      font-weight: var(--ds-date-field-note-weight);
      letter-spacing: var(--ds-date-field-note-tracking);
      --ds-date-field-note-ink: var(--ds-text-tertiary-neutral);
      color: var(--ds-date-field-note-ink);
    }

    .ds-date-field__day[aria-selected="true"] .ds-date-field__note {
      color: inherit;
    }

    /* A day that carries a note is taller than one that does not, so the whole
       grid takes the taller box: the numbers stay on one baseline down the month
       rather than each row finding its own. */
    .ds-date-field--noted .ds-date-field__day {
      flex-direction: column;
      gap: 0;
      block-size: auto;
      min-block-size: calc(var(--ds-size-2) + var(--ds-space-3));
      padding-block: var(--ds-space-1);
    }

    /* Seven columns of one width, so a month of 31 days and one of 28 are the
       same shape and the panel does not resize under the pointer paging it. The
       square is a tier up from the floor: at the floor the month came out as a
       block of numbers with nothing between them, which is a table of figures
       rather than something to point at. */
    .ds-date-field__grid {
      display: grid;
      grid-template-columns: repeat(7, var(--ds-size-2));
      gap: var(--ds-space-1);
    }

    .ds-date-field__weekday {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-date-field-weekday-face: var(--ds-font-small-face);
      --ds-date-field-weekday-tracking: var(--ds-font-small-tracking);
      --ds-date-field-weekday-size: var(--ds-font-small-size);
      --ds-date-field-weekday-weight: var(--ds-font-small-weight);
      --ds-date-field-weekday-leading: var(--ds-font-small-leading);
      display: flex;
      align-items: center;
      justify-content: center;
      block-size: var(--ds-size-1);
      inline-size: var(--ds-size-2);
      font-family: var(--ds-date-field-weekday-face);
      font-size: var(--ds-date-field-weekday-size);
      letter-spacing: var(--ds-date-field-weekday-tracking);
      font-weight: var(--ds-date-field-weekday-weight);
      line-height: var(--ds-date-field-weekday-leading);
      --ds-date-field-weekday-ink: var(--ds-text-tertiary-neutral);
      color: var(--ds-date-field-weekday-ink);
    }

    /* A day. Square, and a whole tier of the ramp: the number inside it reads at
       the size a label does, which is what turns a month from a block of figures
       into a row of things to press. */
    .ds-date-field__day {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-date-field-day-face: var(--ds-font-label-face);
      --ds-date-field-day-tracking: var(--ds-font-label-tracking);
      --ds-date-field-day-size: var(--ds-font-label-size);
      --ds-date-field-day-weight: var(--ds-font-label-weight);
      --ds-date-field-day-leading: var(--ds-font-label-leading);
      display: flex;
      align-items: center;
      justify-content: center;
      inline-size: var(--ds-size-2);
      block-size: var(--ds-size-2);
      padding: 0;
      border: 1px solid transparent;
      border-radius: var(--ds-radius-1);
      background: none;
      font-family: var(--ds-date-field-day-face);
      font-size: var(--ds-date-field-day-size);
      letter-spacing: var(--ds-date-field-day-tracking);
      font-weight: var(--ds-date-field-day-weight);
      line-height: var(--ds-date-field-day-leading);
      font-variant-numeric: tabular-nums;
      --ds-date-field-day-ink: var(--ds-text-primary-neutral);
      color: var(--ds-date-field-day-ink);
      cursor: pointer;
      transition:
        background-color var(--ds-duration) var(--ds-ease),
        border-color var(--ds-duration) var(--ds-ease),
        color var(--ds-duration) var(--ds-ease);
    }

    .ds-date-field__day:hover:not(:disabled):not([aria-selected="true"]) {
      background-color: var(--ds-surface-hover);
    }

    .ds-date-field__day:focus-visible {
      outline: 2px solid var(--ds-focus);
      outline-offset: -1px;
    }

    /* The days either side of this month. Shown rather than left blank, because
       the last Monday of a month is often the first week of the next one and a
       hole in the grid is a week that looks like it does not exist. Quiet, since
       pressing one moves the panel as well as choosing. */
    .ds-date-field__day--outside {
      --ds-date-field-day--outside-ink: var(--ds-text-tertiary-neutral);
      color: var(--ds-date-field-day--outside-ink);
    }

    /* Today. An edge and not a fill, so it cannot be mistaken for the day that is
       chosen: one of them is a fact about the calendar and the other is an answer
       the reader gave. */
    .ds-date-field__day--today {
      border-color: var(--ds-border);
    }

    .ds-date-field__day[aria-selected="true"] {
      background-color: var(--ds-inverse);
      border-color: var(--ds-inverse);
      --ds-date-field-day-ink: var(--ds-on-inverse);
      color: var(--ds-date-field-day-ink);
    }

    .ds-date-field__day:disabled {
      --ds-date-field-day-ink: var(--ds-text-disabled);
      color: var(--ds-date-field-day-ink);
      cursor: not-allowed;
    }

    /* A value edited where it stands.
     *
     * At rest it is a button that reads as the value, and its box is the whole
     * of the slot: the target is the word, not a pencil beside it. Pressed, the
     * field takes its place at the same tier, so the row it sits in does not
     * move. The tier is this component's decision and the consumer may move it
     * by name; the type is the label role, since a value in a cell or a
     * property row is a label's size, and every half of it is declared here.
     */
    .ds-inline-edit {
      --ds-inline-edit-tier: var(--ds-size-2);
      /* The value's type is the field's type at the same tier, role for role,
         so the letters do not change size when the field takes the value's
         place: small at xsmall, label at small, body from medium up. */
      --ds-inline-edit-value-face: var(--ds-font-label-face);
      --ds-inline-edit-value-size: var(--ds-font-label-size);
      --ds-inline-edit-value-leading: var(--ds-font-label-leading);
      --ds-inline-edit-value-weight: var(--ds-font-label-weight);
      --ds-inline-edit-value-tracking: var(--ds-font-label-tracking);
      /* The value's box is the field's box: the same inset the field takes at
         this tier, behind a line of the same width drawn in nothing, so the text
         does not move by a pixel when one replaces the other. */
      --ds-inline-edit-inset: var(--ds-space-3);
      display: grid;
      inline-size: 100%;
      min-inline-size: 0;
    }

    .ds-inline-edit--xsmall {
      --ds-inline-edit-tier: var(--ds-size-1);
      --ds-inline-edit-inset: var(--ds-space-2);
      --ds-inline-edit-value-face: var(--ds-font-small-face);
      --ds-inline-edit-value-size: var(--ds-font-small-size);
      --ds-inline-edit-value-leading: var(--ds-font-small-leading);
      --ds-inline-edit-value-weight: var(--ds-font-small-weight);
      --ds-inline-edit-value-tracking: var(--ds-font-small-tracking);
    }

    .ds-inline-edit--medium,
    .ds-inline-edit--large,
    .ds-inline-edit--xlarge,
    .ds-inline-edit--xxlarge {
      --ds-inline-edit-value-face: var(--ds-font-body-face);
      --ds-inline-edit-value-size: var(--ds-font-body-size);
      --ds-inline-edit-value-leading: var(--ds-font-body-leading);
      --ds-inline-edit-value-weight: var(--ds-font-body-weight);
      --ds-inline-edit-value-tracking: var(--ds-font-body-tracking);
    }

    .ds-inline-edit--medium {
      --ds-inline-edit-tier: var(--ds-size-3);
      --ds-inline-edit-inset: var(--ds-space-3);
    }

    .ds-inline-edit--large {
      --ds-inline-edit-tier: var(--ds-size-4);
      --ds-inline-edit-inset: var(--ds-space-4);
    }

    .ds-inline-edit--xlarge {
      --ds-inline-edit-tier: var(--ds-size-5);
      --ds-inline-edit-inset: var(--ds-space-4);
    }

    .ds-inline-edit--xxlarge {
      --ds-inline-edit-tier: var(--ds-size-6);
      --ds-inline-edit-inset: var(--ds-space-5);
    }

    /* Declared only while it is shown: `display: block` on the class beat the
       `hidden` attribute's own `display: none`, so the value stayed on screen
       with the field under it and the row grew by a tier on every press. Same
       rule as the field below, and for the same reason. */
    .ds-inline-edit__value:not([hidden]) {
      --ds-inline-edit-value-ink: var(--ds-text-primary-neutral);
      --ds-inline-edit-empty-ink: var(--ds-text-tertiary-neutral);
      /* The wash under the pointer, and the pad that centres the line in the
         tier: both named, so a reading of the box answers with a decision and
         not with a number. The pad is what Button holds as a bare calc; here it
         carries the component's name, since a value's box is read far more often
         than a button's. */
      --ds-inline-edit-wash: var(--ds-surface-hover);
      --ds-inline-edit-value-pad: calc((var(--ds-inline-edit-tier) - var(--ds-inline-edit-value-leading) - 2px) / 2);
      display: block;
      box-sizing: border-box;
      inline-size: 100%;
      min-inline-size: 0;
      min-block-size: var(--ds-inline-edit-tier);
      /* What centres the line in the tier, the way Button holds its own: the
         tier less the leading and the two lines, halved. */
      padding-block: var(--ds-inline-edit-value-pad);
      padding-inline: var(--ds-inline-edit-inset);
      margin: 0;
      border: 1px solid transparent;
      border-radius: var(--ds-radius-2);
      background-color: transparent;
      font-family: var(--ds-inline-edit-value-face);
      font-size: var(--ds-inline-edit-value-size);
      line-height: var(--ds-inline-edit-value-leading);
      font-weight: var(--ds-inline-edit-value-weight);
      letter-spacing: var(--ds-inline-edit-value-tracking);
      color: var(--ds-inline-edit-value-ink);
      text-align: start;
      white-space: nowrap;
      overflow: hidden;
      text-overflow: ellipsis;
      cursor: text;
      transition: background-color var(--ds-duration) var(--ds-ease);
    }

    .ds-inline-edit__value:hover {
      background-color: var(--ds-inline-edit-wash);
    }

    .ds-inline-edit__value:focus-visible {
      outline: 2px solid var(--ds-focus);
      outline-offset: 2px;
    }

    .ds-inline-edit__value--empty {
      color: var(--ds-inline-edit-empty-ink);
    }

    /* A choice is pressed, not typed into. */
    .ds-inline-edit--choice .ds-inline-edit__value {
      cursor: pointer;
    }

    /* Shut has to mean absent: the block is declared only while it is shown, so
       `hidden` keeps the whole of what a box takes from a layout. */
    .ds-inline-edit__field:not([hidden]) {
      display: block;
    }

    /* ==========================================================================
       DropZone

       The CSS behind <DropZone>. Private, like every other component's.

       The box you drop a file on, and the button that opens the picker, and they
       are the same box.

       ── It is the target, and the upload is the page's ───────────────────────
       The bucket, the resize, the retry and the queue belong to whatever is
       storing the file: that part is different on every runtime, so each one
       writes its own. What comes back, a row per file and a Progress and a
       thumbnail, the page composes out of components it already has.

       ── The input covers the box, and that is the whole trick ────────────────
       `<input type="file">` is already a drop target and already opens a picker
       when it is activated. Stretched over the box at zero opacity it makes the
       whole area do both, natively: a file dropped anywhere lands in the input,
       a click anywhere opens the picker, Tab reaches it, Enter opens it. So the
       thing works with the stylesheet and no script at all, which matters here,
       because the PHP renderer would otherwise need script for a plain upload
       form to work at all.

       `opacity: 0` and not `display: none` or `visibility: hidden`: both of those
       take the element out of hit testing, and a drop target that cannot be hit
       is not a drop target.

       The one thing script adds is the highlight while a file is over the box.
       CSS has no `:drop`, so both renderers set `data-over` on the box and the
       rule for it lives below. Without the script you lose the highlight and
       keep the function, which is the right way round.
       ========================================================================== */

    .ds-drop-zone {
      display: flex;
      flex-direction: column;
      gap: var(--ds-rhythm-line);
    }

    /* The box. `position: relative` so the input can cover it, and the padding is
       what makes it look like an area rather than a control: a drop target has to
       read as somewhere you can aim at from across the screen while dragging. */
    .ds-drop-zone__box {
      box-sizing: border-box;
      position: relative;
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      text-align: center;
      /* Between the mark and the words. The element step, because the two are
         different things: at the line step the mark pressed against the
         instruction, and the owner said so on 22 August 2026. Measured 4px
         before, 16px after. */
      gap: var(--ds-rhythm-element);
      padding: var(--ds-drop-zone-inset);
      /* The well, not the sheet: it is a place things go into. */
      background-color: var(--ds-surface-2);
      /* Dashed, and this is the only dashed edge in the package. It is the one
         convention a file drop target has that people already read without being
         told, and spending it here buys the whole affordance for one line.

         The STRONG step and not the one a field wears, which is the opposite of
         the rule everywhere else in this package. A field says what it is with its
         ground, its placeholder and its label, so its edge can be quiet; this box
         has nothing but the edge to say where the target ends, which makes the
         edge the boundary of a control and puts it under WCAG 1.4.11. Measured on
         surface 2: `--ds-border` gives 1.36:1, this gives 3.56:1. */
      border: 1px dashed var(--ds-border-strong);
      border-radius: var(--ds-radius-2);
      transition:
        border-color var(--ds-duration) var(--ds-ease),
        background-color var(--ds-duration) var(--ds-ease);
    }

    .ds-drop-zone--panel .ds-drop-zone__box {
      --ds-drop-zone-inset: var(--ds-space-10);
    }

    /* Beside a list of files that are already there, or in a table cell, where the
       box gets the room a couple of rows would have used and no more. */
    .ds-drop-zone--inline .ds-drop-zone__box {
      --ds-drop-zone-inset: var(--ds-space-4);
      flex-direction: row;
      justify-content: flex-start;
      text-align: start;
    }

    .ds-drop-zone__box:hover:not(:has(:disabled)) {
      border-color: var(--ds-border-active);
    }

    /* On the box and not on the input, so the ring is drawn around the thing you
       can see rather than around a transparent rectangle on top of it. */
    .ds-drop-zone__box:focus-within {
      border-color: var(--ds-focus);
      box-shadow: 0 0 0 1px var(--ds-focus);
    }

    /* Something is being dragged over it. Set by script, because CSS has no way
       to ask. Solid rather than dashed: the dashes said "aim here", and once you
       have aimed the line has a different job, which is to say that letting go
       will land. */
    .ds-drop-zone__box[data-over] {
      border-style: solid;
      border-color: var(--ds-border-active);
      background-color: var(--ds-surface-1);
    }

    .ds-drop-zone__box:has(:disabled) {
      border-color: var(--ds-border-disabled);
      cursor: not-allowed;
    }

    /* The input itself: the whole box, invisible, and the reason any of this
       works. `font-size: 0` because a stretched file input still lays out its own
       button and filename text inside itself, and that text otherwise sets a
       minimum width the box has to honour. */
    .ds-drop-zone__input {
      position: absolute;
      inset: 0;
      inline-size: 100%;
      block-size: 100%;
      opacity: 0;
      font-size: 0;
      cursor: pointer;
    }

    .ds-drop-zone__input:disabled {
      cursor: not-allowed;
    }

    .ds-drop-zone__mark {
      display: inline-flex;
      /* A fixed mark in a row of shrinkable things. The svg fills its box, so the
         box has no min-content to defend itself with: in the inline fit the row
         squeezed it to 0px wide before this line. */
      flex: none;
      align-items: center;
      justify-content: center;
      /* Scenery, like the state block's: the line under it is the sentence, and a
         mark that competes with it gets read first and understood second. */
      --ds-drop-zone-mark-ink: var(--ds-text-tertiary-neutral);
      color: var(--ds-drop-zone-mark-ink);
      /* A box, not type, so the tier token sizes the box and the svg fills it.
         It was `font-size: var(--ds-size-3)` with the svg at 1em, a geometry
         token in a type slot, and at 40px the mark shouted over the sentence
         under it. Owner's call, 22 August 2026: smaller. Measured 40px before,
         32px after, 24px inline. */
      --ds-drop-zone-mark-size: var(--ds-size-2);
      inline-size: var(--ds-drop-zone-mark-size);
      block-size: var(--ds-drop-zone-mark-size);
    }

    .ds-drop-zone--inline .ds-drop-zone__mark {
      --ds-drop-zone-mark-size: var(--ds-size-1);
    }

    .ds-drop-zone__mark > svg {
      inline-size: 100%;
      block-size: 100%;
    }

    .ds-drop-zone__text {
      display: flex;
      flex-direction: column;
      gap: var(--ds-rhythm-line);
      align-items: center;
    }

    .ds-drop-zone--inline .ds-drop-zone__text {
      align-items: flex-start;
    }

    /* The instruction, and the accessible name of the input. A label element, so
       the picker opens from the words too: belt and braces with the covering
       input, and the reason the control has a name at all. */
    .ds-drop-zone__label {
      --ds-drop-zone-label-weight: var(--ds-emphasis-weight);
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-drop-zone-label-face: var(--ds-font-body-face);
      --ds-drop-zone-label-tracking: var(--ds-font-body-tracking);
      --ds-drop-zone-label-size: var(--ds-font-body-size);
      --ds-drop-zone-label-leading: var(--ds-font-body-leading);
      font-family: var(--ds-drop-zone-label-face);
      font-size: var(--ds-drop-zone-label-size);
      letter-spacing: var(--ds-drop-zone-label-tracking);
      line-height: var(--ds-drop-zone-label-leading);
      font-weight: var(--ds-drop-zone-label-weight);
      --ds-drop-zone-label-ink: var(--ds-text-primary-neutral);
      color: var(--ds-drop-zone-label-ink);
      cursor: pointer;
    }

    .ds-drop-zone__box:has(:disabled) .ds-drop-zone__label {
      --ds-drop-zone-label-ink: var(--ds-text-disabled);
      color: var(--ds-drop-zone-label-ink);
      cursor: not-allowed;
    }

    /* What it takes: the formats, the size, how many. Inside the box, because it
       is part of the instruction. */
    .ds-drop-zone__hint {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-drop-zone-hint-face: var(--ds-font-label-face);
      --ds-drop-zone-hint-tracking: var(--ds-font-label-tracking);
      --ds-drop-zone-hint-size: var(--ds-font-label-size);
      --ds-drop-zone-hint-weight: var(--ds-font-label-weight);
      --ds-drop-zone-hint-leading: var(--ds-font-label-leading);
      font-family: var(--ds-drop-zone-hint-face);
      font-size: var(--ds-drop-zone-hint-size);
      letter-spacing: var(--ds-drop-zone-hint-tracking);
      font-weight: var(--ds-drop-zone-hint-weight);
      line-height: var(--ds-drop-zone-hint-leading);
      --ds-drop-zone-hint-ink: var(--ds-text-secondary-neutral);
      color: var(--ds-drop-zone-hint-ink);
    }

    /* What the box is holding, under the hint. The label says what to give it and
       the hint says what it takes; this says what it HAS, and without it a file
       that was accepted looks exactly like a file that was refused. In the primary
       ink and at the label's weight, because it is the one line here that is the
       reader's own content rather than the component's instructions. */
    .ds-drop-zone__file {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. */
      --ds-drop-zone-file-face: var(--ds-font-label-face);
      --ds-drop-zone-file-tracking: var(--ds-font-label-tracking);
      --ds-drop-zone-file-size: var(--ds-font-label-size);
      --ds-drop-zone-file-leading: var(--ds-font-label-leading);
      --ds-drop-zone-file-weight: var(--ds-bold-weight);
      --ds-drop-zone-file-ink: var(--ds-text-primary-neutral);
      font-family: var(--ds-drop-zone-file-face);
      font-size: var(--ds-drop-zone-file-size);
      letter-spacing: var(--ds-drop-zone-file-tracking);
      font-weight: var(--ds-drop-zone-file-weight);
      line-height: var(--ds-drop-zone-file-leading);
      color: var(--ds-drop-zone-file-ink);
      /* A long name does not push the box wider than its column. */
      overflow-wrap: anywhere;
    }

    /* What went wrong, under the box.
       It does not replace the hint the way a field's message does, and the reason
       is that here the two do not compete: the hint is the rule and the error is
       the breach of it, and both have to be on screen at once to fix anything.
       "That file is 14 MB" is only actionable next to "max 10 MB". */
    .ds-drop-zone__message {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-drop-zone-message-face: var(--ds-font-label-face);
      --ds-drop-zone-message-tracking: var(--ds-font-label-tracking);
      --ds-drop-zone-message-size: var(--ds-font-label-size);
      --ds-drop-zone-message-weight: var(--ds-font-label-weight);
      --ds-drop-zone-message-leading: var(--ds-font-label-leading);
      font-family: var(--ds-drop-zone-message-face);
      font-size: var(--ds-drop-zone-message-size);
      letter-spacing: var(--ds-drop-zone-message-tracking);
      font-weight: var(--ds-drop-zone-message-weight);
      line-height: var(--ds-drop-zone-message-leading);
      color: var(--ds-danger-ink);
    }

    .ds-drop-zone--invalid .ds-drop-zone__box {
      border-color: var(--ds-danger-ink);
    }

    .ds-drop-zone--invalid .ds-drop-zone__box:focus-within {
      box-shadow: 0 0 0 1px var(--ds-danger-ink);
    }

    /* ==========================================================================
       Sortable

       The CSS behind <Sortable>. Private, like every other component's.

       A list whose order is the data, changed by the reader.

       ── Why it is not a description list ─────────────────────────────────────
       A description list is a set of pairs to be read, and the order is the
       author's. This is a list where the order IS the value being edited: the
       sections of a page, the images of a gallery, the steps of a form. Nothing
       in it is read for its own sake; it is read to be arranged.

       ── The grip picks it up, the item catches it ────────────────────────────
       `draggable` sits on the grip and not on the row, because a row here often
       holds a text field, and `draggable` on an ancestor takes away the ability
       to select the text inside one. The row stays the drop target, so the
       landing area is the whole row and the aim is the small part.

       The grip is a button, always visible. Dragging is a pointer gesture and a
       keyboard has none, so the arrows on that button move the item instead. A
       grip that fades in on hover hides the one thing that says the list can be
       arranged from everybody who was not already dragging: an affordance nobody
       can see is a feature nobody has.

       ── The line, not the shaded row ─────────────────────────────────────────
       The gap the item will fall into is drawn as a line above the row under the
       pointer. A shaded row says the row you are over is the one moving, which
       is the opposite of what is happening.
       ========================================================================== */

    .ds-sortable {
      /* The wrapper exists for one reason: the live region has to sit beside the
         list rather than in it, because nothing but an <li> may be a child of a
         <ul>. It is also what the absolutely positioned region is positioned
         against. */
      position: relative;
    }

    .ds-sortable__list {
      display: flex;
      flex-direction: column;
      gap: var(--ds-rhythm-line);
      margin: 0;
      padding: 0;
      list-style: none;
    }

    .ds-sortable__item {
      box-sizing: border-box;
      position: relative;
      display: flex;
      align-items: center;
      gap: var(--ds-space-3);
      padding: var(--ds-space-2) var(--ds-space-3);
      background-color: var(--ds-surface-1);
      border: 1px solid var(--ds-border);
      border-radius: var(--ds-radius-2);
      transition: opacity var(--ds-duration) var(--ds-ease);
    }

    /* The one in the air. Faded rather than hidden: the list keeps its length
       while something is being carried, so nothing under the pointer moves out
       from under it. */
    .ds-sortable__item--dragging {
      opacity: 0.4;
    }

    /* Where it lands. A line in the gap above, drawn on the item rather than
       between them, because there is no element in a gap to draw on.

       A border and not a filled 2px box, which is what it was first: the token is
       the colour of a LINE, and a box painted with it is a line token doing a
       fill's job. The check said so. */
    .ds-sortable__item--over::before {
      content: "";
      position: absolute;
      inset-inline: 0;
      inset-block-start: calc(var(--ds-rhythm-line) / -2 - 1px);
      block-size: 0;
      border-block-start: 2px solid var(--ds-border-active);
    }

    .ds-sortable__grip {
      position: relative;
      display: inline-flex;
      flex: none;
      /* A button inherits neither the face nor the leading, so `1lh` resolves
         against `normal` and the mark comes out 16 whatever the line beside it is.
         Measured 16 in a 24px line before this. */
      font: inherit;
      /* The height of the line beside it, like every other mark in the package:
         an item of one line and an item of three both get a grip that matches the
         first line of what it is holding. */
      inline-size: 1lh;
      block-size: 1lh;
      padding: 0;
      border: 0;
      background: none;
      --ds-sortable-grip-ink: var(--ds-text-tertiary-neutral);
      color: var(--ds-sortable-grip-ink);
      cursor: grab;
      transition: color var(--ds-duration) var(--ds-ease);
    }

    .ds-sortable__grip:active {
      cursor: grabbing;
    }

    .ds-sortable__grip:hover,
    .ds-sortable__grip:focus-visible {
      --ds-sortable-grip-ink: var(--ds-text-primary-neutral);
      color: var(--ds-sortable-grip-ink);
    }

    .ds-sortable__grip > svg {
      inline-size: 100%;
      block-size: 100%;
    }

    /* The mark is the size of the line, which is smaller than a finger. The target
       is pushed out around it the way the checkbox does it, to the tier a form
       control stands on: 24px was the checkbox's own floor and the owner still
       had to aim, because a mark beside a row of text earns none of the padding a
       framed control gets for free. */
    .ds-sortable__grip::after {
      content: "";
      position: absolute;
      inset-block-start: 50%;
      inset-inline-start: 50%;
      inline-size: max(var(--ds-size-3), 100%);
      block-size: max(var(--ds-size-3), 100%);
      transform: translate(-50%, -50%);
    }

    /* The whole row as the handle, chosen by the consumer on the list. The grip
       stays: it is the keyboard's way in and the one thing saying the list can be
       arranged. The cursor moves out to the row because the handle did. */
    .ds-sortable__list[data-ds-sortable-handle="row"] .ds-sortable__item {
      cursor: grab;
    }

    .ds-sortable__list[data-ds-sortable-handle="row"] .ds-sortable__item:active {
      cursor: grabbing;
    }

    .ds-sortable__grip:focus-visible {
      outline: 2px solid var(--ds-focus);
      outline-offset: 2px;
      border-radius: var(--ds-radius-1);
    }

    .ds-sortable__content {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-sortable-content-face: var(--ds-font-body-face);
      --ds-sortable-content-tracking: var(--ds-font-body-tracking);
      --ds-sortable-content-size: var(--ds-font-body-size);
      --ds-sortable-content-weight: var(--ds-font-body-weight);
      --ds-sortable-content-leading: var(--ds-font-body-leading);
      flex: 1;
      min-inline-size: 0;
      font-family: var(--ds-sortable-content-face);
      font-size: var(--ds-sortable-content-size);
      letter-spacing: var(--ds-sortable-content-tracking);
      font-weight: var(--ds-sortable-content-weight);
      line-height: var(--ds-sortable-content-leading);
      --ds-sortable-content-ink: var(--ds-text-primary-neutral);
      color: var(--ds-sortable-content-ink);
    }

    /* Said, not shown. A reader who moves an item with the arrows gets no picture
       of the list, so the new position is spoken. Sighted readers watch the row
       move and are told nothing, which is the right way round. */
    .ds-sortable__said {
      position: absolute;
      inline-size: 1px;
      block-size: 1px;
      margin: -1px;
      padding: 0;
      overflow: hidden;
      clip-path: inset(50%);
      white-space: nowrap;
      border: 0;
    }

    /* ==========================================================================
       ColorPicker

       The CSS behind <ColorPicker>. Private, like every other component's.

       Choosing a colour by its name, from a set somebody decided on.

       ── The swatches come from the caller, and that is the design ────────────
       This package has no palette to offer. Its colours are ROLES, one set per
       project: `--ds-brand` is whatever this project's brand is, `--ds-surface-2`
       is the well inside a card. A control that opened onto a rainbow would be
       inviting people to paint things the system cannot then describe, which is
       how a design system stops being one.

       So the swatches are given, and they are NAMED. A grid of unnamed squares
       is a control a screen reader cannot read out and a colleague cannot be
       told about over the phone: "the third blue" is not a value. Naming them
       also puts the panel on the select's own skin, keyboard and all, instead of
       a second kind of list that moves differently.

       ── It is the select's mechanism ─────────────────────────────────────────
       The frame, the panel, the roles and the arrow keys are the select's, so
       `.ds-popover__item` and its parts are worn here rather than copied. What
       this file adds is the chip, the row that holds a free value, and the
       trigger showing the colour rather than only its name.
       ========================================================================== */

    /* The chip. One custom property carries the colour in, which is how a value
       that is data rather than a token reaches the stylesheet: the same way the
       progress bar takes its share. */
    .ds-color-picker__swatch {
      display: inline-block;
      flex: none;
      /* With the border on top of `1lh` and the default content-box, the chip came
         out 18 in a 16px line, 22 in a 20 and 26 in a 24: the line plus its two
         edges, at every tier. */
      box-sizing: border-box;
      /* The height of the line beside it, like every other mark in the package. */
      inline-size: 1lh;
      block-size: 1lh;
      background-color: var(--ds-swatch);
      border-radius: var(--ds-radius-1);
      /* A line around it, because a swatch of the page's own colour on the page
         is a swatch with no edges, and white on white is the commonest one in
         any palette. */
      border: 1px solid var(--ds-border);
    }

    /* The value beside the chip in the trigger: the name of the colour, or the
       colour itself when nothing is chosen. */
    .ds-color-picker__value {
      flex: 1;
      min-inline-size: 0;
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
      text-align: start;
    }

    .ds-color-picker__value--empty {
      --ds-color-picker-value--empty-ink: var(--ds-text-tertiary-neutral);
      color: var(--ds-color-picker-value--empty-ink);
    }

    .ds-color-picker__control {
      display: flex;
      align-items: center;
      gap: var(--ds-field-gap);
    }

    .ds-color-picker__arrow {
      display: inline-flex;
      flex: none;
      --ds-color-picker-arrow-ink: var(--ds-text-tertiary-neutral);
      color: var(--ds-color-picker-arrow-ink);
    }

    .ds-color-picker__arrow > svg {
      inline-size: 1lh;
      block-size: 1lh;
    }

    /* The chip inside a panel row sits where the select puts an option's mark, so
       the names line up down the list whether or not a row is ticked. */
    .ds-color-picker__item .ds-color-picker__swatch {
      margin-inline-end: var(--ds-space-2);
    }

    /* The free value, at the foot of the panel and behind a line, because it is a
       different kind of answer: everything above it was decided in advance and
       this is not. */
    .ds-color-picker__custom {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-color-picker-custom-face: var(--ds-font-label-face);
      --ds-color-picker-custom-tracking: var(--ds-font-label-tracking);
      --ds-color-picker-custom-size: var(--ds-font-label-size);
      --ds-color-picker-custom-weight: var(--ds-font-label-weight);
      --ds-color-picker-custom-leading: var(--ds-font-label-leading);
      display: flex;
      align-items: center;
      gap: var(--ds-space-2);
      margin-block-start: var(--ds-space-1);
      padding: var(--ds-space-2);
      border-block-start: 1px solid var(--ds-border-subtle);
      font-family: var(--ds-color-picker-custom-face);
      font-size: var(--ds-color-picker-custom-size);
      letter-spacing: var(--ds-color-picker-custom-tracking);
      font-weight: var(--ds-color-picker-custom-weight);
      line-height: var(--ds-color-picker-custom-leading);
      --ds-color-picker-custom-ink: var(--ds-text-secondary-neutral);
      color: var(--ds-color-picker-custom-ink);
    }

    /* The native colour input, which is the whole of the free value: the operating
       system already has a colour picker, it is the one the reader knows, and
       nothing drawn here would be better than the one they use everywhere else.
       Stripped of its own chrome so what shows is the chip.

       `-webkit-color-swatch-wrapper` is the padding the user agent puts around the
       swatch; without it the chip sits in a box a few pixels bigger than every
       other chip in the panel. */
    .ds-color-picker__native {
      box-sizing: border-box;
      flex: none;
      /* A form control inherits neither the face nor the leading, so `1lh` below
         resolves against `normal`: measured 16 beside the panel's own 20px chips.
         The third time this package has paid for that line in one afternoon, after
         the sortable grip and the table's. */
      font: inherit;
      inline-size: 1lh;
      block-size: 1lh;
      padding: 0;
      border: 1px solid var(--ds-border);
      border-radius: var(--ds-radius-1);
      background: none;
      cursor: pointer;
    }

    /* The same answer as text, for the colour that arrives written down in a
       brand brief. Either control may write the other: a valid hex lands in the
       chip and a picked colour lands here. */
    .ds-color-picker__hex {
      flex: 1;
      min-inline-size: 0;
      box-sizing: border-box;
      /* A form control inherits neither the face nor the leading: the row above
         declared the label role, and this line is what lets the field take it. */
      font: inherit;
      padding: var(--ds-space-1) var(--ds-space-2);
      background-color: transparent;
      border: 1px solid var(--ds-border);
      border-radius: var(--ds-radius-1);
      --ds-color-picker-hex-ink: var(--ds-text-primary-neutral);
      color: var(--ds-color-picker-hex-ink);
    }

    .ds-color-picker__hex::placeholder {
      --ds-color-picker-hex-placeholder-ink: var(--ds-text-tertiary-neutral);
      color: var(--ds-color-picker-hex-placeholder-ink);
    }

    .ds-color-picker__hex:focus-visible {
      outline: 2px solid var(--ds-focus);
      outline-offset: 1px;
    }

    .ds-color-picker__native::-webkit-color-swatch-wrapper {
      padding: 0;
    }

    .ds-color-picker__native::-webkit-color-swatch {
      border: none;
      border-radius: calc(var(--ds-radius-1) - 1px);
    }

    .ds-color-picker__native::-moz-color-swatch {
      border: none;
      border-radius: calc(var(--ds-radius-1) - 1px);
    }

    .ds-color-picker__native:focus-visible {
      outline: 2px solid var(--ds-focus);
      outline-offset: 2px;
    }

    /* ==========================================================================
       Description list

       The CSS behind <DescriptionList>. Private, like every other component's.

       A column of name and value: what an admin detail page is made of, and what
       every one of them builds by hand.

       ── A real `dl` ──────────────────────────────────────────────────────────
       Not a grid of divs. The pairing is in the markup, which is how a screen
       reader can say "Status, published" as one thing rather than reading a
       column of words and then a column of other words.

       ── Two arrangements, and the narrow one is not a breakpoint ─────────────
       `rows` puts the name beside the value and `stacked` puts it above. Which
       one a page wants depends on the room it has, and the room is the page's
       business — so it is a prop rather than a media query. A detail panel 300px
       wide and a page 900px wide are both right, and neither is a phone.
       ========================================================================== */

    .ds-description-list {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-description-list-face: var(--ds-font-label-face);
      --ds-description-list-tracking: var(--ds-font-label-tracking);
      --ds-description-list-size: var(--ds-font-label-size);
      --ds-description-list-weight: var(--ds-font-label-weight);
      --ds-description-list-leading: var(--ds-font-label-leading);
      margin: 0;
      font-family: var(--ds-description-list-face);
      font-size: var(--ds-description-list-size);
      letter-spacing: var(--ds-description-list-tracking);
      font-weight: var(--ds-description-list-weight);
      line-height: var(--ds-description-list-leading);
    }

    /* The name's own five halves, pointed at the list's: the second hole the
       first consumer had to plug from outside. Its direction wanted the name in
       the metadata voice, the list exposed only the ink, and the body, weight
       and case got written onto this selector from a project file, where the
       leading stayed behind the size and the rows touched. Named here, a
       project moves the five together, and `case` is the hook for the
       uppercase. */
    .ds-description-list__name {
      --ds-description-list-name-face: var(--ds-description-list-face);
      --ds-description-list-name-size: var(--ds-description-list-size);
      --ds-description-list-name-weight: var(--ds-description-list-weight);
      --ds-description-list-name-leading: var(--ds-description-list-leading);
      --ds-description-list-name-tracking: var(--ds-description-list-tracking);
      --ds-description-list-name-ink: var(--ds-text-secondary-neutral);
      font-family: var(--ds-description-list-name-face);
      font-size: var(--ds-description-list-name-size);
      font-weight: var(--ds-description-list-name-weight);
      line-height: var(--ds-description-list-name-leading);
      letter-spacing: var(--ds-description-list-name-tracking);
      text-transform: var(--ds-description-list-name-case, none);
      color: var(--ds-description-list-name-ink);
    }

    .ds-description-list__value {
      margin: 0;
      --ds-description-list-value-ink: var(--ds-text-primary-neutral);
      color: var(--ds-description-list-value-ink);
      min-inline-size: 0;
      text-wrap: pretty;
      /* A value is often one long unbreakable thing — an address, an IBAN, an id
         — and a column narrow enough to hold the pairs is not always wide enough
         to hold one of those. `min-inline-size: 0` above lets the column shrink;
         without this the word does not, and it walks out of the panel taking the
         page's horizontal scroll with it. Measured on a phone: a 238px address in
         a 208px column pushed the whole document 31px sideways. */
      overflow-wrap: anywhere;
    }

    /* ── Rows: the name beside the value ──────────────────────────────────── */

    .ds-description-list--rows {
      display: grid;
      /* The names take what the longest of them needs and no more, so the values
         line up down the page without a number being picked. */
      grid-template-columns: max-content 1fr;
      /* The row step only. The column gap lives in the name's own padding rather
         than in the grid, so a ruled line can cross it: as a grid gap the rule
         stopped at the name's edge and started again on the value, measured as a
         24px hole in every line. */
      row-gap: var(--ds-space-3);
    }

    .ds-description-list--rows > .ds-description-list__name {
      padding-inline-end: var(--ds-space-6);
    }

    /* ── Stacked: the name above the value ───────────────────────────────── */

    .ds-description-list--stacked {
      display: flex;
      flex-direction: column;
      gap: var(--ds-space-1);
    }

    .ds-description-list--stacked .ds-description-list__value + .ds-description-list__name {
      margin-block-start: var(--ds-space-3);
    }

    /* ── Inline: le coppie una accanto all'altra ──────────────────────────────
       La fascia con cui si apre una pagina-record: cinque o sei fatti corti che si
       leggono di traverso sotto il titolo. Ogni coppia e' impilata come in
       `stacked`, e le coppie vanno a capo quando la stanza finisce.

       Una griglia e non una riga flex: le colonne restano incolonnate quando i
       valori sono di lunghezze diverse, che e' esattamente il caso qui (un nome di
       cliente accanto a una data accanto a un badge). */
    /* Una griglia, non una riga che va a capo. In flex ogni coppia era larga
       quanto il suo contenuto, quindi la seconda riga non stava sotto la prima:
       otto fatti uscivano incolonnati male, con due orfani sotto due colonne che
       non erano le loro (proprietario, 15 settembre 2026). Le celle hanno tutte la
       stessa larghezza e le righe si incolonnano.

       Il fondo e' 18rem: a 1440 fa quattro colonne, che e' il numero a cui un
       fatto sta su una riga sola. `min(100%, …)` perche' su un telefono la cella
       non puo' essere piu' larga della pagina. */
    .ds-description-list--inline {
      display: grid;
      grid-template-columns: repeat(auto-fill, minmax(min(100%, 18rem), 1fr));
      column-gap: var(--ds-space-8);
      row-gap: var(--ds-space-6);
    }

    /* Ogni coppia e' una cella, ed e' per questo che la fascia va a capo per
       COPPIE. Piatta, le due meta' sono figli separati e la riga si spezza fra un
       nome e il suo valore: otto fatti uscivano come otto colonne da 22px invece
       che come due righe da quattro. */
    .ds-description-list__pair {
      min-inline-size: 0;
    }

    /* ── Nella fascia il nome arretra e il valore avanza ──────────────────────
       Misurato il 15 settembre 2026: nome e valore erano identici, 14px, peso 400,
       stessa interlinea, e l'unica differenza era l'inchiostro. Il colore da solo
       non fa una gerarchia — e' la stessa ragione per cui un link in un paragrafo
       porta la riga sotto — e otto coppie cosi' sono sedici pezzi di testo che
       pesano uguale.

       Il nome scende di un gradino, che e' il ruolo `small`, "timestamps, counts,
       the genuinely supplementary"; il valore resta sulla misura di prima e
       prende il peso dell'enfasi. Solo qui: in colonna le due meta' si leggono
       una sotto l'altra e la distanza basta a distinguerle. */
    .ds-description-list--inline .ds-description-list__name {
      --ds-description-list-name-size: var(--ds-font-small-size);
      --ds-description-list-name-leading: var(--ds-font-small-leading);
      --ds-description-list-name-tracking: var(--ds-font-small-tracking);
    }

    .ds-description-list--inline .ds-description-list__value {
      /* Il nome del componente, non quello di sistema: si sposta questo e si
         sposta la fascia, si sposta il sistema e si sposta tutto quello che
         nessuno ha spostato apposta. */
      --ds-description-list-value-weight: var(--ds-emphasis-weight);
      --ds-description-list-value-leading: var(--ds-font-body-leading);
      font-weight: var(--ds-description-list-value-weight);
      line-height: var(--ds-description-list-value-leading);
    }

    .ds-description-list--inline .ds-description-list__name {
      /* Il nome sta attaccato al suo valore: quattro pixel fra le due meta' di una
         coppia contro ventiquattro fra una riga e l'altra, cosi' la fascia si
         legge per coppie e non per colonne. */
      margin-block-end: var(--ds-space-1);
    }

    /* ── A line between the pairs ─────────────────────────────────────────── */

    /* Off by default: a list of six pairs reads fine without rules, and a rule
       under every one of them is five lines of furniture in a panel that had none.
       On, for the long ones. */
    .ds-description-list--ruled.ds-description-list--rows > .ds-description-list__name,
    .ds-description-list--ruled.ds-description-list--rows > .ds-description-list__value {
      padding-block-end: var(--ds-space-3);
      border-block-end: 1px solid var(--ds-border-subtle);
    }

    .ds-description-list--ruled.ds-description-list--stacked > .ds-description-list__value {
      padding-block-end: var(--ds-space-3);
      border-block-end: 1px solid var(--ds-border-subtle);
    }

    /* Between the pairs, as this section says: the last pair closes the list, so
       a rule under it would underline nothing and fence the list off from
       whatever follows. The last name is the second-to-last child, beside the
       value that is the last. A list of one pair takes no rule at all, which is
       what "between" means. */
    .ds-description-list--ruled.ds-description-list--rows > .ds-description-list__name:nth-last-child(2),
    .ds-description-list--ruled.ds-description-list--rows > .ds-description-list__value:last-child,
    .ds-description-list--ruled.ds-description-list--stacked > .ds-description-list__value:last-child {
      padding-block-end: 0;
      border-block-end: 0;
    }

    /* ── Values that are controls ─────────────────────────────────────────────
       A value can be a word to read or a control to change it, and the two want
       different things from the pair. A paragraph is read from its first line
       down, so its name stands at the top of it. A control is a box on the ramp,
       taller than the line inside it, and a name left at the top of one sits
       above the words it names: measured 8px high on a record page whose every
       value was edited where it stood.

       Both halves stay stretched to the row, because the ruled line is drawn on
       the two cells and a pair that stopped stretching would draw it twice at two
       heights. What centres is what is INSIDE them. */
    .ds-description-list--controls > .ds-description-list__name,
    .ds-description-list--controls > .ds-description-list__value {
      display: flex;
      align-items: center;
    }

    /* A value is one control or several — the options that are on, the records
       this one points at — and several of them are a row that wraps, not a
       sentence. The gap is the one a row of marks takes everywhere else. */
    .ds-description-list--controls > .ds-description-list__value {
      flex-wrap: wrap;
      gap: var(--ds-space-2);
    }

    /* ==========================================================================
       Field group

       The CSS behind <FieldGroup>. Private, like every other component's.

       A name over a set of controls that answer one question together: a region
       picked from three ticks, a window with a date at each end, an address in
       four boxes. The name is the group's, and a screen reader says it before
       every control under it, so each one is heard as part of the answer rather
       than as a question of its own.

       Where a single control already carries its own name, this is the layer
       above: a field says "Postcode" and a group says "Address".

       ── It is a fieldset, and that costs two resets ───────────────────────────
       The element is what makes a screen reader announce the group at all, and it
       arrives with a border, an inset, and `min-inline-size: min-content`. The
       last one is the expensive one: inside a grid or a flex row it stops the
       group shrinking under the widest word it holds, so a panel with a long
       place name in it stops being able to narrow at all.
       ========================================================================== */

    .ds-field-group {
      /* This component's own. The role says what the words are; this says that
         THIS component wants a heavier hand than the role has, which is a
         decision the component makes and nobody else. Pointed at the semantic
         rather than holding a number, so moving the system moves every component
         that has not been moved on purpose, and moving one moves one. */
      display: flex;
      flex-direction: column;
      /* The element rhythm: a field is a part of the group the way a label is a
         part of a field, so the gap is the surface's element step, 16 on web and
         12 on product. It was a flat 12, which sat under the web rhythm and read
         as crowding next to the block gaps around the card. */
      gap: var(--ds-rhythm-element);
      min-inline-size: 0;
      margin: 0;
      padding: 0;
      border: 0;
    }

    /* One step over the labels inside it, and back in the flow: a legend is taken
       out of it by the user agent, and `display: block` is what puts it in the
       column with everything else. At the same size and weight as a field's
       own name it read as a fourth label glued to the first field: the group had a
       title nobody could tell from its contents. */
    .ds-field-group__name {
      --ds-field-group-name-weight: var(--ds-emphasis-weight);
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-field-group-name-face: var(--ds-font-body-face);
      --ds-field-group-name-tracking: var(--ds-font-body-tracking);
      --ds-field-group-name-size: var(--ds-font-body-size);
      --ds-field-group-name-leading: var(--ds-font-body-leading);
      display: block;
      padding: 0;
      /* The column's own gap, written again here because the legend never gets
         it: the user agent takes it out of the flow and `display: block` puts it
         back as a block, not as a flex item. Measured 0 between the name and what
         follows it with the gap alone. Keep the two numbers equal. */
      margin-block-end: var(--ds-rhythm-element);
      font-family: var(--ds-field-group-name-face);
      font-size: var(--ds-field-group-name-size);
      letter-spacing: var(--ds-field-group-name-tracking);
      line-height: var(--ds-field-group-name-leading);
      font-weight: var(--ds-field-group-name-weight);
      --ds-field-group-name-ink: var(--ds-text-primary-neutral);
      color: var(--ds-field-group-name-ink);
    }

    .ds-field-group__hint {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-field-group-hint-face: var(--ds-font-small-face);
      --ds-field-group-hint-tracking: var(--ds-font-small-tracking);
      --ds-field-group-hint-size: var(--ds-font-small-size);
      --ds-field-group-hint-weight: var(--ds-font-small-weight);
      --ds-field-group-hint-leading: var(--ds-font-small-leading);
      font-family: var(--ds-field-group-hint-face);
      font-size: var(--ds-field-group-hint-size);
      letter-spacing: var(--ds-field-group-hint-tracking);
      font-weight: var(--ds-field-group-hint-weight);
      line-height: var(--ds-field-group-hint-leading);
      --ds-field-group-hint-ink: var(--ds-text-secondary-neutral);
      color: var(--ds-field-group-hint-ink);
    }

    /* The controls. A column by default, since a form is read down; a row when
       the answers are short enough to sit side by side, and it wraps, because two
       fields on one line at 1200px are two fields on two lines at 400. */
    .ds-field-group__fields {
      display: flex;
      flex-direction: column;
      gap: var(--ds-rhythm-element);
      min-inline-size: 0;
    }

    .ds-field-group--row .ds-field-group__fields {
      flex-direction: row;
      flex-wrap: wrap;
      align-items: start;
    }

    /* Each field takes an equal share of the line, so a From and a To are the
       same width and the pair reads as one control with two ends. */
    .ds-field-group--row .ds-field-group__fields > * {
      flex: 1 1 8rem;
      min-inline-size: 0;
    }

    /* What is wrong with the answer as a whole, rather than with one control in
       it: a window whose end is before its start is neither field's fault. */
    .ds-field-group__message {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-field-group-message-face: var(--ds-font-small-face);
      --ds-field-group-message-tracking: var(--ds-font-small-tracking);
      --ds-field-group-message-size: var(--ds-font-small-size);
      --ds-field-group-message-weight: var(--ds-font-small-weight);
      --ds-field-group-message-leading: var(--ds-font-small-leading);
      font-family: var(--ds-field-group-message-face);
      font-size: var(--ds-field-group-message-size);
      letter-spacing: var(--ds-field-group-message-tracking);
      font-weight: var(--ds-field-group-message-weight);
      line-height: var(--ds-field-group-message-leading);
      color: var(--ds-danger-ink);
    }

    /* ==========================================================================
       Tabs

       The CSS behind <Tabs> and <TabPanel>. Private, like every other
       component's.

       A row of names over one panel: pressing a name swaps what is underneath.
       That swap is the whole difference from a segmented control, which changes a
       value and leaves the page where it was.

       ── The rail and the line ─────────────────────────────────────────────────
       The strip carries a hairline across its foot and the chosen tab covers a
       piece of it with a thicker one. Two rules, no shadow, no filled pill: the
       line is the only thing that moves, so the eye follows it rather than
       re-reading the row.
       ========================================================================== */

    .ds-tabs {
      /* This component's own. The role says what the words are; this says that
         THIS component wants a heavier hand than the role has, which is a
         decision the component makes and nobody else. Pointed at the semantic
         rather than holding a number, so moving the system moves every component
         that has not been moved on purpose, and moving one moves one. */
      display: flex;
      align-items: stretch;
      /* Adjacent, with the room inside each name rather than between them. Every
         modern strip does it this way and the reason is the target: with a gap,
         the space between two names belongs to neither, and the line under the
         chosen one is as narrow as the word. Padded and touching, the whole block
         is pressable and the line is the width of the block. */
      gap: 0;
      border-block-end: 1px solid var(--ds-border);
      /* Scrolls rather than wraps. A wrapped tab strip puts a second rail under
         the first and the reader has to work out which line belongs to which row;
         eight names that run off the side are at least still one row. */
      overflow-x: auto;
    }

    /* ── The ramp, read again ─────────────────────────────────────────────────
       Six tiers and not two, for the same reason every other control here has
       six: a strip inside a dense panel and one across a page are different
       heights, and a component that offers two of them leaves the third to be
       invented. The type follows the tier, and so does the room at the sides: one
       fixed inset for all six left a name at the top of the ramp sitting in the
       same 12px it had at the bottom, which reads as a tall word rather than a
       tab. In em, and shorter steps than a button takes, because a tab is a name
       on a rail and not a block to press: the line under it should be a little
       wider than the word, not twice it.

       Both are written where they are used: an alias of a token that varies with
       the surface resolves where it is declared, which the check refuses and
       rightly.

       Each size comes with the leading paired to it. For a revision a tab carried
       a size and no leading at all, so it fell back on `normal`, and a 16px name
       sat in a 19px line box: a number nothing in this system produces. And the
       top two tiers read at the level above body, which is fluid here, so a tab
       changed size as the window moved inside a rail that did not. The ramp reads
       12, 14, then 16 the rest of the way up, the same as every control on it. */
    .ds-tabs--xsmall { --ds-tabs-tier: var(--ds-size-1); }
    .ds-tabs--xsmall .ds-tabs__tab {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-tabs-tab-face: var(--ds-font-small-face);
      --ds-tabs-tab-tracking: var(--ds-font-small-tracking);
      --ds-tabs-tab-size: var(--ds-font-small-size);
      --ds-tabs-tab-leading: var(--ds-font-small-leading);
     padding-inline: 0.6em; font-size: var(--ds-tabs-tab-size); line-height: var(--ds-tabs-tab-leading); }
    .ds-tabs--small { --ds-tabs-tier: var(--ds-size-2); }
    .ds-tabs--small .ds-tabs__tab {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-tabs-tab-face: var(--ds-font-label-face);
      --ds-tabs-tab-tracking: var(--ds-font-label-tracking);
      --ds-tabs-tab-size: var(--ds-font-label-size);
      --ds-tabs-tab-leading: var(--ds-font-label-leading);
     padding-inline: 0.7em; font-size: var(--ds-tabs-tab-size); line-height: var(--ds-tabs-tab-leading); }
    .ds-tabs--medium { --ds-tabs-tier: var(--ds-size-3); }
    .ds-tabs--medium .ds-tabs__tab {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-tabs-tab-face: var(--ds-font-body-face);
      --ds-tabs-tab-tracking: var(--ds-font-body-tracking);
      --ds-tabs-tab-size: var(--ds-font-body-size);
      --ds-tabs-tab-leading: var(--ds-font-body-leading);
     padding-inline: 0.75em; font-size: var(--ds-tabs-tab-size); line-height: var(--ds-tabs-tab-leading); }
    .ds-tabs--large { --ds-tabs-tier: var(--ds-size-4); }
    .ds-tabs--large .ds-tabs__tab {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-tabs-tab-face: var(--ds-font-body-face);
      --ds-tabs-tab-tracking: var(--ds-font-body-tracking);
      --ds-tabs-tab-size: var(--ds-font-body-size);
      --ds-tabs-tab-leading: var(--ds-font-body-leading);
     padding-inline: 0.9em; font-size: var(--ds-tabs-tab-size); line-height: var(--ds-tabs-tab-leading); }
    .ds-tabs--xlarge { --ds-tabs-tier: var(--ds-size-5); }
    .ds-tabs--xlarge .ds-tabs__tab {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-tabs-tab-face: var(--ds-font-body-face);
      --ds-tabs-tab-tracking: var(--ds-font-body-tracking);
      --ds-tabs-tab-size: var(--ds-font-body-size);
      --ds-tabs-tab-leading: var(--ds-font-body-leading);
     padding-inline: 1em; font-size: var(--ds-tabs-tab-size); line-height: var(--ds-tabs-tab-leading); }
    .ds-tabs--xxlarge { --ds-tabs-tier: var(--ds-size-6); }
    .ds-tabs--xxlarge .ds-tabs__tab {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-tabs-tab-face: var(--ds-font-body-face);
      --ds-tabs-tab-tracking: var(--ds-font-body-tracking);
      --ds-tabs-tab-size: var(--ds-font-body-size);
      --ds-tabs-tab-leading: var(--ds-font-body-leading);
     padding-inline: 1.15em; font-size: var(--ds-tabs-tab-size); line-height: var(--ds-tabs-tab-leading); }

    .ds-tabs__tab {
      --ds-tabs-tab-weight: var(--ds-emphasis-weight);
      --ds-tabs-tab-face: var(--ds-font-body-face);
      display: inline-flex;
      align-items: center;
      gap: var(--ds-space-2);
      min-block-size: var(--ds-tabs-tier);
      border: 0;
      /* Sits on the rail and covers it when it is the chosen one. The negative
         margin is what puts the two lines on the same pixel: without it the tab's
         line rides one pixel above the strip's and the row reads as double-ruled. */
      border-block-end: 2px solid transparent;
      margin-block-end: -1px;
      background: none;
      font-family: var(--ds-tabs-tab-face);
      font-weight: var(--ds-tabs-tab-weight);
      --ds-tabs-tab-ink: var(--ds-text-secondary-neutral);
      color: var(--ds-tabs-tab-ink);
      white-space: nowrap;
      cursor: pointer;
      transition:
        color var(--ds-duration) var(--ds-ease),
        background-color var(--ds-duration) var(--ds-ease),
        border-color var(--ds-duration) var(--ds-ease);
    }

    .ds-tabs__tab[aria-selected="true"] {
      border-block-end-color: var(--ds-inverse);
      --ds-tabs-tab-ink: var(--ds-text-primary-neutral);
      color: var(--ds-tabs-tab-ink);
    }

    /* A wash under the pointer, now that the whole block is the target: without
       it a padded tab gives back less than the narrow one did, because the only
       thing that answered was the line at its foot. The corners are rounded at
       the top alone, since the bottom of a tab is the rail. */
    .ds-tabs__tab:hover:not([aria-selected="true"]):not(:disabled) {
      --ds-tabs-tab-ink: var(--ds-text-primary-neutral);
      color: var(--ds-tabs-tab-ink);
      background-color: var(--ds-surface-hover);
      border-start-start-radius: var(--ds-radius-2);
      border-start-end-radius: var(--ds-radius-2);
    }

    /* Inside the tab rather than around it: an offset ring on a strip this tight
       would sit on the names either side. */
    .ds-tabs__tab:focus-visible {
      outline: 2px solid var(--ds-focus);
      outline-offset: -2px;
      border-radius: var(--ds-radius-1);
    }

    .ds-tabs__tab:disabled {
      --ds-tabs-tab-ink: var(--ds-text-disabled);
      color: var(--ds-tabs-tab-ink);
      cursor: not-allowed;
    }

    /* ── The name, and its door ───────────────────────────────────────────────
       A tab with a menu is a name and a door beside it, and the pair is what
       moves when the strip is reorderable. The door shows under the pointer,
       on the keyboard and while open, the way the column's does: a strip of
       names each wearing a mark reads as a row of controls. */
    .ds-tabs__item {
      position: relative;
      display: inline-flex;
      align-items: stretch;
      flex: none;
    }

    .ds-tabs__menu {
      align-self: center;
      opacity: 0;
      transition: opacity var(--ds-duration) var(--ds-ease);
    }

    .ds-tabs__item:hover .ds-tabs__menu,
    .ds-tabs__menu:focus-visible,
    .ds-tabs__menu[aria-expanded="true"] {
      opacity: 1;
    }

    @media (hover: none) {
      .ds-tabs__menu {
        opacity: 1;
      }
    }

    /* The name in the air, and the gap it will fall into: a line at the start
       edge of the name under the pointer, the way the sortable list draws it. */
    .ds-tabs__item--carrying {
      opacity: 0.4;
    }

    .ds-tabs__item--over::before {
      content: "";
      position: absolute;
      inset-block: 0;
      inset-inline-start: -1px;
      inline-size: 0;
      border-inline-start: 2px solid var(--ds-border-active);
    }

    .ds-tabs__icon {
      display: inline-flex;
      flex: none;
      inline-size: 1lh;
      block-size: 1lh;
    }

    .ds-tabs__icon > svg {
      inline-size: 100%;
      block-size: 100%;
    }

    /* The panel takes focus when nothing inside it can, so a reader arriving from
       the strip with Tab lands on the content rather than skipping past it. It
       needs no border: the rail above already says where it starts. */
    .ds-tabs__panel {
      padding-block-start: var(--ds-rhythm-element);
    }

    .ds-tabs__panel:focus-visible {
      outline: 2px solid var(--ds-focus);
      outline-offset: 2px;
      border-radius: var(--ds-radius-2);
    }

    /* ==========================================================================
       Toast

       The CSS behind <Toasts> and <Toast>. Private, like every other component's.

       Something happened and the page did not change: a row was saved, a message
       was sent, a copy failed. It says so and leaves.

       ── The region exists before the message does ─────────────────────────────
       A live region is announced when its CONTENTS change, so a screen reader has
       to have been watching it already. A region added to the page at the same
       moment as the first toast is a region nobody hears, and that is the single
       commonest way this component is built wrong. Render it empty, always.

       ── It is not where the eye is ────────────────────────────────────────────
       Which is the whole difficulty. A toast in the corner is a thing a reader
       using a magnifier never sees, so nothing that must be read may live only
       here: an error a form can show belongs on the form.
       ========================================================================== */

    .ds-toasts {
      position: fixed;
      z-index: 2;
      display: flex;
      flex-direction: column;
      gap: var(--ds-space-2);
      /* Room at the edges, and the phone's own strip under it. */
      padding: var(--ds-space-4);
      padding-block-end: calc(var(--ds-space-4) + env(safe-area-inset-bottom, 0px));
      /* The region is the whole side of the screen and the toasts are what can be
         pressed: a full-width invisible box over the page would swallow every
         click that missed a toast. */
      pointer-events: none;
      inline-size: min(24rem, 100%);
    }

    .ds-toasts > * {
      pointer-events: auto;
    }

    /* Where it stands. Bottom by default, at the end of the writing direction:
       the top is where a page's own bar is, and the start is where a sidebar is. */
    .ds-toasts--bottom-end { inset-block-end: 0; inset-inline-end: 0; align-items: end; }
    .ds-toasts--bottom-start { inset-block-end: 0; inset-inline-start: 0; align-items: start; }
    .ds-toasts--top-end { inset-block-start: 0; inset-inline-end: 0; align-items: end; }
    .ds-toasts--top-start { inset-block-start: 0; inset-inline-start: 0; align-items: start; }
    .ds-toasts--bottom-center { inset-block-end: 0; inset-inline: 0; margin-inline: auto; align-items: center; }
    .ds-toasts--top-center { inset-block-start: 0; inset-inline: 0; margin-inline: auto; align-items: center; }

    .ds-toast {
      --ds-toast-face: var(--ds-font-body-face);
      /* This component's own. The role says what the words are; this says that
         THIS component wants a heavier hand than the role has, which is a
         decision the component makes and nobody else. Pointed at the semantic
         rather than holding a number, so moving the system moves every component
         that has not been moved on purpose, and moving one moves one. */
      box-sizing: border-box;
      display: flex;
      align-items: start;
      gap: var(--ds-space-3);
      inline-size: 100%;
      padding: var(--ds-space-3) var(--ds-space-4);
      border-radius: var(--ds-radius-3);
      /* The inverse block, owner's call on 22 August 2026: it stood on
         `--ds-surface-1` with a border, and a toast the colour of the page melts
         into the page it interrupts, darkest exactly where the dark theme is. The
         inverse ground is the page quoted back at itself, white on the dark theme
         and near-black on the light, so the one thing that appears over
         everything is the one thing that can never be the colour of what it
         covers. It is also its own edge, which is where the border went. */
      background-color: var(--ds-inverse);
      --ds-toast-ink: var(--ds-on-inverse);
      color: var(--ds-toast-ink);
      font-family: var(--ds-toast-face);
    }

    /* The buttons inside stand on the inverse ground, and the toast says so with
       `data-ground="inverse"` on its root, state like `data-open`: the button's
       own stylesheet answers it with its own tokens, because a component may only
       declare variables that carry its name and the check holds it to that. */

    /* The tone is a mark and an edge, not a ground: a filled green box for "saved"
       is the loudest thing on a screen saying the quietest. */
    /* The cross stands on the TITLE's line, as the mark does. The button is a
       small tier, 32, against a title line of 16, and start-aligned it sat 8px
       below the mark: measured 765 against 757 on php.html, 19 September 2026,
       the same defect AdminHead's arrow had on 15 September. Half the difference
       taken back on both sides keeps the target at 32 and puts its centre on the
       line; `(tier - line) / 2` is the same arithmetic Button uses for its own
       padding, read from the same two names. */
    .ds-toast > .ds-button--icon {
      --ds-toast-cross-tier: var(--ds-size-2);
      --ds-toast-cross-line: var(--ds-font-label-leading);
      margin-block: calc((var(--ds-toast-cross-line) - var(--ds-toast-cross-tier)) / 2);
    }

    .ds-toast__mark {
      --ds-toast-mark-leading: var(--ds-font-label-leading);
      display: inline-flex;
      flex: none;
      inline-size: 1lh;
      block-size: 1lh;
      /* The title's leading, so the mark lands on the line beside it rather than
         on the page's. */
      line-height: var(--ds-toast-mark-leading);
    }

    .ds-toast__mark > svg {
      inline-size: 100%;
      block-size: 100%;
    }

    /* The tone inks are tuned for the page's surfaces and the mark stands on the
       inverse block, which is the OTHER theme's page: the `-on-inverse` four are
       that theme's own vetted values, so a mark reads on the block in both
       directions. */
    .ds-toast--success .ds-toast__mark { color: var(--ds-success-on-inverse); }
    .ds-toast--warning .ds-toast__mark { color: var(--ds-warning-on-inverse); }
    .ds-toast--danger .ds-toast__mark { color: var(--ds-danger-on-inverse); }
    .ds-toast--info .ds-toast__mark { color: var(--ds-info-on-inverse); }

    .ds-toast__text {
      flex: 1;
      min-inline-size: 0;
      display: flex;
      flex-direction: column;
      gap: var(--ds-space-1);
    }

    .ds-toast__title {
      --ds-toast-title-weight: var(--ds-emphasis-weight);
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-toast-title-face: var(--ds-font-label-face);
      --ds-toast-title-tracking: var(--ds-font-label-tracking);
      --ds-toast-title-size: var(--ds-font-label-size);
      --ds-toast-title-leading: var(--ds-font-label-leading);
      margin: 0;
      font-family: var(--ds-toast-title-face);
      font-size: var(--ds-toast-title-size);
      letter-spacing: var(--ds-toast-title-tracking);
      line-height: var(--ds-toast-title-leading);
      font-weight: var(--ds-toast-title-weight);
    }

    .ds-toast__body {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-toast-body-face: var(--ds-font-label-face);
      --ds-toast-body-tracking: var(--ds-font-label-tracking);
      --ds-toast-body-size: var(--ds-font-label-size);
      --ds-toast-body-weight: var(--ds-font-label-weight);
      --ds-toast-body-leading: var(--ds-font-label-leading);
      margin: 0;
      font-family: var(--ds-toast-body-face);
      font-size: var(--ds-toast-body-size);
      letter-spacing: var(--ds-toast-body-tracking);
      font-weight: var(--ds-toast-body-weight);
      line-height: var(--ds-toast-body-leading);
      --ds-toast-body-ink: var(--ds-secondary-on-inverse);
      color: var(--ds-toast-body-ink);
      text-wrap: pretty;
    }

    .ds-toast__actions {
      display: flex;
      gap: var(--ds-space-2);
      margin-block-start: var(--ds-space-1);
    }

    /* ==========================================================================
       Consent

       The bar a site shows before it measures anything: one sentence, a link to
       the policy, and two answers of equal standing. Fixed to the foot of the
       window, because it has to be answerable from any point of any page, and
       painted on the inverse ground, because the one thing asking for an answer
       must never be the colour of the page it interrupts. Same ruling as the
       toast, and the same `data-ground="inverse"` on the root, so the two
       buttons come out banner-tuned without this file saying anything about
       them.

       The server renders it `hidden` and the script shows it, only to a reader
       who has not answered: the server cannot see what is in anyone's storage,
       and showing the bar to everyone and then taking it away flashes it at
       exactly the people who must not see it again. The first consumer built
       this whole arrangement by hand as `gl-cookie`, column alignment included.
       ========================================================================== */

    .ds-consent {
      position: fixed;
      inset-block-end: 0;
      inset-inline: 0;
      /* The header's own layer: the two bars of a page, one at each edge, and
         they never meet. Above it there is only what lies over the page on
         purpose, and a question the reader has not answered may not cover a
         sheet they opened themselves. */
      z-index: 20;
      /* The section's own centring: one track, held to the page's band so the
         sentence starts in column with the words above it. Read with fallbacks,
         because the bar is fixed and can stand outside any shell: there the
         names are undeclared and the bar takes the window with the default
         gutter, which is what a page without a shell is doing anyway. */
      display: grid;
      justify-content: center;
      grid-template-columns: min(100%, var(--ds-site-band, 100%));
      padding-block: var(--ds-space-5);
      background-color: var(--ds-inverse);
      --ds-consent-ink: var(--ds-on-inverse);
      color: var(--ds-consent-ink);
    }

    /* `display: grid` above beats the user agent's `display: none` for [hidden],
       so the promise has to be kept by hand. Without this rule the bar stands
       visible in front of exactly the readers who already answered. The first
       consumer paid this line first. */
    .ds-consent[hidden] {
      display: none;
    }

    .ds-consent__band {
      box-sizing: border-box;
      min-inline-size: 0;
      display: flex;
      flex-wrap: wrap;
      align-items: center;
      justify-content: space-between;
      gap: var(--ds-space-4) var(--ds-space-8);
      padding-inline: var(--ds-site-gutter, var(--ds-space-6));
    }

    .ds-consent__text {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. */
      --ds-consent-text-face: var(--ds-font-body-face);
      --ds-consent-text-size: var(--ds-font-body-size);
      --ds-consent-text-weight: var(--ds-font-body-weight);
      --ds-consent-text-leading: var(--ds-font-body-leading);
      --ds-consent-text-tracking: var(--ds-font-body-tracking);
      margin: 0;
      /* Held to a reading measure, or on a wide window the sentence runs the
         whole track and the answers sit a metre from the question. */
      max-inline-size: 60ch;
      font-family: var(--ds-consent-text-face);
      font-size: var(--ds-consent-text-size);
      font-weight: var(--ds-consent-text-weight);
      line-height: var(--ds-consent-text-leading);
      letter-spacing: var(--ds-consent-text-tracking);
    }

    /* The policy link keeps the bar's ink: the page's link colour is drawn for
       the page's ground, and on the inverse block it is the one thing that
       would not hold. */
    .ds-consent__text a {
      color: inherit;
    }

    .ds-consent__actions {
      display: flex;
      flex-wrap: wrap;
      gap: var(--ds-space-3);
    }

    /* ==========================================================================
       Contents

       The CSS behind <Contents>. Private, like every other component's.

       The headings of the page you are on, in the order they appear, and the one
       you are standing in.

       ── The rail belongs to the list and the mark belongs to the line ─────────
       One border runs down the whole list; every line carries its own segment of
       the same width, transparent until it is the current one, pulled back over
       the list's border by its own width. So the mark IS the rail where the
       reader is, rather than a second line drawn beside it, and nothing has to be
       positioned against a rail it cannot measure.

       ── Two levels ───────────────────────────────────────────────────────────
       A heading and the headings under it. The page's own outline carries the
       rest: the third level indents by a space it would have to share with the
       second, and at 12px of indent two levels apart read as one.

       ── The line stands on a tier ────────────────────────────────────────────
       `--ds-space-2` above and below a 16px line makes a 32px target, which is
       the second rung of the control ramp and over the 24px floor. A row of links
       is a column of targets and the fingers get more than the words do.
       ========================================================================== */

    .ds-contents {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. */
      --ds-contents-face: var(--ds-font-label-face);
      --ds-contents-size: var(--ds-font-label-size);
      --ds-contents-weight: var(--ds-font-label-weight);
      --ds-contents-leading: var(--ds-font-label-leading);
      --ds-contents-tracking: var(--ds-font-label-tracking);
      /* The weight the current line takes. Pointed at the semantic rather than
         holding a number, so moving the system moves every component that has not
         been moved on purpose. */
      --ds-contents-here-weight: var(--ds-emphasis-weight);
      display: flex;
      flex-direction: column;
      gap: var(--ds-space-3);
      font-family: var(--ds-contents-face);
      font-size: var(--ds-contents-size);
      font-weight: var(--ds-contents-weight);
      line-height: var(--ds-contents-leading);
      letter-spacing: var(--ds-contents-tracking);
    }

    /* The name over the list, quieter than the links under it: the links are what
       the reader came for and the title says which list this is. Small rather than
       the overline role, whose 14px in capitals at robust weight is louder than
       every line it introduces. */
    .ds-contents__title {
      --ds-contents-title-face: var(--ds-font-small-face);
      --ds-contents-title-size: var(--ds-font-small-size);
      --ds-contents-title-weight: var(--ds-font-small-weight);
      --ds-contents-title-leading: var(--ds-font-small-leading);
      --ds-contents-title-tracking: var(--ds-font-small-tracking);
      --ds-contents-title-ink: var(--ds-text-tertiary-neutral);
      display: block;
      margin: 0;
      font-family: var(--ds-contents-title-face);
      font-size: var(--ds-contents-title-size);
      font-weight: var(--ds-contents-title-weight);
      line-height: var(--ds-contents-title-leading);
      letter-spacing: var(--ds-contents-title-tracking);
      color: var(--ds-contents-title-ink);
    }

    .ds-contents__list {
      --ds-contents-rail: var(--ds-border);
      display: flex;
      flex-direction: column;
      margin: 0;
      padding: 0;
      list-style: none;
      border-inline-start: 1px solid var(--ds-contents-rail);
    }

    .ds-contents__item {
      display: flex;
      min-inline-size: 0;
    }

    .ds-contents__link {
      --ds-contents-link-ink: var(--ds-text-secondary-neutral);
      --ds-contents-mark: transparent;
      display: block;
      flex: 1;
      min-inline-size: 0;
      padding-block: var(--ds-space-2);
      padding-inline: var(--ds-space-3);
      /* Its own segment of the rail, over the list's rather than beside it. */
      margin-inline-start: -1px;
      border-inline-start: 1px solid var(--ds-contents-mark);
      color: var(--ds-contents-link-ink);
      text-decoration: none;
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
      transition:
        color var(--ds-duration) var(--ds-ease),
        border-color var(--ds-duration) var(--ds-ease);
    }

    /* The ink stepping up to primary is the whole hover, as it is on the
       breadcrumb and in the sidebar: navigation chrome says it once. Prose keeps
       its underline because a link inside running text cannot be marked by colour
       alone. */
    .ds-contents__link:hover {
      --ds-contents-link-ink: var(--ds-text-primary-neutral);
    }

    .ds-contents__link:focus-visible {
      outline: 2px solid var(--ds-focus);
      outline-offset: -2px;
      border-radius: var(--ds-radius-1);
    }

    /* Where the reader is: the loudest line in the column, and the only one whose
       segment of the rail is painted. Two signals, because the mark alone is a
       colour and the weight alone is a difference nobody sees in a column of
       twelve. */
    .ds-contents__link[aria-current] {
      --ds-contents-link-ink: var(--ds-text-primary-neutral);
      --ds-contents-mark: var(--ds-border-active);
      font-weight: var(--ds-contents-here-weight);
    }

    /* A heading under a heading. One space of indent past the line above it,
       which is the smallest step that reads as a step at this size. */
    .ds-contents__item--sub .ds-contents__link {
      padding-inline-start: var(--ds-space-6);
    }

    /* ==========================================================================
       Theme toggle

       The control that flips the page between light and dark, in two kinds: a
       lever standing between the sun and the moon, and a single mark showing
       the theme a press would take you to. The first consumer drew it by hand
       as `gl-theme-toggle`, sun, moon and storage included.

       ── The state lives on the root, not in here ──────────────────────────────
       Everything visual reads `[data-theme="dark"]` off the document, which is
       the one place the theme is true: a page can hold two of these, the docs
       bar can change the theme over their heads, and every copy stays right
       because none of them keeps a state of its own. The script's whole job is
       flipping that attribute and remembering the choice.

       ── The lever is the switch's own drawing ─────────────────────────────────
       Same shares, taken from `switch.css` where each one is argued: 1.75 of
       the mark across, a knob of 0.8 with a tenth of rail all round, travel of
       0.75 falling out of the arithmetic. The mark number here is `1lh`, the
       line of the size the control stands on, so the six tiers keep one drawing.
       `1lh` holds because every size class declares the face and the leading on
       the button itself: a form control inherits neither, and an undeclared
       `1lh` resolves against `normal` (CLAUDE.md pays this trap four times).
       ========================================================================== */

    .ds-theme-toggle {
      /* This component's own, pointed at the system. The current theme's mark
         takes the stronger ink, the other stays quiet. */
      --ds-theme-toggle-ink: var(--ds-text-secondary-neutral);
      --ds-theme-toggle-ink-current: var(--ds-text-primary-neutral);
      --ds-theme-toggle-rail: var(--ds-surface-3);
      --ds-theme-toggle-knob: var(--ds-surface-1);
      display: inline-flex;
      align-items: center;
      justify-content: center;
      gap: var(--ds-space-2);
      border: 0;
      padding-block: 0;
      padding-inline: var(--ds-space-2);
      background-color: transparent;
      border-radius: var(--ds-radius-2);
      color: var(--ds-theme-toggle-ink);
      cursor: pointer;
      transition: background-color var(--ds-duration) var(--ds-ease);
    }

    .ds-theme-toggle:hover {
      background-color: var(--ds-surface-hover);
    }

    .ds-theme-toggle:focus-visible {
      outline: 2px solid var(--ds-focus);
      outline-offset: 2px;
    }

    /* ── The six sizes, on the control ramp ───────────────────────────────────
       The tier nails the box and the type trio gives `1lh` its number, the same
       arrangement the button has: the two smallest steps take the smaller
       roles, and from medium up the line is the body's and only the box grows. */
    .ds-theme-toggle--xsmall {
      --ds-theme-toggle-face: var(--ds-font-small-face);
      --ds-theme-toggle-size: var(--ds-font-small-size);
      --ds-theme-toggle-leading: var(--ds-font-small-leading);
      font-family: var(--ds-theme-toggle-face);
      font-size: var(--ds-theme-toggle-size);
      line-height: var(--ds-theme-toggle-leading);
      --ds-theme-toggle-tier: var(--ds-size-1);
      min-block-size: var(--ds-theme-toggle-tier);
    }

    .ds-theme-toggle--small {
      --ds-theme-toggle-face: var(--ds-font-label-face);
      --ds-theme-toggle-size: var(--ds-font-label-size);
      --ds-theme-toggle-leading: var(--ds-font-label-leading);
      font-family: var(--ds-theme-toggle-face);
      font-size: var(--ds-theme-toggle-size);
      line-height: var(--ds-theme-toggle-leading);
      --ds-theme-toggle-tier: var(--ds-size-2);
      min-block-size: var(--ds-theme-toggle-tier);
    }

    .ds-theme-toggle--medium {
      --ds-theme-toggle-face: var(--ds-font-body-face);
      --ds-theme-toggle-size: var(--ds-font-body-size);
      --ds-theme-toggle-leading: var(--ds-font-body-leading);
      font-family: var(--ds-theme-toggle-face);
      font-size: var(--ds-theme-toggle-size);
      line-height: var(--ds-theme-toggle-leading);
      --ds-theme-toggle-tier: var(--ds-size-3);
      min-block-size: var(--ds-theme-toggle-tier);
    }

    .ds-theme-toggle--large {
      --ds-theme-toggle-face: var(--ds-font-body-face);
      --ds-theme-toggle-size: var(--ds-font-body-size);
      --ds-theme-toggle-leading: var(--ds-font-body-leading);
      font-family: var(--ds-theme-toggle-face);
      font-size: var(--ds-theme-toggle-size);
      line-height: var(--ds-theme-toggle-leading);
      --ds-theme-toggle-tier: var(--ds-size-4);
      min-block-size: var(--ds-theme-toggle-tier);
    }

    .ds-theme-toggle--xlarge {
      --ds-theme-toggle-face: var(--ds-font-body-face);
      --ds-theme-toggle-size: var(--ds-font-body-size);
      --ds-theme-toggle-leading: var(--ds-font-body-leading);
      font-family: var(--ds-theme-toggle-face);
      font-size: var(--ds-theme-toggle-size);
      line-height: var(--ds-theme-toggle-leading);
      --ds-theme-toggle-tier: var(--ds-size-5);
      min-block-size: var(--ds-theme-toggle-tier);
    }

    .ds-theme-toggle--xxlarge {
      --ds-theme-toggle-face: var(--ds-font-body-face);
      --ds-theme-toggle-size: var(--ds-font-body-size);
      --ds-theme-toggle-leading: var(--ds-font-body-leading);
      font-family: var(--ds-theme-toggle-face);
      font-size: var(--ds-theme-toggle-size);
      line-height: var(--ds-theme-toggle-leading);
      --ds-theme-toggle-tier: var(--ds-size-6);
      min-block-size: var(--ds-theme-toggle-tier);
    }

    /* ── The marks ────────────────────────────────────────────────────────────
       Both are always in the markup and the stylesheet picks, the same
       arrangement the snippet's copy tick has: a script that swapped the SVG
       would be a second renderer nobody compares. */
    .ds-theme-toggle__mark {
      display: inline-flex;
      inline-size: 1lh;
      block-size: 1lh;
    }

    .ds-theme-toggle__mark > svg {
      inline-size: 100%;
      block-size: 100%;
    }

    /* The lever kind: the current theme's mark speaks up. */
    .ds-theme-toggle--switch .ds-theme-toggle__mark--sun {
      color: var(--ds-theme-toggle-ink-current);
    }

    [data-theme="dark"] .ds-theme-toggle--switch .ds-theme-toggle__mark--sun {
      color: var(--ds-theme-toggle-ink);
    }

    [data-theme="dark"] .ds-theme-toggle--switch .ds-theme-toggle__mark--moon {
      color: var(--ds-theme-toggle-ink-current);
    }

    /* ── The track, the switch's shares off `1lh` ─────────────────────────────
       Painted, so it says how it lays out: a grid of nothing, positioned so the
       knob has something to be absolute against. */
    .ds-theme-toggle__track {
      position: relative;
      display: grid;
      inline-size: calc(1lh * 1.75);
      block-size: 1lh;
      border-radius: var(--ds-radius-full);
      background-color: var(--ds-theme-toggle-rail);
      transition: background-color var(--ds-duration) var(--ds-ease);
    }

    /* On, the rail fills the way every other control fills: the change is a
       change of colour and of position at once, for whoever cannot tell the two
       greys apart. */
    [data-theme="dark"] .ds-theme-toggle__track {
      background-color: var(--ds-inverse);
    }

    .ds-theme-toggle__track::before {
      content: "";
      position: absolute;
      inset-block-start: 50%;
      inset-inline-start: calc(1lh * 0.1);
      inline-size: calc(1lh * 0.8);
      block-size: calc(1lh * 0.8);
      border-radius: 50%;
      background-color: var(--ds-theme-toggle-knob);
      translate: 0 -50%;
      transition: translate var(--ds-duration) var(--ds-ease);
    }

    [data-theme="dark"] .ds-theme-toggle__track::before {
      translate: calc(1lh * 0.75) -50%;
    }

    /* ── The pill kind: both marks ride the rail ──────────────────────────────
       The old system's theme-toggle, redrawn on this system's numbers: a pill
       holding the two marks, and the knob slides UNDER the one that is on, so
       current and destination are both visible at once. The knob is the
       switch's own, an elevated disc on a filled rail; what moves it is the
       root, like everything else in this file.

       The geometry hangs off the tier rather than off the line: the knob fills
       the pill less one rail inset all round, so the pill reads as a control of
       its size the way the reference does, and the travel is one cell exactly
       because the cells and the knob are the same box. */
    .ds-theme-toggle--pill {
      position: relative;
      gap: 0;
      padding-inline: var(--ds-space-1);
      border-radius: var(--ds-radius-full);
      background-color: var(--ds-theme-toggle-rail);
    }

    .ds-theme-toggle--pill:hover {
      background-color: var(--ds-surface-hover);
    }

    /* The knob, first in the paint order so the marks read over it. Positioned
       against the pill, one inset from its start edge, and as tall as the pill
       leaves it. */
    .ds-theme-toggle--pill::before {
      content: "";
      position: absolute;
      inset-block-start: 50%;
      inset-inline-start: var(--ds-space-1);
      inline-size: calc(var(--ds-theme-toggle-tier) - var(--ds-space-1) * 2);
      block-size: calc(var(--ds-theme-toggle-tier) - var(--ds-space-1) * 2);
      border-radius: var(--ds-radius-full);
      background-color: var(--ds-theme-toggle-knob);
      translate: 0 -50%;
      transition: translate var(--ds-duration) var(--ds-ease);
    }

    [data-theme="dark"] .ds-theme-toggle--pill::before {
      translate: calc(var(--ds-theme-toggle-tier) - var(--ds-space-1) * 2) -50%;
    }

    /* The cells are the knob's own box, so the knob sits exactly behind one.
       Positioned, which is what puts them over the knob in the paint order. */
    .ds-theme-toggle--pill .ds-theme-toggle__mark {
      position: relative;
      align-items: center;
      justify-content: center;
      inline-size: calc(var(--ds-theme-toggle-tier) - var(--ds-space-1) * 2);
      block-size: calc(var(--ds-theme-toggle-tier) - var(--ds-space-1) * 2);
    }

    /* The mark keeps the line's size inside the larger cell: the cell is the
       knob's geometry, the drawing stays the text's. */
    .ds-theme-toggle--pill .ds-theme-toggle__mark > svg {
      inline-size: 1em;
      block-size: 1em;
    }

    /* The current theme's mark speaks up, same rule as the lever's. */
    .ds-theme-toggle--pill .ds-theme-toggle__mark--sun {
      color: var(--ds-theme-toggle-ink-current);
    }

    [data-theme="dark"] .ds-theme-toggle--pill .ds-theme-toggle__mark--sun {
      color: var(--ds-theme-toggle-ink);
    }

    [data-theme="dark"] .ds-theme-toggle--pill .ds-theme-toggle__mark--moon {
      color: var(--ds-theme-toggle-ink-current);
    }

    /* ── The single mark kind: a square on the tier, like the icon button, and
       the mark is the theme a press would take you to. ─────────────────────── */
    .ds-theme-toggle--icon {
      padding-inline: 0;
    }

    .ds-theme-toggle--icon.ds-theme-toggle--xsmall { inline-size: var(--ds-size-1); }
    .ds-theme-toggle--icon.ds-theme-toggle--small { inline-size: var(--ds-size-2); }
    .ds-theme-toggle--icon.ds-theme-toggle--medium { inline-size: var(--ds-size-3); }
    .ds-theme-toggle--icon.ds-theme-toggle--large { inline-size: var(--ds-size-4); }
    .ds-theme-toggle--icon.ds-theme-toggle--xlarge { inline-size: var(--ds-size-5); }
    .ds-theme-toggle--icon.ds-theme-toggle--xxlarge { inline-size: var(--ds-size-6); }

    .ds-theme-toggle--icon .ds-theme-toggle__mark--sun {
      display: none;
    }

    [data-theme="dark"] .ds-theme-toggle--icon .ds-theme-toggle__mark--sun {
      display: inline-flex;
    }

    [data-theme="dark"] .ds-theme-toggle--icon .ds-theme-toggle__mark--moon {
      display: none;
    }

    /* ==========================================================================
       View modes

       The three reading choices in one group: the theme, the contrast, the size
       of the text. One shape three times — the name on the left, a select on the
       right — because three drawings for one question read as three questions.

       A GRID, so the names share a column and the controls share a column: three
       flex rows sized to their own content gave a ragged edge, which reads as
       three unrelated things. The control column is `max-content`, not `1fr`: as
       wide as the widest of the three, not as wide as the room.

       The control column has a FLOOR and not just `max-content`: `max-content`
       measures what is shown, so on "Normal" the column was 126px against 175px
       on "Larger" and the group jumped under the finger that picked. 11rem is the
       longest option of the three axes as they ship, measured; a longer label
       than that grows the column instead of being cut.

       Layout only: the typography is the role classes the markup carries. No
       surface either — it sits in whatever holds it.
       ========================================================================== */

    .ds-view-modes {
      display: grid;
      grid-template-columns: auto minmax(11rem, max-content);
      align-items: center;
      gap: var(--ds-space-4);
    }

    /* Il titolo del blocco, in mezzo: a sinistra si leggeva come l'etichetta
       della prima riga. */
    .ds-view-modes__label {
      grid-column: 1 / -1;
      margin: 0;
      text-align: center;
    }

    /* La riga non e' una scatola: i suoi due pezzi sono celle della griglia di
       sopra, o le colonne non esisterebbero. */
    .ds-view-modes__row {
      display: contents;
    }

    /* Il nome pesa come l'etichetta del comando che ha accanto: due misure
       diverse sulla stessa riga si vedono. */
    .ds-view-modes__name {
      font: inherit;
      font-size: var(--ds-font-label-size);
    }

    .ds-view-modes__control {
      justify-self: stretch;
    }

    .ds-view-modes__control > .ds-select {
      inline-size: 100%;
    }


    /* ==========================================================================
       Blog card

       One article as a card. Everything it is comes from the card, the media,
       the header and the footer; what lives here is the one decision those do
       not make: where the category stands.
       ========================================================================== */

    /* On the cover, in the corner, where a word costs no line of a card that
       has few: the first consumer put its category exactly here. Over whatever
       the picture is, which is why it arrives as a SOLID badge: the one fill
       with its own ground under the label. */
    .ds-blog-card__category {
      position: absolute;
      inset-block-end: var(--ds-space-2);
      inset-inline-start: var(--ds-space-2);
    }

    /* ==========================================================================
       Filter bar

       A strip of filters, one on: the categories over a blog's grid, the states
       over a list. The first consumer drew it by hand over its archive, chips,
       counts, radio semantics and all; what it could take from the system was
       only the drag on the strip.

       ── One choice, worn as chips ─────────────────────────────────────────────
       A radiogroup and not tabs, because choosing a filter changes what the list
       shows, not where the reader is; and not the segmented control, because a
       segmented is a closed box of two or three answers and this is an open row
       that grows with the content and scrolls when it outgrows the line. The
       chosen chip fills with the inverse pair, the same word every control here
       uses for "this one".

       The behaviour is the bundle's roving: one tab stop, arrows inside,
       `aria-checked` moved, the hidden carrier filled and saying so. What the
       choice MEANS is the page's business, same ruling as the consent.
       ========================================================================== */

    .ds-filter-bar {
      display: flex;
      align-items: center;
      gap: var(--ds-space-2);
      /* Scrolls rather than wraps, the tabs' own argument: a wrapped strip puts
         a second rail under the first. The drag hook makes the far end
         reachable with a plain mouse. */
      overflow-x: auto;
      padding-block: var(--ds-space-1);
    }

    /* ── Wrapped, on request ──────────────────────────────────────────────────
       The other answer, for the row that is a FACET list rather than a strip:
       fourteen categories a reader filters by want to be all visible, and a
       scroll would hide the ones that matter as much as the first. The first
       consumer's archive is exactly this. Wrapped, there is nothing to drag,
       and the renderers leave the drag hook off. */
    .ds-filter-bar--wrap {
      flex-wrap: wrap;
      overflow-x: visible;
    }

    .ds-filter-bar__chip {
      --ds-filter-bar-chip-ink: var(--ds-text-secondary-neutral);
      display: inline-flex;
      align-items: center;
      gap: var(--ds-space-2);
      flex: none;
      border: 1px solid var(--ds-border);
      border-radius: var(--ds-radius-full);
      background-color: transparent;
      color: var(--ds-filter-bar-chip-ink);
      font-family: var(--ds-filter-bar-chip-face);
      font-size: var(--ds-filter-bar-chip-size);
      font-weight: var(--ds-filter-bar-chip-weight);
      line-height: var(--ds-filter-bar-chip-leading);
      letter-spacing: var(--ds-filter-bar-chip-tracking);
      cursor: pointer;
      transition:
        background-color var(--ds-duration) var(--ds-ease),
        border-color var(--ds-duration) var(--ds-ease),
        color var(--ds-duration) var(--ds-ease);
    }

    /* ── The six sizes, on the control ramp ───────────────────────────────────
       The button's own arrangement, tier for tier and inset for inset, declared
       on the BAR so one word sizes the row. The default is small, and that is a
       decision: a chip is furniture beside the content, not a call to action,
       and inside a toolbar it stands level with the small controls a toolbar
       holds. */
    .ds-filter-bar--xsmall .ds-filter-bar__chip {
      --ds-filter-bar-chip-face: var(--ds-font-small-face);
      --ds-filter-bar-chip-size: var(--ds-font-small-size);
      --ds-filter-bar-chip-weight: var(--ds-font-small-weight);
      --ds-filter-bar-chip-leading: var(--ds-font-small-leading);
      --ds-filter-bar-chip-tracking: var(--ds-font-small-tracking);
      min-block-size: var(--ds-size-1);
      padding-inline: var(--ds-space-2);
    }

    .ds-filter-bar--small .ds-filter-bar__chip {
      --ds-filter-bar-chip-face: var(--ds-font-label-face);
      --ds-filter-bar-chip-size: var(--ds-font-label-size);
      --ds-filter-bar-chip-weight: var(--ds-font-label-weight);
      --ds-filter-bar-chip-leading: var(--ds-font-label-leading);
      --ds-filter-bar-chip-tracking: var(--ds-font-label-tracking);
      min-block-size: var(--ds-size-2);
      padding-inline: var(--ds-space-3);
    }

    .ds-filter-bar--medium .ds-filter-bar__chip {
      --ds-filter-bar-chip-face: var(--ds-font-body-face);
      --ds-filter-bar-chip-size: var(--ds-font-body-size);
      --ds-filter-bar-chip-weight: var(--ds-font-body-weight);
      --ds-filter-bar-chip-leading: var(--ds-font-body-leading);
      --ds-filter-bar-chip-tracking: var(--ds-font-body-tracking);
      min-block-size: var(--ds-size-3);
      padding-inline: var(--ds-space-4);
    }

    .ds-filter-bar--large .ds-filter-bar__chip {
      --ds-filter-bar-chip-face: var(--ds-font-body-face);
      --ds-filter-bar-chip-size: var(--ds-font-body-size);
      --ds-filter-bar-chip-weight: var(--ds-font-body-weight);
      --ds-filter-bar-chip-leading: var(--ds-font-body-leading);
      --ds-filter-bar-chip-tracking: var(--ds-font-body-tracking);
      min-block-size: var(--ds-size-4);
      padding-inline: var(--ds-space-5);
    }

    .ds-filter-bar--xlarge .ds-filter-bar__chip {
      --ds-filter-bar-chip-face: var(--ds-font-body-face);
      --ds-filter-bar-chip-size: var(--ds-font-body-size);
      --ds-filter-bar-chip-weight: var(--ds-font-body-weight);
      --ds-filter-bar-chip-leading: var(--ds-font-body-leading);
      --ds-filter-bar-chip-tracking: var(--ds-font-body-tracking);
      min-block-size: var(--ds-size-5);
      padding-inline: var(--ds-space-6);
    }

    .ds-filter-bar--xxlarge .ds-filter-bar__chip {
      --ds-filter-bar-chip-face: var(--ds-font-body-face);
      --ds-filter-bar-chip-size: var(--ds-font-body-size);
      --ds-filter-bar-chip-weight: var(--ds-font-body-weight);
      --ds-filter-bar-chip-leading: var(--ds-font-body-leading);
      --ds-filter-bar-chip-tracking: var(--ds-font-body-tracking);
      min-block-size: var(--ds-size-6);
      padding-inline: var(--ds-space-8);
    }

    .ds-filter-bar__chip:hover {
      --ds-filter-bar-chip-ink: var(--ds-text-primary-neutral);
      background-color: var(--ds-surface-hover);
    }

    .ds-filter-bar__chip:focus-visible {
      outline: 2px solid var(--ds-focus);
      outline-offset: 2px;
    }

    /* The one that is on: filled with the inverse pair, the word every control
       here uses for "this one", so the state is a colour and not only a ring. */
    .ds-filter-bar__chip[aria-checked="true"] {
      --ds-filter-bar-chip-ink: var(--ds-on-inverse);
      border-color: var(--ds-inverse);
      background-color: var(--ds-inverse);
    }

    /* The count rides along in the lesser ink, and on the filled chip it takes
       the lesser ink OF that ground: a number the same weight as its word would
       read as part of the name. */
    .ds-filter-bar__count {
      --ds-filter-bar-count-ink: var(--ds-text-tertiary-neutral);
      color: var(--ds-filter-bar-count-ink);
    }

    .ds-filter-bar__chip[aria-checked="true"] .ds-filter-bar__count {
      --ds-filter-bar-count-ink: var(--ds-secondary-on-inverse);
      color: var(--ds-filter-bar-count-ink);
    }

    /* ==========================================================================
       Blog article

       The screen is composition; what lives here is the four slots the first
       consumer added by hand and their arrangement. Each slot holds content AS
       WRITTEN, so the rules lay things out and decide nothing about the words.
       ========================================================================== */

    /* The step between the page and a big title: category, date, minutes,
       whatever the page hands over, on one wrapping line. */
    .ds-blog-article__kicker {
      display: flex;
      flex-wrap: wrap;
      align-items: center;
      gap: var(--ds-space-3);
    }

    /* Who wrote it on the left, what stands beside that on the right, and a
       narrow screen stacks them. */
    .ds-blog-article__byline {
      display: flex;
      flex-wrap: wrap;
      align-items: center;
      justify-content: space-between;
      gap: var(--ds-space-3);
    }

    /* The same thing in another format, after the reading: a quiet box, so it
       reads as a companion to the article rather than a paragraph of it. */
    .ds-blog-article__attachment {
      display: flex;
      flex-wrap: wrap;
      align-items: center;
      justify-content: space-between;
      gap: var(--ds-space-4);
      padding: var(--ds-space-4);
      border: 1px solid var(--ds-border);
      border-radius: var(--ds-radius-2);
    }

    /* ── Beside: the article and its aside, on the page's track ───────────────
       The column takes what the aside leaves and wraps under it when there is
       not enough; the prose inside holds its own reading measure, so the column
       may be any width and the lines stay readable. The basis is the first
       consumer's own number. */
    .ds-blog-article__row {
      display: flex;
      flex-wrap: wrap;
      align-items: flex-start;
      /* The block step and not the section's: the aside is part of this
         section, and the section gap, fluid up to 10rem, pushed the pair past
         the track by a hair and wrapped the aside under on any panel. */
      gap: var(--ds-rhythm-block);
    }

    .ds-blog-article__column {
      --ds-blog-article-column: 32rem;
      flex: 1 1 var(--ds-blog-article-column);
      min-inline-size: 0;
    }

    .ds-blog-article__aside {
      min-inline-size: 0;
    }

    /* ==========================================================================
       Section title

       The head of one band of a page: the section's name and, where there is
       one, the way to more of it, on one baseline over a rule. The first
       consumer drew it by hand as `gl-capo` and used it on every section of its
       home; what made a page read as one thing was every band opening the same
       way.

       The rule is the whole drawing, which is why it is 2px: at 1px it is the
       hairline every card already wears, and the head stops reading as a
       decision. Its colour is the component's own name, pointed at the resting
       line; the first consumer points it at its brand, which is exactly what
       the hook is for.

       The scroll offset under a sticky bar is NOT here: the shell already gives
       every `:target` inside a site the bar's margin, so an `id` on this head
       lands readable with no line of its own.
       ========================================================================== */

    .ds-section-title {
      display: flex;
      flex-wrap: wrap;
      align-items: baseline;
      justify-content: space-between;
      gap: var(--ds-space-2) var(--ds-space-6);
    }

    /* The rule is opt-in, owner's call: most sections open with the name alone,
       and a line nobody asked for is furniture. Asked for, it is the whole
       drawing, which is why it is 2px: at 1px it is the hairline every card
       already wears. */
    .ds-section-title--ruled {
      --ds-section-title-rule: var(--ds-border);
      border-block-start: 2px solid var(--ds-section-title-rule);
      padding-block-start: var(--ds-space-4);
    }

    .ds-section-title__title {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. */
      --ds-section-title-title-face: var(--ds-font-heading-2-face);
      --ds-section-title-title-size: var(--ds-font-heading-2-size);
      --ds-section-title-title-weight: var(--ds-font-heading-2-weight);
      --ds-section-title-title-leading: var(--ds-font-heading-2-leading);
      --ds-section-title-title-tracking: var(--ds-font-heading-2-tracking);
      margin: 0;
      font-family: var(--ds-section-title-title-face);
      font-size: var(--ds-section-title-title-size);
      font-weight: var(--ds-section-title-title-weight);
      line-height: var(--ds-section-title-title-leading);
      letter-spacing: var(--ds-section-title-title-tracking);
    }

    /* The way to more, as written: a link the page hands over. It reads in the
       brand's text colour and keeps to one line, because "See all" broken in
       two is a button that lost its box. */
    .ds-section-title__action {
      --ds-section-title-action-face: var(--ds-font-label-face);
      --ds-section-title-action-size: var(--ds-font-label-size);
      --ds-section-title-action-weight: var(--ds-font-label-weight);
      --ds-section-title-action-leading: var(--ds-font-label-leading);
      --ds-section-title-action-tracking: var(--ds-font-label-tracking);
      --ds-section-title-action-ink: var(--ds-brand-ink);
      color: var(--ds-section-title-action-ink);
      font-family: var(--ds-section-title-action-face);
      font-size: var(--ds-section-title-action-size);
      font-weight: var(--ds-section-title-action-weight);
      line-height: var(--ds-section-title-action-leading);
      letter-spacing: var(--ds-section-title-action-tracking);
      white-space: nowrap;
    }

    .ds-section-title__action a {
      color: inherit;
      text-decoration: none;
      /* Tall enough to hit: the line alone is under the smallest tier, and the
         negative margin keeps the words where they were, so the target grows
         and the drawing does not. */
      padding-block: var(--ds-space-1);
      margin-block: calc(var(--ds-space-1) * -1);
      transition: color var(--ds-duration) var(--ds-ease);
    }

    .ds-section-title__action a:hover {
      --ds-section-title-action-ink: var(--ds-text-primary-neutral);
      color: var(--ds-section-title-action-ink);
    }

    /* ==========================================================================
       Event card

       One event as a card: the anatomy every event marketplace converges on,
       read off Eventbrite's grid and Luma's rows on 3 September 2026. The card,
       the media, the header with its eyebrow and the footer do the standing;
       what lives here is the two decisions they do not make: where the status
       stands, and how the price reads.
       ========================================================================== */

    /* On the picture, in the corner, where the category badge of a blog card
       already stands: the one word that changes whether you can go has to be
       read before the title is, and a word on the media is read first. */
    .ds-event-card__status {
      position: absolute;
      inset-block-end: var(--ds-space-2);
      inset-inline-start: var(--ds-space-2);
    }

    /* What it costs, at the footer's far end, in the label's own voice but the
       full ink: on every marketplace the price is the second thing a reader
       checks, and a price in the lesser grey reads as a footnote. */
    .ds-event-card__price {
      --ds-event-card-price-face: var(--ds-font-label-face);
      --ds-event-card-price-size: var(--ds-font-label-size);
      --ds-event-card-price-weight: var(--ds-emphasis-weight);
      --ds-event-card-price-leading: var(--ds-font-label-leading);
      --ds-event-card-price-tracking: var(--ds-font-label-tracking);
      --ds-event-card-price-ink: var(--ds-text-primary-neutral);
      margin-inline-start: auto;
      font-family: var(--ds-event-card-price-face);
      font-size: var(--ds-event-card-price-size);
      font-weight: var(--ds-event-card-price-weight);
      line-height: var(--ds-event-card-price-leading);
      letter-spacing: var(--ds-event-card-price-tracking);
      color: var(--ds-event-card-price-ink);
    }

    /* ==========================================================================
       Agenda

       What is coming, day by day: the arrangement every events calendar
       converges on, read off Luma's city pages on 3 September 2026. A rail with
       the date, the day's events beside it, and a reader who scans the rail for
       a day, then the rows for an evening.

       Every date and time arrives AS WRITTEN: the component draws the grid and
       decides nothing about the words, which is the same ruling the blog
       article's slots follow, for the same two-platform reason.
       ========================================================================== */

    .ds-agenda {
      display: flex;
      flex-direction: column;
      gap: var(--ds-rhythm-block);
      /* The day asks its own container, because a component does not know the
         window: inside a drawer or a panel the rail folds at the same content
         width it folds at on a phone. */
      container-type: inline-size;
      container-name: ds-agenda;
    }

    /* ── One day ──────────────────────────────────────────────────────────────
       The rail and the rows. The rail's width is the component's own name, so a
       project with long month names moves one number. */
    .ds-agenda__day {
      --ds-agenda-rail: 10rem;
      display: grid;
      grid-template-columns: var(--ds-agenda-rail) 1fr;
      gap: var(--ds-space-4) var(--ds-space-6);
      align-items: start;
    }

    /* The date is a heading, so the outline of the page is the calendar itself:
       a reader moving heading to heading lands day by day. */
    .ds-agenda__date {
      --ds-agenda-date-face: var(--ds-font-copy-face);
      --ds-agenda-date-size: var(--ds-font-copy-size);
      --ds-agenda-date-weight: var(--ds-emphasis-weight);
      --ds-agenda-date-leading: var(--ds-font-copy-leading);
      --ds-agenda-date-tracking: var(--ds-font-copy-tracking);
      margin: 0;
      display: flex;
      flex-direction: column;
      gap: var(--ds-space-1);
      font-family: var(--ds-agenda-date-face);
      font-size: var(--ds-agenda-date-size);
      font-weight: var(--ds-agenda-date-weight);
      line-height: var(--ds-agenda-date-leading);
      letter-spacing: var(--ds-agenda-date-tracking);
    }

    /* The day's name, the quieter half of the rail, in the metadata voice. */
    .ds-agenda__date-name {
      --ds-agenda-date-name-face: var(--ds-font-micro-face);
      --ds-agenda-date-name-size: var(--ds-font-micro-size);
      --ds-agenda-date-name-weight: var(--ds-font-micro-weight);
      --ds-agenda-date-name-leading: var(--ds-font-micro-leading);
      --ds-agenda-date-name-tracking: var(--ds-font-micro-tracking);
      --ds-agenda-date-name-ink: var(--ds-text-secondary-neutral);
      font-family: var(--ds-agenda-date-name-face);
      font-size: var(--ds-agenda-date-name-size);
      font-weight: var(--ds-agenda-date-name-weight);
      line-height: var(--ds-agenda-date-name-leading);
      letter-spacing: var(--ds-agenda-date-name-tracking);
      text-transform: uppercase;
      color: var(--ds-agenda-date-name-ink);
    }

    .ds-agenda__events {
      margin: 0;
      padding: 0;
      list-style: none;
      display: flex;
      flex-direction: column;
      min-inline-size: 0;
    }

    /* A hairline between two rows and none around the block: the rows read as a
       list, not as a stack of boxes. */
    .ds-agenda__slot + .ds-agenda__slot {
      border-block-start: 1px solid var(--ds-border-subtle);
    }

    /* ── One event ────────────────────────────────────────────────────────────
       The whole row is the link: time, then the name over its quiet line, then
       the note at the end. */
    .ds-agenda__event {
      display: flex;
      align-items: baseline;
      gap: var(--ds-space-4);
      padding-block: var(--ds-space-3);
      padding-inline: var(--ds-space-2);
      border-radius: var(--ds-radius-2);
      color: inherit;
      text-decoration: none;
      transition: background-color var(--ds-duration) var(--ds-ease);
    }

    .ds-agenda__event:hover {
      background-color: var(--ds-surface-hover);
    }

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

    /* The time holds a column of its own, so the names start in line down the
       whole day: a ragged left edge is what makes a list slow to scan. */
    .ds-agenda__time {
      --ds-agenda-time-face: var(--ds-font-label-face);
      --ds-agenda-time-size: var(--ds-font-label-size);
      --ds-agenda-time-weight: var(--ds-font-label-weight);
      --ds-agenda-time-leading: var(--ds-font-label-leading);
      --ds-agenda-time-tracking: var(--ds-font-label-tracking);
      --ds-agenda-time-ink: var(--ds-text-secondary-neutral);
      flex: none;
      inline-size: var(--ds-space-10);
      font-family: var(--ds-agenda-time-face);
      font-size: var(--ds-agenda-time-size);
      font-weight: var(--ds-agenda-time-weight);
      line-height: var(--ds-agenda-time-leading);
      letter-spacing: var(--ds-agenda-time-tracking);
      color: var(--ds-agenda-time-ink);
      font-variant-numeric: tabular-nums;
    }

    .ds-agenda__what {
      display: flex;
      flex-direction: column;
      gap: var(--ds-space-1);
      flex: 1;
      min-inline-size: 0;
    }

    .ds-agenda__title {
      --ds-agenda-title-face: var(--ds-font-body-face);
      --ds-agenda-title-size: var(--ds-font-body-size);
      --ds-agenda-title-weight: var(--ds-emphasis-weight);
      --ds-agenda-title-leading: var(--ds-font-body-leading);
      --ds-agenda-title-tracking: var(--ds-font-body-tracking);
      font-family: var(--ds-agenda-title-face);
      font-size: var(--ds-agenda-title-size);
      font-weight: var(--ds-agenda-title-weight);
      line-height: var(--ds-agenda-title-leading);
      letter-spacing: var(--ds-agenda-title-tracking);
    }

    .ds-agenda__detail {
      --ds-agenda-detail-face: var(--ds-font-small-face);
      --ds-agenda-detail-size: var(--ds-font-small-size);
      --ds-agenda-detail-weight: var(--ds-font-small-weight);
      --ds-agenda-detail-leading: var(--ds-font-small-leading);
      --ds-agenda-detail-tracking: var(--ds-font-small-tracking);
      --ds-agenda-detail-ink: var(--ds-text-secondary-neutral);
      font-family: var(--ds-agenda-detail-face);
      font-size: var(--ds-agenda-detail-size);
      font-weight: var(--ds-agenda-detail-weight);
      line-height: var(--ds-agenda-detail-leading);
      letter-spacing: var(--ds-agenda-detail-tracking);
      color: var(--ds-agenda-detail-ink);
    }

    /* The price, or the reason you cannot go: one line at the end, in the full
       ink, because it is the second thing a reader checks. */
    .ds-agenda__note {
      --ds-agenda-note-face: var(--ds-font-label-face);
      --ds-agenda-note-size: var(--ds-font-label-size);
      --ds-agenda-note-weight: var(--ds-emphasis-weight);
      --ds-agenda-note-leading: var(--ds-font-label-leading);
      --ds-agenda-note-tracking: var(--ds-font-label-tracking);
      --ds-agenda-note-ink: var(--ds-text-primary-neutral);
      flex: none;
      margin-inline-start: auto;
      font-family: var(--ds-agenda-note-face);
      font-size: var(--ds-agenda-note-size);
      font-weight: var(--ds-agenda-note-weight);
      line-height: var(--ds-agenda-note-leading);
      letter-spacing: var(--ds-agenda-note-tracking);
      color: var(--ds-agenda-note-ink);
      white-space: nowrap;
    }

    /* ── The narrow fold ──────────────────────────────────────────────────────
       Under the fold the rail stands over its day instead of beside it. The
       width is `--ds-break-phone`, written out because a container query cannot
       read a token in its condition; the check holds the two in step. */
    @container ds-agenda (width <= 40rem) {
      .ds-agenda__day {
        grid-template-columns: 100%;
        gap: var(--ds-space-2);
      }
    }

    /* ==========================================================================
       Tooltip

       The CSS behind <Tooltip>. Private, like every other component's.

       A word about the control it is attached to, on hover and on focus.

       ── It shows itself ───────────────────────────────────────────────────────
       `:hover` and `:focus-visible` on the trigger are the two things that open a
       tooltip, and CSS reads both, so it opens on a page whose JavaScript has not
       loaded, which is when a reader is most lost.

       ── It flips when the room runs out ───────────────────────────────────────
       The one thing CSS cannot do is measure, so the measuring lives with the
       behaviour: React's own handlers on one side, `data-ds-tooltip` in the
       bundle on the other. Both answer with `data-flip` on the wrapper, state
       this sheet reads, and each place rule below is written as a pair so that a
       flipped `top` IS `bottom`'s rule rather than a second copy of it. Without
       the script the attribute never appears and the bubble stays where it was
       put, which is the same page as before the flip existed.

       ── It never holds the only copy of anything ──────────────────────────────
       It cannot be reached by touch, it is invisible to a magnified viewport
       scrolled elsewhere, and it disappears the moment the pointer moves. A label
       is a label; this is a footnote.
       ========================================================================== */

    .ds-tooltip {
      position: relative;
      display: inline-flex;
    }

    .ds-tooltip__bubble {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-tooltip-bubble-face: var(--ds-font-small-face);
      --ds-tooltip-bubble-tracking: var(--ds-font-small-tracking);
      --ds-tooltip-bubble-size: var(--ds-font-small-size);
      --ds-tooltip-bubble-weight: var(--ds-font-small-weight);
      --ds-tooltip-bubble-leading: var(--ds-font-small-leading);
      position: absolute;
      z-index: 2;
      /* Out of reach and out of the tree until it is wanted. `visibility` and not
         `display`, so the transition has something to run on. */
      visibility: hidden;
      opacity: 0;
      /* Never the target: a box that appears under the pointer and takes the
         pointer is a box that flickers as the two fight. */
      pointer-events: none;
      inline-size: max-content;
      max-inline-size: 16rem;
      padding: var(--ds-space-2) var(--ds-space-3);
      border-radius: var(--ds-radius-2);
      background-color: var(--ds-inverse);
      --ds-tooltip-bubble-ink: var(--ds-on-inverse);
      color: var(--ds-tooltip-bubble-ink);
      font-family: var(--ds-tooltip-bubble-face);
      font-size: var(--ds-tooltip-bubble-size);
      letter-spacing: var(--ds-tooltip-bubble-tracking);
      font-weight: var(--ds-tooltip-bubble-weight);
      line-height: var(--ds-tooltip-bubble-leading);
      text-wrap: pretty;
      transition:
        opacity var(--ds-duration) var(--ds-ease),
        visibility var(--ds-duration) var(--ds-ease);
    }

    .ds-tooltip:hover .ds-tooltip__bubble,
    .ds-tooltip:focus-within .ds-tooltip__bubble {
      visibility: visible;
      opacity: 1;
    }

    /* Where it sits. Above by default, since below is where a menu opens and a
       reader who has learned that reads the two as the same thing.

       Each pair is one arrangement with two names: the place, and the opposite
       place once the script has measured and written `data-flip`. The `:not()`
       keeps the two selectors of a pair at the same specificity, so which rule
       wins is never a question of order. */
    .ds-tooltip--top:not([data-flip]) .ds-tooltip__bubble,
    .ds-tooltip--bottom[data-flip] .ds-tooltip__bubble {
      inset-block-end: calc(100% + var(--ds-space-2));
      inset-inline-start: 50%;
      translate: -50% 0;
    }

    .ds-tooltip--bottom:not([data-flip]) .ds-tooltip__bubble,
    .ds-tooltip--top[data-flip] .ds-tooltip__bubble {
      inset-block-start: calc(100% + var(--ds-space-2));
      inset-inline-start: 50%;
      translate: -50% 0;
    }

    .ds-tooltip--start:not([data-flip]) .ds-tooltip__bubble,
    .ds-tooltip--end[data-flip] .ds-tooltip__bubble {
      inset-inline-end: calc(100% + var(--ds-space-2));
      inset-block-start: 50%;
      translate: 0 -50%;
    }

    .ds-tooltip--end:not([data-flip]) .ds-tooltip__bubble,
    .ds-tooltip--start[data-flip] .ds-tooltip__bubble {
      inset-inline-start: calc(100% + var(--ds-space-2));
      inset-block-start: 50%;
      translate: 0 -50%;
    }

    /* ==========================================================================
       Selection bar

       The CSS behind <SelectionBar>. Private, like every other component's.

       The bar that appears once rows are ticked: how many, who they are, and what
       can be done to them. It sticks to the foot of whatever scrolls the rows,
       inside the column they are in, because that is where it stays reachable
       while you carry on ticking: pinned above the list it scrolls away, and
       inside the toolbar it takes the place of the filters at the exact moment
       somebody is using them. It was fixed to the window until 11 September
       2026, and in a shell with a sidebar that put it across the sidebar and,
       where the shell kept room for a row of places that was not there, a hand's
       width above the foot: the owner, "troppo alta, in una posizione strana".
       Sticky in the column, it can only ever be where the rows are.

       ── The inverse ground ────────────────────────────────────────────────────
       The page quoted back at itself, white on the dark theme and near-black on
       the light, the way the toast is: the one thing that stands over the rows
       is the one thing that cannot be the colour of what it covers. It wears
       `data-ground="inverse"`, so the neutral inks and the buttons inside answer
       it on their own. It was a surface with a surface's line for a while, on
       the argument that a floating thing should obey the theme; the owner, 11
       September 2026: inverted, and everything in it with it.

       ── Wide ──────────────────────────────────────────────────────────────────
       It spans the page rather than hugging its own contents. What it holds is
       the count at one end and the actions at the other, and a pill that shrinks
       to fit puts those two a centimetre apart while the reader is looking at a
       list that is a screen wide.
       ========================================================================== */

    .ds-selection-bar {
      /* This component's own. The role says what the words are; this says that
         THIS component wants a heavier hand than the role has, which is a
         decision the component makes and nobody else. Pointed at the semantic
         rather than holding a number, so moving the system moves every component
         that has not been moved on purpose, and moving one moves one. */
      position: sticky;
      /* A step off the foot of the scrollport, and in the flow of the column it
         belongs to: a sticky box can only stand where its column stands, which is
         the whole reason it is sticky and not fixed. */
      inset-block-end: var(--ds-space-4);
      z-index: 10;
      /* Both insets set, so it stretches instead of sizing to its contents. */

      display: flex;
      align-items: center;
      gap: var(--ds-space-4);
      flex-wrap: wrap;
      padding: var(--ds-space-2) var(--ds-space-3);
      border-radius: var(--ds-radius-4);
      background-color: var(--ds-inverse);
      --ds-selection-bar-ink: var(--ds-on-inverse);
      color: var(--ds-selection-bar-ink);

      /* Arrives from just below, which is the direction it came from. The duration
         is the motion token, so a reduced-motion preference takes it to zero and
         the bar simply appears: still there, still where it belongs. */
      animation: ds-selection-in var(--ds-duration) var(--ds-ease-out);
    }

    @keyframes ds-selection-in {
      from {
        opacity: 0;
        translate: 0 var(--ds-space-3);
      }
    }

    /* The word and the number, in that order: the word does not change and the
       number does, so the number goes in the badge where a changing thing belongs
       and the sentence stops rewriting itself under the reader. */
    .ds-selection-bar__count {
      --ds-selection-bar-count-weight: var(--ds-emphasis-weight);
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-selection-bar-count-face: var(--ds-font-label-face);
      --ds-selection-bar-count-tracking: var(--ds-font-label-tracking);
      --ds-selection-bar-count-size: var(--ds-font-label-size);
      --ds-selection-bar-count-leading: var(--ds-font-label-leading);
      display: flex;
      align-items: center;
      gap: var(--ds-space-2);
      font-family: var(--ds-selection-bar-count-face);
      font-size: var(--ds-selection-bar-count-size);
      letter-spacing: var(--ds-selection-bar-count-tracking);
      line-height: var(--ds-selection-bar-count-leading);
      font-weight: var(--ds-selection-bar-count-weight);
    }

    /* Pushed to the far end, so the count and the faces stay together on the left
       and everything you can do stays together on the right. */
    .ds-selection-bar__actions {
      display: flex;
      align-items: center;
      gap: var(--ds-space-2);
      margin-inline-start: auto;
    }

    /* ==========================================================================
       Drawer

       The CSS behind <Drawer>. Private, like every other component's.

       A panel that comes in from the side of the screen and has to be closed
       before the page underneath is usable again: filters, the details of a row,
       a form that would take the page over if it were a page.

       ── Over the page, or beside it ───────────────────────────────────────────
       Two modes, because a panel that covers what it is about is the wrong panel:
       filters that hide the table while you filter it are filters you apply
       blind. `overlay` is the sheet over the page, for details and for forms;
       `inline` sits in the layout and pushes the content along, which is what a
       filter panel wants.

       The two share everything above the behaviour: the three bands, the title,
       the widths, the way out. What differs is whether the page behind is still
       yours.

       ── The overlay is a dialog ───────────────────────────────────────────────
       The element, not a div pretending. `showModal()` gives the top layer, the
       focus held inside, Escape, the page behind made inert and a backdrop that
       is a pseudo-element rather than a second div. All of that is behaviour
       nobody here has to write, and all of it is behaviour that goes subtly wrong
       when it is written.
       ========================================================================== */

    /* A closed dialog is hidden by the user agent with `dialog:not([open])`,
       which is an element selector and loses to the class below. Without this the
       shut drawer stands in the page as a plain white column: the rule that gives
       it `display: flex` also gives it away.

       The inline one is exempt: it is an <aside> and is never `open`. */
    .ds-drawer:not(.ds-drawer--inline):not([open]) {
      display: none;
    }

    .ds-drawer {
      /* The user agent centres a dialog and gives it a border and a margin. It is
         a sheet against one edge, full height, so all three go. */
      position: fixed;
      inset-block: 0;
      margin: 0;
      margin-inline-start: auto;
      max-block-size: 100dvh;
      block-size: 100dvh;
      inline-size: min(var(--ds-drawer-width), 100vw);
      max-inline-size: 100vw;

      display: flex;
      flex-direction: column;
      padding: 0;
      border: 0;
      border-inline-start: 1px solid var(--ds-border);
      background-color: var(--ds-surface-1);
      --ds-drawer-ink: var(--ds-text-primary-neutral);
      color: var(--ds-drawer-ink);
      overflow: hidden;
    }

    /* From the left instead, for a navigation sheet. */
    .ds-drawer--start {
      margin-inline: 0 auto;
      border-inline: 0 1px solid var(--ds-border);
    }

    .ds-drawer--small { --ds-drawer-width: 20rem; }
    .ds-drawer--medium { --ds-drawer-width: 26rem; }
    .ds-drawer--large { --ds-drawer-width: 34rem; }

    /* It does not move, and that is deliberate.

       It slid in, first as a keyframe animation and then as a transition out of
       `@starting-style`, and both put the panel's POSITION in the hands of the
       motion: the movement started fully off screen and the resting place was
       wherever the movement ended. Anything that stops the movement therefore
       leaves the panel off screen, and what the reader gets for pressing the mark
       is a page that goes dark and nothing else. Measured in that state:
       `playState: running`, `currentTime: 0`, `translate: -100%`, left -320 of a
       320 wide panel, with the backdrop showing perfectly.

       A sheet that slides is nicer than a sheet that appears. A sheet that
       sometimes never arrives is broken, and the second is not worth the first.
       The backdrop keeps its own fade, which fails safe: it is behind the panel
       either way.

       If this is ever animated again, animate something that is NOT the panel's
       position — opacity on the backdrop, a scale that starts at 1 — so that a
       frame that never paints leaves a drawer that is simply open. */

    /* The page behind, dimmed. The wash is a token, because the day a modal
       arrives the two of them have to agree, and because it is heavier on the
       dark theme where 40% of near-black over near-black reads as nothing. */
    .ds-drawer::backdrop {
      background-color: var(--ds-scrim);
    }

    /* ── The three bands ──────────────────────────────────────────────────────
       A head that names it, a body that scrolls, a foot that decides. The head
       and the foot hold still: a panel where the title scrolls away is a panel
       you get lost in. */
    .ds-drawer__head {
      display: flex;
      align-items: center;
      gap: var(--ds-space-3);
      flex: none;
      padding: var(--ds-space-4);
      border-block-end: 1px solid var(--ds-border);
    }

    .ds-drawer__title {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-drawer-title-face: var(--ds-font-heading-5-face);
      --ds-drawer-title-tracking: var(--ds-font-heading-5-tracking);
      --ds-drawer-title-size: var(--ds-font-heading-5-size);
      --ds-drawer-title-leading: var(--ds-font-heading-5-leading);
      display: flex;
      align-items: center;
      gap: var(--ds-space-2);
      flex: 1;
      min-inline-size: 0;
      font-family: var(--ds-drawer-title-face);
      font-size: var(--ds-drawer-title-size);
      letter-spacing: var(--ds-drawer-title-tracking);
      font-weight: var(--ds-font-heading-5-weight);
      line-height: var(--ds-drawer-title-leading);
    }

    .ds-drawer__body {
      flex: 1;
      min-block-size: 0;
      overflow-y: auto;
      padding: var(--ds-space-4);
    }

    .ds-drawer__foot {
      display: flex;
      align-items: center;
      gap: var(--ds-space-3);
      flex: none;
      padding: var(--ds-space-4);
      border-block-start: 1px solid var(--ds-border);
    }

    /* ── Inline ───────────────────────────────────────────────────────────────
       Not fixed, not in the top layer, no backdrop: a panel in the flow, so the
       content beside it gets the room that is left. The caller puts the two in a
       row and the browser does the pushing.

       It keeps its own scrolling and its own bands, which is the whole reason it
       is this component and not a div: a filter panel that scrolls the page
       instead of itself takes the table with it. */
    .ds-drawer--inline {
      position: static;
      /* The sides (--start, --end) place the overlay with auto margins, and an
         auto margin in a flex row eats every free pixel: the table beside it
         ended at the far end of the row with a hole between, open or shut. The
         inline panel stands in the flow, and the caller's row decides its place
         and its step.

         The step is the row's, on both sides. Until 10 September 2026 this took
         the row's gap back on the content side with a negative margin, on the
         owner's ruling of 25 August that a border and a step beside a TABLE were
         the same thing said twice. Beside a toolbar and a column of cards, which
         is what the first product put next to it, the panel stood glued to the
         controls; the owner, 10 September: "e' proprio attaccato". A panel is a
         panel whatever it stands beside, so it keeps the step the caller composed
         the row with, and a caller that wants it flush against a sheet composes
         the row with `gap="none"`. */
      margin-inline: 0;
      /* A panel is its width: in the row the caller puts it in, the shrinking
         belongs to the thing beside it. Without this, a table with 148 rows of
         appetite pressed the OPEN panel to its two border pixels, measured on
         the CMS desk on 24 August 2026. */
      flex: none;
      inline-size: var(--ds-drawer-width);
      max-inline-size: 100%;
      block-size: auto;
      max-block-size: none;
      /* As tall as what it sits beside, so the foot lands on the bottom of the
         table rather than halfway up it. */
      align-self: stretch;
      border-radius: var(--ds-radius-3);
      border: 1px solid var(--ds-border);
      /* The width is what is animated, and animating it is what moves the thing
         beside it: a flex row recalculates every frame, so the table slides over
         rather than jumping. Everything that takes room goes with it, or a closed
         panel is still two pixels of border and a padding wide.

         The duration is the motion token, so a reduced-motion preference takes it
         to zero and the panel is simply there or not. */
      transition:
        inline-size var(--ds-duration) var(--ds-ease-out),
        opacity var(--ds-duration) var(--ds-ease-out),
        border-width var(--ds-duration) var(--ds-ease-out);
    }

    /* What stands beside an inline panel in a row takes the room the panel
       leaves, and gives way first: a sheet of rows has an appetite of its own
       and would otherwise press the open panel to its border, or sit at its own
       width with a hole beside it. The docs had said this with a class of their
       own on every page that paired the two; it is the pairing's rule. */
    .ds-row:has(> .ds-drawer--inline) > :not(.ds-drawer) {
      flex: 1;
      min-inline-size: 0;
    }

    /* The promise the header makes, enforced: the content beside the panel
       gets the room that is left. Without this, a narrow table shrank to fit
       and floated in the row.
       Not another panel, though. A row with a drawer on each side, filters at
       the start and the view at the end, put the end one after the start one,
       and this rule handed it the room meant for the table: open it measured
       408 against the 322 of its own size, and shut it kept 248 of width while
       the table beside them had 86 (measured 5 September 2026 on the
       playground). A panel is its width whichever side it is on. */
    .ds-drawer--inline ~ :not(.ds-drawer--inline) {
      flex: 1 1 auto;
      min-inline-size: 0;
    }

    /* In a card the toolbar stands at its own inset and the table bleeds to
       the edge: the panel belongs with the CONTROLS, not with the bleed, so it
       takes the toolbar's inset. Same token, measured against the Filters
       button it opens from (owner's call, 24 August 2026). */
    .ds-card .ds-drawer--inline {
      /* Il token del componente, puntato al passo che la toolbar usa per il
         suo padding: se quel passo cambia, cambiano insieme. */
      --ds-drawer-inset: var(--ds-space-4);
      margin-inline-start: var(--ds-drawer-inset);
    }

    /* From the end, the inset is on the other side. */
    .ds-card .ds-drawer--inline.ds-drawer--end {
      margin-inline: 0 var(--ds-drawer-inset);
    }

    /* Shut: no width, no edges, nothing to see. It stays in the tree, because
       something has to be there to animate; `inert` is what keeps it out of the
       tab order and out of the accessible tree while it is closed. */
    .ds-drawer--inline[data-open="false"] {
      /* Ferreo, su ogni motore: base flex a zero esplicita (con `auto`, un
         motore puo' risolvere sul contenuto), larghezza e minimo a zero,
         niente bordi, e overflow chiuso cosi' la testa del pannello non puo'
         puntellarlo da dentro. */
      flex-basis: 0;
      /* And no growing, said outright: a basis of zero on a box that may grow
         is a box as wide as the room lets it. */
      flex-grow: 0;
      inline-size: 0;
      min-inline-size: 0;
      border-inline-width: 0;
      padding-inline: 0;
      /* Shut, it leaves neither the inset nor the row's step: a box zero pixels
         wide still cost the 12px gap, and the table started off the card's edge
         with nothing to explain it. Open, the step is the row's; shut, the panel
         gives it back by reading the row's own `--ds-grid-gap`, which the layout
         writes on the row for exactly this. */
      margin-inline: 0 calc(var(--ds-grid-gap, 0px) * -1);
      /* Ne' altezza. A larghezza zero il contenuto continua a impilarsi: un
         pannello di filtri chiuso misurava 1688px dentro una riga alta 744,
         e la pagina cresceva oltre la finestra per una colonna che nessuno
         vede (misurato il 25 agosto 2026). Con overflow chiuso e opacita' a
         zero non c'e' niente da mostrare, quindi non c'e' niente da occupare. */
      block-size: 0;
      min-block-size: 0;
      padding-block: 0;
      border-block-width: 0;
      opacity: 0;
      overflow: hidden;
    }

    /* Shut from the end, the step it gives back is on its other side. */
    .ds-drawer--inline.ds-drawer--end[data-open="false"],
    .ds-card .ds-drawer--inline.ds-drawer--end[data-open="false"] {
      margin-inline: calc(var(--ds-grid-gap, 0px) * -1) 0;
    }

    /* ── On a phone, a sheet from the bottom ────────────────────────────────────
       A drawer against the end edge is a column of the screen, and a phone has no
       column to spare: under the shell's phone break the overlay drawer takes the
       bottom sheet's shape, as wide as the screen, capped at the same ceiling,
       round at the top. The width is `--ds-break-phone`, written out because a
       container query cannot read a token in its condition; the check holds the
       two in step. Asked by the shell's name, since a modal dialog has no width
       of its own to ask. The inline drawer is a column of the page and stays. */
    @container ds-app-shell (width <= 40rem) {
      .ds-drawer:not(.ds-drawer--inline) {
        --ds-drawer-ceiling: 85dvh;
        /* Against the bottom and as tall as what it holds: `inset: 0` with the
           margins doing the work stretched it to the ceiling whatever was in it,
           measured 718 of 844 around four lines of properties. */
        inset: auto 0 0;
        margin: 0;
        inline-size: 100%;
        max-inline-size: 100%;
        block-size: auto;
        max-block-size: var(--ds-drawer-ceiling);
        border: 0;
        border-block-start: 1px solid var(--ds-border);
        border-start-start-radius: var(--ds-radius-4);
        border-start-end-radius: var(--ds-radius-4);
      }
    }

    /* ==========================================================================
       Dialog

       The CSS behind <Dialog>. Private, like every other component's.

       A card in the middle of the screen that has to be answered before anything
       else can be: a confirmation, a short form, a thing to read and accept, a
       deletion to say yes to.

       ── The element does the hard half ───────────────────────────────────────
       `<dialog>` opened with `showModal()`: top layer, focus held inside, Escape,
       the page behind made inert, and a backdrop that is a pseudo-element rather
       than a second div. All of that is behaviour nobody here has to write, and
       all of it is behaviour that goes subtly wrong when it is written.

       ── Three bands, and only the middle one moves ───────────────────────────
       A head that names it, a body that scrolls, a foot that decides. The head
       and the foot hold still, so what a reader is being asked and what they can
       answer are both on screen the whole time: a confirmation whose buttons have
       scrolled out of view reads as a page with no way out.
       ========================================================================== */

    /* A closed dialog is hidden by the user agent with `dialog:not([open])`,
       which is an element selector and loses to the class below. Without this the
       shut dialog stands in the middle of the page as a plain white card.

       On the element and not on the class alone, because the inline card is a
       section and has no `open` to carry: written the other way it hid the one
       specimen that is meant to be looked at. */
    dialog.ds-dialog:not([open]) {
      display: none;
    }

    .ds-dialog {
      /* Il proprio allineamento, sempre. Un dialog e' disegnato nel top layer ma
         EREDITA dal punto del DOM dove sta il suo markup: aperto da un bottone
         dentro la cella delle azioni di una tabella — `text-align: end`, perche'
         li' i comandi stanno a destra — la sua riga sotto il titolo usciva
         allineata a destra mentre il titolo stava a sinistra. Chi scrive il
         dialog non sa da dove verra' aperto, e non deve saperlo. Segnalato dal
         proprietario il 20 settembre 2026. */
      text-align: start;
      /* This component's own. The role says what the words are; this says that
         THIS component wants a heavier hand than the role has, which is a
         decision the component makes and nobody else. Pointed at the semantic
         rather than holding a number, so moving the system moves every component
         that has not been moved on purpose, and moving one moves one. */
      display: flex;
      flex-direction: column;
      /* Centred by the user agent's own `margin: auto` on a modal dialog, which
         is why there is no positioning here at all. */
      inline-size: var(--ds-dialog-width);
      /* The gutter is the page's own step, so a dialog on a phone stands in the
         same margin every other thing on that screen does. */
      max-inline-size: calc(100vw - var(--ds-space-8));
      max-block-size: calc(100dvh - var(--ds-space-8));
      padding: 0;
      border: 1px solid var(--ds-border);
      border-radius: var(--ds-radius-4);
      background-color: var(--ds-surface-1);
      --ds-dialog-ink: var(--ds-text-primary-neutral);
      color: var(--ds-dialog-ink);
    }

    .ds-dialog--small { --ds-dialog-width: 20rem; }
    .ds-dialog--medium { --ds-dialog-width: 28rem; }
    .ds-dialog--large { --ds-dialog-width: 36rem; }

    /* Up from the middle, a little, and only as far as the motion token allows:
       a card that flies in from an edge is a card the eye follows instead of
       reads, and this one is asking a question. At zero duration it is simply
       there. */
    .ds-dialog[open] {
      animation: ds-dialog-in var(--ds-duration) var(--ds-ease-out);
    }

    @keyframes ds-dialog-in {
      from {
        opacity: 0;
        translate: 0 var(--ds-space-2);
      }
    }

    .ds-dialog::backdrop {
      background-color: var(--ds-scrim);
    }

    /* In the flow, and answering nothing. It keeps the card and gives up the
       things that belong to being modal: no top layer, no backdrop, no way out.
       What it is for is showing the shape of one. */
    .ds-dialog--inline {
      position: static;
      max-inline-size: 100%;
      animation: none;
    }

    .ds-dialog__head {
      display: flex;
      align-items: start;
      justify-content: space-between;
      gap: var(--ds-space-3);
      flex: none;
      padding: var(--ds-space-4);
      padding-block-end: 0;
    }

    /* The question, and it is a heading: a dialog is a page's worth of attention
       in a card, and what it is asking is the first thing on it. */
    .ds-dialog__title {
      --ds-dialog-title-weight: var(--ds-emphasis-weight);
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-dialog-title-face: var(--ds-font-body-face);
      --ds-dialog-title-tracking: var(--ds-font-body-tracking);
      --ds-dialog-title-size: var(--ds-font-body-size);
      --ds-dialog-title-leading: var(--ds-font-body-leading);
      margin: 0;
      font-family: var(--ds-dialog-title-face);
      font-size: var(--ds-dialog-title-size);
      letter-spacing: var(--ds-dialog-title-tracking);
      line-height: var(--ds-dialog-title-leading);
      font-weight: var(--ds-dialog-title-weight);
      --ds-dialog-title-ink: var(--ds-text-primary-neutral);
      color: var(--ds-dialog-title-ink);
    }

    .ds-dialog__body {
      flex: 1;
      min-block-size: 0;
      overflow-y: auto;
      display: flex;
      flex-direction: column;
      gap: var(--ds-rhythm-element);
      padding: var(--ds-space-4);
    }

    /* The line under the title, when there is one. It reads as the sentence the
       question is asked in, so it takes the reading colour rather than the quiet
       one a hint would. */
    .ds-dialog__lede {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-dialog-lede-face: var(--ds-font-label-face);
      --ds-dialog-lede-tracking: var(--ds-font-label-tracking);
      --ds-dialog-lede-size: var(--ds-font-label-size);
      --ds-dialog-lede-weight: var(--ds-font-label-weight);
      --ds-dialog-lede-leading: var(--ds-font-label-leading);
      margin: 0;
      font-family: var(--ds-dialog-lede-face);
      font-size: var(--ds-dialog-lede-size);
      letter-spacing: var(--ds-dialog-lede-tracking);
      font-weight: var(--ds-dialog-lede-weight);
      line-height: var(--ds-dialog-lede-leading);
      --ds-dialog-lede-ink: var(--ds-text-secondary-neutral);
      color: var(--ds-dialog-lede-ink);
    }

    /* What decides, at the end of the line. The answer sits on the side the eye
       leaves from, and the way out sits beside it: a dialog with its buttons in
       the middle makes the reader look for which one is the answer. */
    .ds-dialog__foot {
      display: flex;
      align-items: center;
      justify-content: flex-end;
      gap: var(--ds-space-2);
      flex: none;
      flex-wrap: wrap;
      padding: var(--ds-space-4);
      padding-block-start: 0;
    }

    /* The answer of a confirm dialog is a form, so a page the server renders
       posts it as rendered. It stands in the foot as one item and lays its two
       buttons out the way the foot would have: the way out first, the answer
       last, the same step between them. */
    .ds-dialog__answer {
      display: flex;
      align-items: center;
      justify-content: flex-end;
      gap: var(--ds-space-2);
      flex-wrap: wrap;
      margin: 0;
    }

    /* ==========================================================================
       Link

       The CSS behind <Link>. Private, like every other component's.

       The base stylesheet already dresses a bare `<a>`: brand ink, underlined,
       the offset and the thickness set once. What is here is the four things an
       anchor leaves to the page, and every one of them is something a page gets
       wrong by hand.

       ── It takes the size around it ───────────────────────────────────────────
       Deliberately. A link inside a paragraph is the size of the paragraph, and a
       link in a header is the size of the header. Everything here is in `em` and
       the colour is inherited where it is not the brand, so the link is whatever
       it is standing in.
       ========================================================================== */

    .ds-link {
      /* And the one you are ON, which is the other thing this file was
         saying with the same number. */
      --ds-link-weight-current: var(--ds-current-weight);
      /* The properties this component sets off the space ramp on purpose, named so
         a tool reads the decision instead of reporting it as a mistake. These are
         in `em`: they track the label rather than the box, which is the whole
         reason a link of one word and a link of five come out proportioned the same. */
      --ds-link-intent: gap;
      /* How far the mark stands from the word. In `em` for the reason above, and
         a name because the right distance depends on what the mark IS: an outline
         glyph at 0.35em sits right, and a solid one — a brand mark, an envelope
         filled in — reads as glued to the first letter at the same number,
         because it has no white inside it to give the eye a gap. */
      --ds-link-gap: 0.35em;
      /* An icon and its word do not break apart across a line. */
      display: inline-flex;
      align-items: baseline;
      gap: var(--ds-link-gap);
      border-radius: var(--ds-radius-1);
    }

    /* ── Underline ────────────────────────────────────────────────────────────
       In running text a link is underlined, and that is not a style: colour alone
       is the one distinction WCAG 1.4.1 refuses, because a reader who cannot pick
       the hue out of a paragraph has nothing else to go on.

       Navigation is the exception the rule allows: a row of links at the top of a
       page is identified by where it is and what it is next to, not by its
       colour, so it does not need a line under every word. */
    /* The line is always there and starts transparent, rather than being switched
       on and off. `text-decoration-line` cannot be animated: turned on at the
       moment the pointer arrives it pops, which was the one thing on this page
       still flicking after everything else had been slowed down. A colour can be
       animated, so the line fades in with the rest. */
    .ds-link--hover {
      text-decoration-color: transparent;
    }

    .ds-link--hover:hover {
      text-decoration-color: currentColor;
    }

    .ds-link--none {
      text-decoration: none;
    }

    /* ── Tone ─────────────────────────────────────────────────────────────────
       A link is the colour of the words around it, which is what the base sheet
       now says and what makes a page of prose read as prose. `brand` is for the
       one link that is the point of the paragraph: a call to action inside a
       sentence, the single thing to press on a marketing block. One per screen,
       like a filled button. */
    .ds-link--brand {
      color: var(--ds-brand-ink);
    }

    .ds-link--brand:hover {
      color: var(--ds-brand);
    }

    /* The page you are on. Not a colour: a colour is what everything else here
       already uses, so it would say "this one is a link" a second time. */
    .ds-link[aria-current] {
      font-weight: var(--ds-link-weight-current);
      --ds-link-ink: var(--ds-text-primary-neutral);
      color: var(--ds-link-ink);
    }

    .ds-link__icon {
      display: inline-flex;
      flex: none;
      inline-size: 1lh;
      block-size: 1lh;
      /* Aligned to the text rather than to the line box, or a mark beside a word
         sits a pixel high in a paragraph and a pixel low in a heading. */
      align-self: center;
    }

    .ds-link__icon > svg {
      inline-size: 100%;
      block-size: 100%;
    }

    /* The mark on a link that leaves the site. Smaller than a real icon: it is a
       footnote about where the link goes, not part of what it says. */
    .ds-link__away {
      inline-size: 0.75em;
      block-size: 0.75em;
      opacity: 0.7;
    }

    /* Said, not shown. A screen reader gets "opens in a new tab" and the page
       gets the mark: the same fact, twice, in the two ways it can be told. */
    .ds-link__note {
      position: absolute;
      inline-size: 1px;
      block-size: 1px;
      margin: -1px;
      padding: 0;
      overflow: hidden;
      clip-path: inset(50%);
      white-space: nowrap;
    }

    /* ── Standing on its own ──────────────────────────────────────────────────
       A link inside a sentence is a few words of a line and its target is that
       line: nothing to fix, and padding there would break the paragraph it lives
       in. A link standing on its own is a control in every way that matters, and
       asking somebody to hit fourteen pixels of text exactly is asking them to
       aim at a word.

       So the target is pushed out to the 24px WCAG 2.5.8 asks for, by a
       pseudo-element rather than by padding: what you can press grows and nothing
       on the page moves by a pixel. The same trick the badge's cross and the
       search box's clear use, and the same reason.

       `max(24px, 100%)` rather than a fixed inset, so a link set in 20px type,
       or in a page at 130% text, keeps its own height and gains nothing it does
       not need. */
    .ds-link--standalone {
      position: relative;
    }

    .ds-link--standalone::after {
      content: "";
      position: absolute;
      inset-block: calc((100% - max(24px, 100%)) / 2);
      /* A little at the sides too. A nav item that answers half a character
         before the word starts feels like a bigger thing to press, and it is. */
      inset-inline: -0.35em;
    }

    /* ==========================================================================
       Credential card

       One way in: what it opens, who it opens as, the password, and the code
       that changes every thirty seconds. Every line is its own line with its own
       copy, which is the anatomy every password manager converges on: a reader
       wants ONE of these at a time.
       ========================================================================== */

    /* Le righe stanno nel corpo della card, quindi prendono il suo padding. Il
       respiro fra una e l'altra e' loro. */
    .ds-credential-card__line {
      position: relative;
      padding-block: var(--ds-space-2);
    }

    /* Il nome sopra e il valore sotto, come nella colonna stretta di una lista di
       fatti: di fianco, il valore piu' lungo decideva la larghezza di tutti e una
       password rivelata spostava la riga sotto di se'. */
    .ds-credential-card__line + .ds-credential-card__line {
      border-block-start: 1px solid var(--ds-border-subtle);
    }

    /* Il tasto copia prende tutta la riga, e resta un tasto solo: quello che si
       vede e' il segno in fondo, quello che si preme e' la riga intera. */
    .ds-credential-card__copy::after {
      content: "";
      position: absolute;
      inset: 0;
    }

    /* E quello che ha un bersaglio suo si alza sopra: l'anello, un valore che e'
       un link, e l'occhio quando accanto c'e' gia' il tasto copia. */
    .ds-credential-card__ring,
    .ds-credential-card__said a,
    .ds-credential-card__line:has(.ds-credential-card__copy) .ds-credential-card__eye {
      position: relative;
      z-index: 1;
    }

    /* Nascosta, la password non ha niente da copiare: allora e' l'occhio a
       prendersi la riga, cosi' la si apre premendo dove capita invece di centrare
       un segno da 16px. */
    .ds-credential-card__line:not(:has(.ds-credential-card__copy)) .ds-credential-card__eye::after {
      content: "";
      position: absolute;
      inset: 0;
    }

    .ds-credential-card__label {
      --ds-credential-card-label-face: var(--ds-font-small-face);
      --ds-credential-card-label-size: var(--ds-font-small-size);
      --ds-credential-card-label-weight: var(--ds-font-small-weight);
      --ds-credential-card-label-leading: var(--ds-font-small-leading);
      --ds-credential-card-label-tracking: var(--ds-font-small-tracking);
      --ds-credential-card-label-ink: var(--ds-text-secondary-neutral);
      display: block;
      font-family: var(--ds-credential-card-label-face);
      font-size: var(--ds-credential-card-label-size);
      font-weight: var(--ds-credential-card-label-weight);
      line-height: var(--ds-credential-card-label-leading);
      letter-spacing: var(--ds-credential-card-label-tracking);
      color: var(--ds-credential-card-label-ink);
    }

    /* Il valore non spinge fuori la card: un indirizzo lungo o una password
       lunga si tagliano, e quello che si preme per averla per intero e' il tasto
       copia accanto. */
    .ds-credential-card__said {
      min-inline-size: 0;
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
    }

    /* La password e il codice in mono: si leggono un carattere alla volta, ed e'
       la faccia che tiene gli zeri distinti dalle O. */
    .ds-credential-card__secret,
    .ds-credential-card__code {
      --ds-credential-card-code-face: var(--ds-font-mono-face);
      font-family: var(--ds-credential-card-code-face);
    }

    /* L'anello che finisce: un codice letto nel secondo in cui cambia e' un
       codice digitato sbagliato. Un cerchio conico, perche' la quota che resta e'
       un angolo e non una barra. */
    .ds-credential-card__ring {
      --ds-credential-card-ring-ink: var(--ds-text-tertiary-neutral);
      --ds-credential-card-ring-track: var(--ds-border-subtle);
      display: inline-grid;
      place-items: center;
      inline-size: var(--ds-size-1);
      block-size: var(--ds-size-1);
      border-radius: var(--ds-radius-full);
      background: conic-gradient(
        var(--ds-credential-card-ring-ink) calc(var(--ds-credential-card-left, 1) * 360deg),
        var(--ds-credential-card-ring-track) 0
      );
    }

    .ds-credential-card__seconds {
      --ds-credential-card-seconds-face: var(--ds-font-micro-face);
      --ds-credential-card-seconds-size: var(--ds-font-micro-size);
      --ds-credential-card-seconds-weight: var(--ds-font-micro-weight);
      --ds-credential-card-seconds-leading: var(--ds-font-micro-leading);
      --ds-credential-card-seconds-tracking: var(--ds-font-micro-tracking);
      --ds-credential-card-seconds-ink: var(--ds-text-primary-neutral);
      display: grid;
      place-items: center;
      inline-size: calc(100% - 4px);
      block-size: calc(100% - 4px);
      border-radius: var(--ds-radius-full);
      background-color: var(--ds-surface-1);
      font-family: var(--ds-credential-card-seconds-face);
      font-size: var(--ds-credential-card-seconds-size);
      font-weight: var(--ds-credential-card-seconds-weight);
      line-height: var(--ds-credential-card-seconds-leading);
      letter-spacing: var(--ds-credential-card-seconds-tracking);
      color: var(--ds-credential-card-seconds-ink);
    }

    /* ==========================================================================
       File card

       One file as a card. The card and its media do the standing; what lives
       here is the one thing they do not know: what to draw when there is nothing
       to show.
       ========================================================================== */

    /* Il segno del tipo, grande, sul fondo del riquadro. Una card con un buco
       dove le vicine hanno una figura si legge come un file che non si e'
       caricato, non come un file che non ha niente da mostrare. */
    .ds-file-card__stand-in {
      display: flex;
      align-items: center;
      justify-content: center;
      inline-size: 100%;
      block-size: 100%;
      /* Il nome del componente, puntato al semantico: si sposta questo e si
         sposta il segno, si sposta il sistema e si spostano tutti quelli che
         nessuno ha spostato apposta. */
      --ds-file-card-stand-in-ink: var(--ds-text-tertiary-neutral);
      color: var(--ds-file-card-stand-in-ink);
    }

    .ds-file-card__stand-in > svg {
      inline-size: var(--ds-size-3);
      block-size: var(--ds-size-3);
      /* La linea resta quella del sistema anche qui: il segno e' disegnato su una
         griglia da 24 con tratto 2, e a 40px il tratto diventerebbe 3,33. */
      vector-effect: non-scaling-stroke;
    }

    /* ==========================================================================
       Files

       A collection of files, browsed. The grid is the layout's own; what lives
       here is the row, which is the same card with its picture taken away.
       ========================================================================== */

    .ds-files__rows {
      list-style: none;
      margin: 0;
      padding: 0;
    }

    /* La riga ha il respiro che ha una riga, non quello di una card: la card
       nuda dentro non porta padding, e senza questo il nome comincia sul bordo
       del foglio. */
    .ds-files__row {
      padding: var(--ds-space-3) var(--ds-space-4);
    }

    /* La riga fra un file e l'altro, non attorno: un bordo su ogni riga dentro
       una scheda che ha gia' il suo disegna due linee sullo stesso pixel. */
    .ds-files__row + .ds-files__row {
      border-block-start: 1px solid var(--ds-border-subtle);
    }

    /* In riga la figura sparisce e resta il segno, che e' il taglio che ogni
       drive fa quando si passa alla lista: quello che si legge sono i nomi. */
    .ds-files__row .ds-card__media {
      display: none;
    }

    /* ==========================================================================
       Link card

       One address as a card. The card, its header and `Link` do the standing;
       what lives here is the one decision they do not make.
       ========================================================================== */

    /* L'indirizzo sta su una riga sola e si taglia. In una griglia di indirizzi
       uno lungo andrebbe a tre righe e trascinerebbe fuori squadra tutta la sua
       fila, e la parte che si perde di un indirizzo lungo e' la coda, che e'
       anche quella che conta meno: quello che si riconosce e' il dominio. */
    .ds-link-card .ds-card__title {
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
    }

    /* ==========================================================================
       Kbd

       The CSS behind <Kbd>. Private, like every other component's.

       A key, rendered as a key: what a menu line prints at its end to say how it
       is reached without the pointer, and what a shortcut sheet is a table of.

       ── A label, not a control ───────────────────────────────────────────────
       It has no hover, no press and no focus ring, because pressing the picture
       of a key does nothing: the key does. Everything here is the drawing of a
       keycap and nothing is the behaviour of one.
       ========================================================================== */

    .ds-kbd {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-kbd-face: var(--ds-font-mono-face);
      --ds-kbd-size: var(--ds-font-mono-size);
      --ds-kbd-tracking: var(--ds-font-mono-tracking);
      --ds-kbd-leading: var(--ds-font-mono-leading);
      --ds-kbd-weight: var(--ds-font-mono-weight);
      /* As tall as the line it stands in, the same reading the count badge and
         the icon slot take: a key belongs to the words beside it, and what those
         occupy is their line box. Square until the word on it is wider, so a
         single letter is a key and `Esc` is a key that got longer. */
      --ds-kbd-floor: var(--ds-kbd-leading);

      display: inline-flex;
      align-items: center;
      justify-content: center;
      box-sizing: border-box;
      min-inline-size: var(--ds-kbd-floor);
      min-block-size: var(--ds-kbd-floor);
      block-size: var(--ds-kbd-floor);
      /* The smallest rung at the sides, for the reason the count badge gives:
         the floor only makes a square while the glyph and its two paddings fit
         inside it. A 12px glyph is 9 wide, so at the rung above it came to 17 in
         a floor of 16 and a single key was a pixel off square (measured on the
         docs, 8 September 2026). Two a side leaves 13 in 16, and a key with a
         WORD on it grows past the floor with room at its sides. */
      padding-block: 0;
      padding-inline: var(--ds-space-0-5);
      border: 1px solid var(--ds-border);
      border-radius: var(--ds-radius-1);
      background-color: var(--ds-surface-2);
      font-family: var(--ds-kbd-face);
      font-size: var(--ds-kbd-size);
      font-weight: var(--ds-kbd-weight);
      letter-spacing: var(--ds-kbd-tracking);
      /* The absence of a leading, like the count and the avatar's initials: a
         glyph in a cap is a mark that happens to be a letter, and a line from the
         type scale puts space inside it that nothing reads. The floor above is
         what holds the height. */
      line-height: 1;
      --ds-kbd-ink: var(--ds-text-secondary-neutral);
      color: var(--ds-kbd-ink);
      /* A key is printed as it is printed: nothing here shouts it. */
      text-transform: none;
      white-space: nowrap;
    }

    /* ==========================================================================
       Maker mark

       The CSS behind <MakerMark>. Private, like every other component's.

       WebCommerciale's logo, as the maker draws it: the dark version on a light
       ground, the light version on a dark one, four inks read from the colour
       file where the theme moves them. Never one ink at four opacities, never
       recoloured, never changed by a pointer. A rung tall by default; where it
       stands decides the rung.
       ========================================================================== */

    .ds-maker-mark {
      --ds-maker-mark-size: var(--ds-size-2);
      display: block;
      block-size: var(--ds-maker-mark-size);
      inline-size: auto;
      fill: var(--ds-maker-word-1);
    }

    .ds-maker-mark__word-2 { fill: var(--ds-maker-word-2); }
    .ds-maker-mark__dot-2 { fill: var(--ds-maker-dot-2); }
    .ds-maker-mark__dot-3 { fill: var(--ds-maker-dot-3); }

    /* The symbol: the dots without the words, for a bar or a favicon-sized
       place. Two inks, as the maker's file has it: the main dots, and one
       quieter grey that turns white on a dark ground. */
    .ds-maker-mark--symbol .ds-maker-mark__dot-2,
    .ds-maker-mark--symbol .ds-maker-mark__dot-3 {
      fill: var(--ds-maker-symbol-dot);
    }

    /* ==========================================================================
       Site credits

       The CSS behind <SiteCredits>. Private, like every other component's.

       The last line of a site: who built it. A word and a mark, and the whole of
       it one link to the maker's site. It stands under the footer's small print
       and speaks at the same size, because it is small print: the site is the
       client's, and the maker signs it in the corner.
       ========================================================================== */

    .ds-site-credits {
      --ds-site-credits-face: var(--ds-font-small-face);
      --ds-site-credits-size: var(--ds-font-small-size);
      --ds-site-credits-tracking: var(--ds-font-small-tracking);
      --ds-site-credits-weight: var(--ds-font-small-weight);
      --ds-site-credits-leading: var(--ds-font-small-leading);
      --ds-site-credits-ink: var(--ds-text-tertiary-neutral);

      display: inline-flex;
      flex-direction: column;
      align-items: flex-start;
      gap: var(--ds-space-2);
      font-family: var(--ds-site-credits-face);
      font-size: var(--ds-site-credits-size);
      letter-spacing: var(--ds-site-credits-tracking);
      font-weight: var(--ds-site-credits-weight);
      line-height: var(--ds-site-credits-leading);
      color: var(--ds-site-credits-ink);
      text-decoration: none;
    }

    /* Nothing changes on hover, by the owner's word: not the mark, which is a
       logo, and not the word beside it. The pointer is the only sign, and the
       focus ring is for the keyboard. */
    .ds-site-credits:focus-visible {
      outline: 2px solid var(--ds-focus);
      outline-offset: 2px;
      border-radius: var(--ds-radius-1);
    }

    /* The mark keeps its own rung, 32px, which is the signature's size too. */

    /* ==========================================================================
       Mark

       The CSS behind <Mark>. Private, like every other component's.

       The part of a string that answered the search: a result reads as an answer
       when the reader can see WHY it is one, and the why is the letters they
       typed.

       ── Weight, not a highlighter ────────────────────────────────────────────
       The browser paints `mark` yellow, which is a pen on paper and wrong here:
       a page of results would be a page of yellow, and the colour would be the
       loudest thing on a screen where nothing about the match is a warning. The
       letters are simply heavier than the ones around them, which is what a
       reader scanning a list actually reads.

       The BOLD rung and not the emphasis one, which was the first cut and was
       invisible: a list title already wears emphasis, so a match marked with it
       measured 500 inside 500 and pointed at nothing (measured on the docs,
       8 September 2026). One rung above whatever a title wears is what makes a
       mark a mark — and where the words around it are already bold, weight has
       nothing left to say and the match will not stand out.
       ========================================================================== */

    .ds-mark__hit {
      /* This component's own. The role says what the words are; this says that
         THIS component wants a heavier hand than the role has, which is a
         decision the component makes and nobody else. Pointed at the semantic
         rather than holding a number, so moving the system moves every component
         that has not been moved on purpose, and moving one moves one. */
      --ds-mark-hit-weight: var(--ds-bold-weight);
      font-weight: var(--ds-mark-hit-weight);
      /* The ground the user agent paints goes: what is left is the weight and the
         ink, and both of those the reader reads as emphasis rather than as a
         colour that means something. */
      background: none;
      --ds-mark-hit-ink: var(--ds-text-primary-neutral);
      color: var(--ds-mark-hit-ink);
    }

    /* ==========================================================================
       Emblem

       The CSS behind <Emblem>. Private, like every other component's.

       The mark of a thing, in the colour that says which one it is: a database's
       mark in the row of a sidebar, at the head of its card, on a line of the
       palette. Asked for on 10 September 2026 by the first product that gave its
       things a mark and a colour and had nowhere to put the two together.

       ── The line's height, and the mark fills it ─────────────────────────────
       The same reading the sidebar's slot, the card's slot and the popover's
       check slot take: a mark beside words is the height of their line, and it
       follows that line wherever it stands, so nothing here says a number.

       ── The hue is the badge's ────────────────────────────────────────────────
       The twelve hues with their inks, read through this component's own token
       so the badge on a row and the emblem on the row's thing agree by
       construction, in both themes. Without a hue the mark takes the ink of the
       words beside it, which is what a mark that is not a thing's own does.
       ========================================================================== */

    .ds-emblem {
      /* The words' own ink until a hue says otherwise. */
      --ds-emblem-ink: currentcolor;
      display: inline-flex;
      flex: none;
      inline-size: 1lh;
      block-size: 1lh;
      color: var(--ds-emblem-ink);
    }

    .ds-emblem > svg {
      inline-size: 100%;
      block-size: 100%;
    }

    .ds-emblem--hue-red { --ds-emblem-ink: var(--ds-hue-red-ink); }
    .ds-emblem--hue-orange { --ds-emblem-ink: var(--ds-hue-orange-ink); }
    .ds-emblem--hue-amber { --ds-emblem-ink: var(--ds-hue-amber-ink); }
    .ds-emblem--hue-yellow { --ds-emblem-ink: var(--ds-hue-yellow-ink); }
    .ds-emblem--hue-lime { --ds-emblem-ink: var(--ds-hue-lime-ink); }
    .ds-emblem--hue-green { --ds-emblem-ink: var(--ds-hue-green-ink); }
    .ds-emblem--hue-teal { --ds-emblem-ink: var(--ds-hue-teal-ink); }
    .ds-emblem--hue-cyan { --ds-emblem-ink: var(--ds-hue-cyan-ink); }
    .ds-emblem--hue-blue { --ds-emblem-ink: var(--ds-hue-blue-ink); }
    .ds-emblem--hue-indigo { --ds-emblem-ink: var(--ds-hue-indigo-ink); }
    .ds-emblem--hue-violet { --ds-emblem-ink: var(--ds-hue-violet-ink); }
    .ds-emblem--hue-pink { --ds-emblem-ink: var(--ds-hue-pink-ink); }

    /* ==========================================================================
       Bottom sheet

       The CSS behind <BottomSheet>. Private, like every other component's.

       A panel that comes up from the bottom of the screen: the phone's answer to
       the dialog. Same top layer, same backdrop, same Escape — it arrives from
       the edge nearest the hand instead of the middle of the screen, it is as
       wide as the phone, and it can be pushed back down with a finger.

       ── A shape, not a breakpoint ────────────────────────────────────────────
       There is no media query here. A sheet is right where a sheet is right, and
       the page is what knows whether it is on a phone: the same component on a
       wide screen still comes from the bottom, still as wide as the window, and
       that is a decision the caller took rather than one this file second-guesses.
       ========================================================================== */

    dialog.ds-bottom-sheet:not([open]) {
      display: none;
    }

    .ds-bottom-sheet {
      /* The tallest it may stand: past this, what it holds scrolls inside it. A
         sheet the height of the window is a page, and a reader who cannot see
         what is behind it has lost the thing they were choosing about. */
      --ds-bottom-sheet-ceiling: 85dvh;

      position: fixed;
      /* Against the bottom, and the user agent's own auto margins take it there:
         `inset` is what a modal dialog needs before a margin can mean anything. */
      inset: 0;
      margin: auto auto 0;
      inline-size: 100%;
      max-inline-size: 100%;
      max-block-size: var(--ds-bottom-sheet-ceiling);

      display: flex;
      flex-direction: column;
      padding: 0;
      border: 0;
      /* Round at the top only: the two corners at the bottom are off the screen,
         and drawing them is drawing a card that has slid up rather than a sheet
         that has come out of the edge. */
      border-start-start-radius: var(--ds-radius-4);
      border-start-end-radius: var(--ds-radius-4);
      border-block-start: 1px solid var(--ds-border);
      background-color: var(--ds-surface-1);
      --ds-bottom-sheet-ink: var(--ds-text-primary-neutral);
      color: var(--ds-bottom-sheet-ink);
      overflow: hidden;
      /* Where the finger has taken it: the component writes the value itself
         while a drag is running and clears it when the finger goes. */
      translate: 0 0;
    }

    /* ── There is no spring back, and that is measured rather than lazy ──────
       It follows the finger with no easing at all, which is right: a lag between
       the thumb and the sheet is the one thing that makes a gesture feel broken.
       Releasing under the threshold puts it back at once.

       A transition on the way back was tried three ways on 8 September 2026 and
       none of them finished: on a `translate` reading a custom property, on the
       same property registered with `@property` so it could interpolate, and on
       a plain `translate` written as a value. Every time the browser reported the
       animation as running, the inline value was already cleared, and the sheet
       stayed 40px down — on a dialog in the top layer, in the engine it was
       measured in. A sheet that returns instantly is worse than one that glides;
       a sheet that stops halfway and stays there is broken, and that is the trade
       this takes. If a later engine transitions a top-layer dialog reliably, the
       spring is four lines and this note says where they go. */

    .ds-bottom-sheet::backdrop {
      background-color: var(--ds-scrim);
    }

    /* The grip: what says it can be pushed down, and the only part the drag
       starts from, because a sheet that follows the finger anywhere is a sheet
       nobody can scroll. The touch target is the whole band, not the bar drawn
       in it. */
    .ds-bottom-sheet__grip {
      display: flex;
      align-items: center;
      justify-content: center;
      flex: none;
      /* The floor under a target, which is what this band is even though the mark
         in it is four pixels tall. */
      min-block-size: var(--ds-size-1);
      /* The finger owns this band: without it the browser takes the drag for a
         scroll and the sheet never moves. */
      touch-action: none;
      cursor: grab;
    }

    /* The mark itself: a filled shape and not a line, so it takes a ground and
       not the border's ink — a token does the job it was calibrated for, and
       `--ds-border` is picked to hold against a surface as a LINE. */
    .ds-bottom-sheet__grip-bar {
      display: block;
      inline-size: var(--ds-space-8);
      block-size: var(--ds-space-1);
      border-radius: var(--ds-radius-full);
      background-color: var(--ds-surface-3);
    }

    .ds-bottom-sheet__head {
      display: flex;
      flex-direction: column;
      gap: var(--ds-space-1);
      flex: none;
      padding: var(--ds-space-2) var(--ds-space-4) var(--ds-space-4);
    }

    .ds-bottom-sheet__title {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-bottom-sheet-title-face: var(--ds-font-heading-5-face);
      --ds-bottom-sheet-title-size: var(--ds-font-heading-5-size);
      --ds-bottom-sheet-title-tracking: var(--ds-font-heading-5-tracking);
      --ds-bottom-sheet-title-leading: var(--ds-font-heading-5-leading);
      --ds-bottom-sheet-title-weight: var(--ds-font-heading-5-weight);
      margin: 0;
      font-family: var(--ds-bottom-sheet-title-face);
      font-size: var(--ds-bottom-sheet-title-size);
      letter-spacing: var(--ds-bottom-sheet-title-tracking);
      line-height: var(--ds-bottom-sheet-title-leading);
      font-weight: var(--ds-bottom-sheet-title-weight);
    }

    .ds-bottom-sheet__description {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-bottom-sheet-description-face: var(--ds-font-body-face);
      --ds-bottom-sheet-description-size: var(--ds-font-body-size);
      --ds-bottom-sheet-description-tracking: var(--ds-font-body-tracking);
      --ds-bottom-sheet-description-leading: var(--ds-font-body-leading);
      margin: 0;
      font-family: var(--ds-bottom-sheet-description-face);
      font-size: var(--ds-bottom-sheet-description-size);
      letter-spacing: var(--ds-bottom-sheet-description-tracking);
      line-height: var(--ds-bottom-sheet-description-leading);
      --ds-bottom-sheet-description-ink: var(--ds-text-secondary-neutral);
      color: var(--ds-bottom-sheet-description-ink);
    }

    /* The one part that scrolls, so the grip stays reachable however long the
       list is: a sheet whose handle scrolls away cannot be pushed back down. */
    .ds-bottom-sheet__body {
      flex: 1;
      min-block-size: 0;
      overflow-y: auto;
      padding-inline: var(--ds-space-4);
      padding-block-end: var(--ds-space-4);
    }

    .ds-bottom-sheet__foot {
      display: flex;
      align-items: center;
      gap: var(--ds-space-3);
      flex: none;
      padding: var(--ds-space-4);
      border-block-start: 1px solid var(--ds-border-subtle);
      /* Clear of the bar every phone draws over the bottom of its own screen. */
      padding-block-end: calc(var(--ds-space-4) + env(safe-area-inset-bottom, 0px));
    }

    .ds-bottom-sheet__foot > * {
      flex: 1;
      min-inline-size: 0;
    }

    /* ==========================================================================
       Palette

       The CSS behind <Palette>. Private, like every other component's.

       Keyboard entry to everything an application can do: one box near the top of
       the screen, the acts under it, the arrows and Enter between them.

       ── Why it is not the dialog ─────────────────────────────────────────────
       A dialog is centred by the user agent, capped at a form's width, and opens
       with a heading band and a cross, because it is something to ANSWER. This is
       anchored near the top, where a reader's eyes already are and where a list
       that grows has room to grow downwards; it is as wide as a sentence; and its
       own search box is its title, so a heading over it would name the box twice.
       Same top layer, same backdrop, same Escape — a different thing standing in
       them.
       ========================================================================== */

    /* A shut dialog is hidden by the user agent with `dialog:not([open])`, which
       is an element selector and loses to the class below: the rule that gives it
       `display: flex` also gives it away, and the panel stood over every page
       whether it had been opened or not. The same line the dialog and the bottom
       sheet carry, and the one this file was shipped without (owner, 8 September
       2026: "non la posso vedere perche' c'e' questo menu in mezzo"). */
    dialog.ds-palette:not([open]) {
      display: none;
    }

    .ds-palette {
      /* The room it keeps from the top of the window. A palette centred sits
         under the reader's hands on a laptop and over their eyes on a desk
         monitor; anchored, it lands in the same place on both, and the list grows
         downwards into room that is already empty. */
      --ds-palette-top: var(--ds-space-16);
      /* As wide as a sentence: the labels are phrases, not paragraphs, and a
         panel wider than the words in it is a panel with a hole down one side. */
      --ds-palette-width: 36rem;
      /* Where it starts, when it comes out of a box. Declared here with a
         resting value even though the component always writes it: a property
         read with a var() and never declared is a property nothing can be
         measured against. */
      --ds-palette-left: auto;

      /* Its width is its OUTER width, which is what "as wide as the box" means:
         with the border outside the number, the panel came out two pixels wider
         than the box it stands on and hung past its end (measured 8 September
         2026). There is no reset in this package; a component that is sized says
         how it is sized. */
      box-sizing: border-box;

      position: fixed;
      inset: 0;
      /* The user agent centres a modal dialog with its own auto margins. Only the
         inline pair is kept, because the block placement is this component's. */
      margin: var(--ds-palette-top) auto auto;
      inline-size: min(var(--ds-palette-width), calc(100vw - var(--ds-space-8)));
      max-inline-size: calc(100vw - var(--ds-space-8));
      /* Whatever is left under it, so a long list scrolls inside the panel and
         never past the bottom of the window. */
      max-block-size: calc(100dvh - var(--ds-palette-top) - var(--ds-space-8));

      display: flex;
      flex-direction: column;
      padding: 0;
      /* Its own, because what stands inside an anchored panel has to take it off
         again: the panel's edge is the edge the box used to draw, so the band at
         the top is shorter by it, top and bottom. */
      --ds-palette-edge: 1px;
      border: var(--ds-palette-edge) solid var(--ds-border);
      border-radius: var(--ds-radius-4);
      background-color: var(--ds-surface-1);
      --ds-palette-ink: var(--ds-text-primary-neutral);
      color: var(--ds-palette-ink);
      overflow: hidden;
    }

    .ds-palette::backdrop {
      background-color: var(--ds-scrim);
    }

    /* The box is the title: it says what the panel is for by asking for it. */
    .ds-palette__box {
      flex: none;
      padding: var(--ds-space-3);
      border-block-end: 1px solid var(--ds-border-subtle);
    }

    /* The lines are what scrolls; the box holds still, because a reader typing
       should not have to find the box again after the list moved. */
    .ds-palette__lines {
      flex: 1;
      min-block-size: 0;
      overflow-y: auto;
      margin: 0;
      padding: var(--ds-space-2);
      list-style: none;
    }

    .ds-palette__line {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-palette-line-face: var(--ds-font-body-face);
      --ds-palette-line-size: var(--ds-font-body-size);
      --ds-palette-line-tracking: var(--ds-font-body-tracking);
      --ds-palette-line-leading: var(--ds-font-body-leading);

      display: flex;
      align-items: center;
      gap: var(--ds-space-3);
      inline-size: 100%;
      /* The medium rung, because a line here is a target before it is a label and
         the whole panel is one column of them. */
      min-block-size: var(--ds-size-3);
      padding-block: 0;
      padding-inline: var(--ds-space-3);
      border: 0;
      border-radius: var(--ds-radius-2);
      background: none;
      font-family: var(--ds-palette-line-face);
      font-size: var(--ds-palette-line-size);
      letter-spacing: var(--ds-palette-line-tracking);
      line-height: var(--ds-palette-line-leading);
      --ds-palette-line-ink: var(--ds-text-primary-neutral);
      color: var(--ds-palette-line-ink);
      text-align: start;
      cursor: pointer;
      transition: background-color var(--ds-duration) var(--ds-ease);
    }

    /* One cursor, and the pointer moves it rather than drawing a second one: a
       hover wash under the pointer and a selected wash under the arrows are two
       answers to "which line will Enter take", and a reader has to pick one. */
    .ds-palette__line--here {
      background-color: var(--ds-surface-hover);
    }

    /* The ring is the cursor's, not the browser's: focus never leaves the box, so
       there is nothing here for a focus ring to sit on. */
    .ds-palette__line:focus-visible {
      outline: 2px solid var(--ds-focus);
      outline-offset: -2px;
    }

    .ds-palette__line:disabled {
      --ds-palette-line-ink: var(--ds-text-disabled);
      color: var(--ds-palette-line-ink);
      cursor: not-allowed;
    }

    /* As tall as the line beside it, and not as tall as the font: `1em` is the
       type size, so a mark next to a 20px line came out 16 and read as the
       smaller of two things that are meant to be one. */
    .ds-palette__icon {
      display: inline-flex;
      flex: none;
      inline-size: 1lh;
      block-size: 1lh;
    }

    .ds-palette__icon > svg {
      inline-size: 100%;
      block-size: 100%;
    }

    .ds-palette__label {
      flex: 1;
      min-inline-size: 0;
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
    }

    /* What to press instead, at the far end and never the reason the panel is as
       wide as it is. Keys draw themselves; words are the quiet run. */
    .ds-palette__keys {
      --ds-palette-keys-face: var(--ds-font-mono-face);
      --ds-palette-keys-size: var(--ds-font-mono-size);
      --ds-palette-keys-tracking: var(--ds-font-mono-tracking);
      display: inline-flex;
      align-items: center;
      gap: var(--ds-space-1);
      flex: none;
      font-family: var(--ds-palette-keys-face);
      font-size: var(--ds-palette-keys-size);
      letter-spacing: var(--ds-palette-keys-tracking);
      --ds-palette-keys-ink: var(--ds-text-tertiary-neutral);
      color: var(--ds-palette-keys-ink);
    }

    /* A word over the run of lines under it. */
    .ds-palette__heading {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-palette-heading-face: var(--ds-font-overline-face);
      --ds-palette-heading-size: var(--ds-font-overline-size);
      --ds-palette-heading-tracking: var(--ds-font-overline-tracking);
      --ds-palette-heading-leading: var(--ds-font-overline-leading);
      --ds-palette-heading-weight: var(--ds-font-overline-weight);
      padding-block: var(--ds-space-3) var(--ds-space-1);
      padding-inline: var(--ds-space-3);
      font-family: var(--ds-palette-heading-face);
      font-size: var(--ds-palette-heading-size);
      letter-spacing: var(--ds-palette-heading-tracking);
      line-height: var(--ds-palette-heading-leading);
      font-weight: var(--ds-palette-heading-weight);
      text-transform: uppercase;
      --ds-palette-heading-ink: var(--ds-text-secondary-neutral);
      color: var(--ds-palette-heading-ink);
    }

    .ds-palette__rule {
      display: block;
      block-size: 1px;
      margin-block: var(--ds-space-2);
      background-color: var(--ds-border-subtle);
    }

    .ds-palette__empty {
      padding: var(--ds-space-4);
    }

    /* ── Out of the box that opened it ────────────────────────────────────────
       A palette that hangs in the middle of the screen arrives from nowhere. One
       that opens under the box a reader was already looking at is the same box
       answering, which is why the key is printed inside that box in the first
       place.

       The three numbers are the box's own, written by the component when it
       opens and again if the window moves under it: where the box ends, where it
       starts, and how wide it is. None of them is a number this file can know. */
    .ds-palette--anchored {
      /* Placed rather than centred. The user agent centres a modal dialog with
         its own auto margins, and an auto margin beside a fixed one is a fight
         the auto one wins: the panel came out 41px to the right of its box and
         narrower than it (measured on the docs, 8 September 2026). Both margins
         go, and the two insets say where it stands. */
      margin: 0;
      /* ON the box, not under it. The panel's own box lands exactly where the
         reader's was, so what they see is one box that grew a list rather than
         two search boxes stacked. */
      inset-block-start: var(--ds-palette-top);
      inset-block-end: auto;
      inset-inline-start: var(--ds-palette-left);
      inset-inline-end: auto;
      /* As wide as what it came out of, and never narrower than a sentence: a
         panel the width of a small box is a column of clipped words. */
      inline-size: max(var(--ds-palette-width), 24rem);
      max-block-size: calc(100dvh - var(--ds-palette-top) - var(--ds-space-8));
    }

    /* Standing on the box, the panel's own band IS that box: the room it keeps
       around its search would push the field down and break the overlap. */
    .ds-palette--anchored .ds-palette__box {
      padding: 0;
      border-block-end: 0;
    }

    /* And the corners are the ones the reader was already looking at. Two shapes
       standing on each other at two different radii is the larger one slicing the
       smaller at every corner: this panel's 16 against the field's 8 is where the
       sliced edge came from (owner, 8 September 2026: "errori di opacita'
       tagliata"). Anchored, the shape is the box's. */
    .ds-palette--anchored {
      border-radius: var(--ds-radius-2);
    }

    /* And the field loses its own edge in here, the same way the search inside a
       popover does: it is already inside a bordered panel, and a box within a box
       one pixel apart is the sort of thing that makes a panel look like a form
       that fell in (owner, same day: "bordi in piu").

       It keeps losing it once the caret is in it, and this box never stops
       carrying `aria-expanded`, so the frame wore its focus edge for as long as
       it existed and the second border was the loud one. Nothing is lost by
       taking it: the panel itself is the thing with the focus, and the caret is
       in the only box there is.
       
       Three lines shorter than the popover's, which is where it was copied from.
       That one clears the edge with a COLOUR and needs a second rule for the
       moment the caret arrives and the field paints its focus colour back; this
       one takes the WIDTH, which no state puts back, so there is nothing to say
       twice. The `box-shadow` and `outline` that came with the copy were
       defending against a ring the field stopped drawing on 5 September 2026. */
    .ds-palette--anchored .ds-field__frame {
      /* Gone, and not merely made transparent: an edge kept for its width is a
         pixel of room, and it put the typed words one across from where the
         reader's own were. Taken, the panel's border stands exactly where the
         box's did and what is inside carries on from where it was. */
      border-width: 0;
      border-radius: 0;
      background-color: transparent;
    }

    /* And it is as tall as the box's inside was, which is two edges less than the
       box: the panel draws one where the box drew one, so a band still asking for
       the whole tier stood a pixel lower than the words it replaced. The reader
       is looking at one box the whole time and nothing in it may move (owner,
       8 September 2026: "il search si sposta quando si apre la palette, l'icona
       dico"). Its inset is left alone for the same reason — it is the box's. */
    .ds-palette--anchored .ds-field__frame {
      min-block-size: calc(var(--ds-size-3) - 2 * var(--ds-palette-edge));
    }

    /* The list moves to the box instead. The lines are indented by the list's own
       room and then by their own, which came to eight pixels past the magnifier
       standing above them; the box cannot move, so the room the lines keep is
       what the box's inset leaves after the list's own. */
    .ds-palette--anchored .ds-palette__line,
    .ds-palette--anchored .ds-palette__heading {
      padding-inline: calc(var(--ds-space-3) - var(--ds-space-2));
    }

    /* And the words start where the typed words do. A line keeps more room
       between its mark and its label than a field keeps between its magnifier and
       its caret, so the labels stood four pixels to the right of what a reader was
       typing to narrow them. The field this panel renders is always the middle
       tier, and that is the step it keeps. */
    .ds-palette--anchored .ds-palette__line {
      gap: var(--ds-space-2);
    }

    /* The wash the frame paints under a pointer goes with it, and on its own rule
       because it is a picture and not a colour: a band as wide as the panel
       lighting up under a pointer on its way somewhere says a control is there to
       be pressed, and the only thing to do here is type.
       
       Named down to the element, which nothing else here needs to be. Written as
       the two classes alone this rule weighs exactly what the field's own hover
       weighs — `.ds-field__frame:hover:not(:has(:disabled))`, where `:not` and
       `:has` each take the weight of what is inside them and `:disabled` is one
       class — so which of the two won was decided by which file `index.css`
       imports second, and nothing said so. `dialog` is a name this file already
       uses to reach the same element, and both renderers print one. */
    dialog.ds-palette--anchored .ds-field__frame:hover {
      background-image: none;
    }

    /* One line between the box and what it answered with, and it is the list that
       draws it: the field's own edge is gone above, and a band with nothing under
       it is a band the reader cannot tell from the list. */
    .ds-palette--anchored .ds-palette__lines,
    .ds-palette--anchored .ds-palette__empty {
      border-block-start: 1px solid var(--ds-border-subtle);
    }

    /* ==========================================================================
       Collapsible

       The CSS behind <Collapsible>. Private, like every other component's.

       One disclosure: a row that opens onto what it holds. A section of a view
       that folds, a group of records with its count and its quick add, one
       question in a list of them.

       ── It used to be an accordion of one ─────────────────────────────────────
       Until 7 September 2026 this component rendered an <Accordion> holding a
       single item, so its markup carried the accordion's class names and its
       own `.ds-collapsible` styled nothing at all. Anyone inspecting a group
       header in a list was told it was an accordion, which it is not, and the
       package's own rule that every class carries the name of its component was
       broken by the one component that had no rules to break it with. The
       relationship is the right way round now: a disclosure owns its markup and
       an accordion is a list of them.

       ── The height animates without anybody measuring it ──────────────────────
       A panel opens from nothing to whatever it turns out to be, and `height:
       auto` has never been animatable. The way round it is a grid whose single
       row goes from `0fr` to `1fr`: the fraction IS animatable, the row resolves
       to the content's own height at the end of it, and nothing has to be
       measured in JavaScript and written back as a pixel value that goes stale
       the moment the text reflows.
       ========================================================================== */

    /* The row holds the head and, beside it, what stands at the row's end: a
       quick add, a menu. Outside the button, since a control inside a control
       is one nobody can reach; the head takes the rest of the row. */
    .ds-collapsible__row {
      display: flex;
      align-items: center;
      gap: var(--ds-space-2);
    }

    .ds-collapsible__row > * {
      flex: 1;
      min-inline-size: 0;
    }

    /* The count's column: a tier wide and the badge centred in it, because a
       count is a circle on one digit and a pill on two and the text beside it
       moved by the difference from row to row. Measured 10 September 2026 on a
       list of parents with a bar under each name: the bars ended at 993 on rows
       counting one digit and at 985 on rows counting two. The tier is the width
       of two tabular digits in their pill, 23.8, and nothing under it. */
    .ds-collapsible__count {
      display: flex;
      flex: none;
      justify-content: center;
      min-inline-size: var(--ds-size-1);
    }

    .ds-collapsible__end {
      display: flex;
      flex: none;
      align-items: center;
      gap: var(--ds-space-2);
    }

    /* The whole row is the button, which is the difference between reaching for a
       chevron and reaching for a heading. */
    .ds-collapsible__head {
      --ds-collapsible-head-weight: var(--ds-emphasis-weight);
      --ds-collapsible-head-face: var(--ds-font-body-face);
      display: flex;
      align-items: center;
      gap: var(--ds-space-3);
      inline-size: 100%;
      min-block-size: var(--ds-collapsible-tier);
      /* Half the padding, and the leading comes off the tier below rather than
         from a ratio here: the page's line-height is in rem, so it inherits as an
         absolute 24px, and 24 of line plus 16 of padding is 40 whatever the tier
         says. The same trap the smallest field fell into, in a different file. */
      padding-block: var(--ds-space-1);
      border: 0;
      background: none;
      font-family: var(--ds-collapsible-head-face);
      font-weight: var(--ds-collapsible-head-weight);
      --ds-collapsible-head-ink: var(--ds-text-primary-neutral);
      color: var(--ds-collapsible-head-ink);
      text-align: start;
      cursor: pointer;
      transition: color var(--ds-duration) var(--ds-ease);
    }

    .ds-collapsible--xsmall { --ds-collapsible-tier: var(--ds-size-1); }
    .ds-collapsible--small { --ds-collapsible-tier: var(--ds-size-2); }
    .ds-collapsible--medium { --ds-collapsible-tier: var(--ds-size-3); }
    .ds-collapsible--large { --ds-collapsible-tier: var(--ds-size-4); }
    .ds-collapsible--xlarge { --ds-collapsible-tier: var(--ds-size-5); }
    .ds-collapsible--xxlarge { --ds-collapsible-tier: var(--ds-size-6); }

    /* No padding at the smallest tier: 24px is 17 of content and 8 of padding,
       which is 25. The tier is the height, so the room comes out rather than the
       tier going up. */
    .ds-collapsible--xsmall .ds-collapsible__head {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-collapsible-head-face: var(--ds-font-small-face);
      --ds-collapsible-head-tracking: var(--ds-font-small-tracking);
      --ds-collapsible-head-size: var(--ds-font-small-size);
      --ds-collapsible-head-leading: var(--ds-font-small-leading);
      font-family: var(--ds-collapsible-head-face);
      font-size: var(--ds-collapsible-head-size);
      letter-spacing: var(--ds-collapsible-head-tracking);
      line-height: var(--ds-collapsible-head-leading);
      padding-block: 0;
    }
    .ds-collapsible--small .ds-collapsible__head {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-collapsible-head-face: var(--ds-font-label-face);
      --ds-collapsible-head-tracking: var(--ds-font-label-tracking);
      --ds-collapsible-head-size: var(--ds-font-label-size);
      --ds-collapsible-head-leading: var(--ds-font-label-leading);
      font-family: var(--ds-collapsible-head-face);
     font-size: var(--ds-collapsible-head-size);
      letter-spacing: var(--ds-collapsible-head-tracking);
      line-height: var(--ds-collapsible-head-leading); }
    .ds-collapsible--medium .ds-collapsible__head {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-collapsible-head-face: var(--ds-font-body-face);
      --ds-collapsible-head-tracking: var(--ds-font-body-tracking);
      --ds-collapsible-head-size: var(--ds-font-body-size);
      --ds-collapsible-head-leading: var(--ds-font-body-leading);
      font-family: var(--ds-collapsible-head-face);
     font-size: var(--ds-collapsible-head-size);
      letter-spacing: var(--ds-collapsible-head-tracking);
      line-height: var(--ds-collapsible-head-leading); }
    .ds-collapsible--large .ds-collapsible__head {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-collapsible-head-face: var(--ds-font-body-face);
      --ds-collapsible-head-tracking: var(--ds-font-body-tracking);
      --ds-collapsible-head-size: var(--ds-font-body-size);
      --ds-collapsible-head-leading: var(--ds-font-body-leading);
      font-family: var(--ds-collapsible-head-face);
     font-size: var(--ds-collapsible-head-size);
      letter-spacing: var(--ds-collapsible-head-tracking);
      line-height: var(--ds-collapsible-head-leading); }
    /* The top two read at the level above body until it was measured: that level
       is fluid on the web surface, 18px growing to 20 with the window, and a
       question whose type moves inside a header whose height does not is a header
       that reflows while nothing has been pressed. The ramp stops at body. */
    .ds-collapsible--xlarge .ds-collapsible__head {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-collapsible-head-face: var(--ds-font-body-face);
      --ds-collapsible-head-tracking: var(--ds-font-body-tracking);
      --ds-collapsible-head-size: var(--ds-font-body-size);
      --ds-collapsible-head-leading: var(--ds-font-body-leading);
      font-family: var(--ds-collapsible-head-face);
     font-size: var(--ds-collapsible-head-size);
      letter-spacing: var(--ds-collapsible-head-tracking);
      line-height: var(--ds-collapsible-head-leading); }
    .ds-collapsible--xxlarge .ds-collapsible__head {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-collapsible-head-face: var(--ds-font-body-face);
      --ds-collapsible-head-tracking: var(--ds-font-body-tracking);
      --ds-collapsible-head-size: var(--ds-font-body-size);
      --ds-collapsible-head-leading: var(--ds-font-body-leading);
      font-family: var(--ds-collapsible-head-face);
     font-size: var(--ds-collapsible-head-size);
      letter-spacing: var(--ds-collapsible-head-tracking);
      line-height: var(--ds-collapsible-head-leading); }

    .ds-collapsible__head:hover:not(:disabled) {
      --ds-collapsible-head-ink: var(--ds-text-primary-neutral);
      color: var(--ds-collapsible-head-ink);
    }

    /* The heading a question sits in, when it is one. No margin and no size of
       its own: the button inside it carries both, and the element is here for the
       outline of the document rather than for the look of the page. */
    .ds-collapsible__heading {
      margin: 0;
      font: inherit;
    }

    .ds-collapsible__head:focus-visible {
      outline: 2px solid var(--ds-focus);
      outline-offset: -2px;
      border-radius: var(--ds-radius-2);
    }

    .ds-collapsible__head:disabled {
      --ds-collapsible-head-ink: var(--ds-text-disabled);
      color: var(--ds-collapsible-head-ink);
      cursor: not-allowed;
    }

    /* The column that holds the label and, under it, the summary. It takes the
       room the badge and the chevron leave. */
    .ds-collapsible__text {
      display: flex;
      flex: 1;
      flex-direction: column;
      min-inline-size: 0;
    }

    .ds-collapsible__label {
      min-inline-size: 0;
    }

    /* What is inside, without opening it. Quiet and one size down, with no gap of
       its own: the two lines carry their own leading and anything on top of it
       reads as two separate things rather than a label and its footnote. */
    .ds-collapsible__summary {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-collapsible-summary-face: var(--ds-font-small-face);
      --ds-collapsible-summary-tracking: var(--ds-font-small-tracking);
      --ds-collapsible-summary-size: var(--ds-font-small-size);
      --ds-collapsible-summary-leading: var(--ds-font-small-leading);
      font-family: var(--ds-collapsible-summary-face);
      font-size: var(--ds-collapsible-summary-size);
      letter-spacing: var(--ds-collapsible-summary-tracking);
      line-height: var(--ds-collapsible-summary-leading);
      /* The role's own, and this is a fix. It read `--ds-weight-body`, which no
         file declares: an invalid value at computed-value time, so the property
         was dropped and the line inherited the head's emphasis weight. The head
         is the title and this is the footnote under it, so it was wearing the
         title's weight for as long as the typo has been there. */
      font-weight: var(--ds-font-small-weight);
      --ds-collapsible-summary-ink: var(--ds-text-secondary-neutral);
      color: var(--ds-collapsible-summary-ink);
    }

    .ds-collapsible__icon {
      display: inline-flex;
      flex: none;
      inline-size: 1lh;
      block-size: 1lh;
    }

    .ds-collapsible__icon > svg {
      inline-size: 100%;
      block-size: 100%;
    }

    /* Points down when there is more to see and up when there is not, which is
       the one bit of state the closed row cannot show by itself. */
    .ds-collapsible__chevron {
      --ds-collapsible-chevron-ink: var(--ds-text-tertiary-neutral);
      color: var(--ds-collapsible-chevron-ink);
      transition: transform var(--ds-duration-fast) var(--ds-ease);
    }

    .ds-collapsible__head[aria-expanded="true"] .ds-collapsible__chevron {
      transform: rotate(180deg);
    }

    .ds-collapsible__panel {
      display: grid;
      grid-template-rows: 0fr;
      transition: grid-template-rows var(--ds-duration) var(--ds-ease-out);
    }

    .ds-collapsible__panel[data-open="true"] {
      grid-template-rows: 1fr;
    }

    /* The row of a grid does not clip its content on its own, so the child does.
       Without this the text is visible at full height while the row is still
       nothing tall. */
    .ds-collapsible__inner {
      overflow: hidden;
    }

    /* The answer is not the row. Opening one has to read as a paragraph under a
       heading and not as text glued to the underside of a button, so the content
       keeps its own air: enough above to break from the label, more below to
       separate it from the next row, whose head begins right after the line. */
    .ds-collapsible__content {
      padding-block: var(--ds-space-3) var(--ds-space-5);
    }

    /* ==========================================================================
       Accordion

       The CSS behind <Accordion>. Private, like every other component's.

       A stack of headings you can open. Reach for it where the list is long, the
       answers are short and most of them are not what the reader came for: the
       groups in a filter panel, a set of questions, the nested part of a menu on
       a narrow screen.

       ── A list of disclosures, and nothing else ───────────────────────────────
       What one row IS lives in `collapsible.css`: the head, the chevron, the
       panel that opens on a grid row going from `0fr` to `1fr`. What is left
       here is what only a LIST of them can have — the column they stand in, the
       line between two of them, and the strip that opens the lot. Until 7
       September 2026 this file held both, and a single disclosure was rendered
       as an accordion of one item, so a group header in a list reported itself
       as an accordion to anyone who inspected it.
       ========================================================================== */

    .ds-accordion {
      /* This component's own. The role says what the words are; this says that
         THIS component wants a heavier hand than the role has, which is a
         decision the component makes and nobody else. Pointed at the semantic
         rather than holding a number, so moving the system moves every component
         that has not been moved on purpose, and moving one moves one. */
      display: flex;
      flex-direction: column;
    }

    /* A line between two, and none under the last: the accordion sits in
       something that already has an edge. */
    .ds-accordion__item {
      border-block-end: 1px solid var(--ds-border-subtle);
    }

    .ds-accordion__item:last-child {
      border-block-end: 0;
    }

    /* The strip that opens the lot. Above the first row and pushed to the end of
       it, where a control that acts on the whole list is out of the way of the
       first thing anybody reads. */
    .ds-accordion__all {
      display: flex;
      justify-content: flex-end;
      padding-block-end: var(--ds-space-2);
    }

    /* ==========================================================================
       Two line

       The CSS behind <TwoLine>. Private, like every other component's.

       A title with a sub label under it, and room either side for what stands
       before the words and after them. The row every list is made of, and
       nothing of its own but the arrangement: the words are the label role and
       the small role, the same two the avatar's identity reads, so a row of
       records and a row of people sit level.
       ========================================================================== */

    .ds-two-line {
      display: flex;
      align-items: center;
      gap: var(--ds-space-3);
      min-inline-size: 0;
    }

    /* What stands before the words and after them keeps its own size; the
       words take the rest and give way first. */
    .ds-two-line__start,
    .ds-two-line__end {
      display: flex;
      flex: none;
      align-items: center;
      gap: var(--ds-space-2);
    }

    .ds-two-line__text {
      display: flex;
      flex: 1;
      flex-direction: column;
      /* No gap: the two lines carry their own leading, and a step between them
         reads as two things rather than a name and what it says. */
      min-inline-size: 0;
    }

    .ds-two-line__title {
      --ds-two-line-title-weight: var(--ds-emphasis-weight);
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. */
      --ds-two-line-title-face: var(--ds-font-label-face);
      --ds-two-line-title-size: var(--ds-font-label-size);
      --ds-two-line-title-leading: var(--ds-font-label-leading);
      --ds-two-line-title-tracking: var(--ds-font-label-tracking);
      font-family: var(--ds-two-line-title-face);
      font-size: var(--ds-two-line-title-size);
      line-height: var(--ds-two-line-title-leading);
      letter-spacing: var(--ds-two-line-title-tracking);
      font-weight: var(--ds-two-line-title-weight);
      --ds-two-line-title-ink: var(--ds-text-primary-neutral);
      color: var(--ds-two-line-title-ink);
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
    }

    .ds-two-line__sub {
      --ds-two-line-sub-face: var(--ds-font-small-face);
      --ds-two-line-sub-size: var(--ds-font-small-size);
      --ds-two-line-sub-weight: var(--ds-font-small-weight);
      --ds-two-line-sub-leading: var(--ds-font-small-leading);
      --ds-two-line-sub-tracking: var(--ds-font-small-tracking);
      font-family: var(--ds-two-line-sub-face);
      font-size: var(--ds-two-line-sub-size);
      font-weight: var(--ds-two-line-sub-weight);
      line-height: var(--ds-two-line-sub-leading);
      letter-spacing: var(--ds-two-line-sub-tracking);
      --ds-two-line-sub-ink: var(--ds-text-secondary-neutral);
      color: var(--ds-two-line-sub-ink);
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
    }

    /* The title is the link and nothing else is, and it does not underline under
       the pointer: the row it sits in already washes, and a rule under the words
       on top of that is the same thing said twice, which is what it read as on
       a list of them (owner, 7 September 2026). The ring still goes round the
       words the keyboard arrived on, since a focus ring is the only thing that
       says where the keyboard is. */
    .ds-two-line__link {
      text-decoration: none;
    }

    .ds-two-line__link:focus-visible {
      outline: 2px solid var(--ds-focus);
      outline-offset: 2px;
      border-radius: var(--ds-radius-1);
    }

    /* ==========================================================================
       List

       The CSS behind <List>. Private, like every other component's.

       The table's sibling for one column: the same sheet, the same ground and
       edge a card has on an element that is not a card, the strip of tools at
       its head inside the same edge and the pager at its foot. The rows are
       two lines with room either side; between them the hairline the table
       draws between its rows; over them the groups, each one a disclosure
       with the sheet's padding.
       ========================================================================== */

    .ds-list {
      display: block;
      background-color: var(--ds-surface-1);
      border: 1px solid var(--ds-border);
      border-radius: var(--ds-radius-3);
      overflow: hidden;
      /* The room around a row's words, the table's own for the header and the
         body, so a list and a table on the same page sit level. */
      --ds-list-pad-block: var(--ds-space-2);
      --ds-list-pad-inline: var(--ds-space-4);
    }

    /* ── Bare ─────────────────────────────────────────────────────────────────
       The rows without the sheet, for a card that already is one: the project
       card's children. The rows keep their padding, which is the card's, and what
       goes is the second box inside the first, the defect the owner's rule about
       a table inside a card is about (6 September 2026). */
    .ds-list--bare {
      background-color: transparent;
      border: 0;
      border-radius: 0;
      overflow: visible;
    }

    .ds-list__rows {
      list-style: none;
      margin: 0;
      padding: 0;
    }

    .ds-list__row {
      padding: var(--ds-list-pad-block) var(--ds-list-pad-inline);
      transition: background-color var(--ds-duration) var(--ds-ease);
    }

    .ds-list--lines-rows .ds-list__row + .ds-list__row {
      border-block-start: 1px solid var(--ds-border-subtle);
    }

    /* The box a row is ticked with. It stands at the head of the row and takes
       its own room, so the words start on one vertical whether a row is ticked
       or not — a box that appears and pushes the title sideways is a row that
       moves under the pointer.

       Always there, as the table's is: a box that appears under the pointer is a
       control the reader cannot know exists until they happen to find it.
       Owner's ruling, 10 September 2026; it was shown on hover until then. */
    .ds-list__row {
      /* The ground the drop line is drawn against, where the list is arranged by
         hand: a line in the gap has no element of its own to sit on. */
      position: relative;
      display: flex;
      align-items: center;
      gap: var(--ds-space-3);
    }

    .ds-list__select {
      display: flex;
      flex: none;
      align-items: center;
    }

    /* The row it is on, said the way the table says it, and it stays said under
       the pointer: a chosen row that reads the same as the one being hovered is
       two things wearing one ground. */
    .ds-list__row--selected,
    .ds-list__row--selected:hover {
      background-color: var(--ds-surface-active);
    }

    /* The two-line block takes the room the box leaves. */
    .ds-list__row > .ds-two-line {
      flex: 1;
      min-inline-size: 0;
    }

    /* A row that is done: the words struck and quiet, the way a ticked task
       reads, and everything else on the row as it was. */
    .ds-list__row--done .ds-two-line__title {
      --ds-list-done-ink: var(--ds-text-secondary-neutral);
      color: var(--ds-list-done-ink);
      text-decoration: line-through;
    }

    /* The row in the air stays where it was, faded, so nothing under the
       pointer moves out from under it: the board's ruling. */
    .ds-list__row--dragging {
      opacity: 0.4;
    }

    /* A row that leads somewhere says so with the hand; the hover wash the rows
       already share does the rest. */
    .ds-list__row--link {
      cursor: pointer;
    }

    .ds-list__row:hover {
      background-color: var(--ds-surface-hover);
    }

    /* The row with the keyboard's focus. Drawn inside its own box, since rows
       touch and a ring outside one would sit on the next. */
    .ds-list__row:focus-visible {
      outline: 2px solid var(--ds-focus);
      outline-offset: -2px;
    }

    /* A group is a disclosure standing in the sheet: its row takes the sheet's
       inline padding, so the label starts over the first word of the rows under
       it. The groups draw the line between them themselves.

       It said "the accordion's row" until 7 September 2026, and two more rules
       stood here zeroing padding that neither the head nor the panel has ever
       had: `.ds-accordion__head` declares `padding-block` twice and
       `padding-inline` never, and `.ds-accordion__panel` is a grid and a
       transition and nothing else. Both were no-ops that read as load-bearing.
       Gone with the rename. */
    .ds-list__group + .ds-list__group {
      border-block-start: 1px solid var(--ds-border-subtle);
    }

    .ds-list__group .ds-collapsible__row {
      padding-inline: var(--ds-list-pad-inline);
    }

    .ds-list__group .ds-list__rows,
    .ds-list__group .ds-list__add {
      border-block-start: 1px solid var(--ds-border-subtle);
    }

    /* The line that adds a row where it is typed: a mark and a field, on the
       row's own padding, so the words typed land where the row will stand. */
    .ds-list__add {
      display: flex;
      align-items: center;
      gap: var(--ds-space-2);
      padding: var(--ds-list-pad-block) var(--ds-list-pad-inline);
      --ds-list-add-ink: var(--ds-text-secondary-neutral);
      color: var(--ds-list-add-ink);
    }

    .ds-list__add-mark {
      display: inline-flex;
      flex: none;
      inline-size: var(--ds-space-4);
      block-size: var(--ds-space-4);
    }

    /* The other way in, at the line's end: a quiet menu of what already exists.
       On the add line and not on a line of its own, so the two ways a row joins
       read as one decision (owner, 10 September 2026, on a select drawn under
       the rows: "incongruenze tra select e add record"). */
    .ds-list__pick {
      display: inline-flex;
      flex: none;
      margin-inline-start: auto;
    }

    .ds-list--lines-rows .ds-list__rows + .ds-list__add {
      border-block-start: 1px solid var(--ds-border-subtle);
    }

    .ds-list__state {
      padding: var(--ds-list-pad-inline);
    }

    .ds-list__foot {
      padding: var(--ds-space-3) var(--ds-space-4);
      border-block-start: 1px solid var(--ds-border-subtle);
    }

    /* ── The row's hue ────────────────────────────────────────────────────────
       The same edge a table row wears, for the same reason and out of the same
       tokens: the hue of the option the record holds, down the leading side, as
       an inset shadow so the row gains no width. A list and a table showing the
       same records show them the same colour. */
    .ds-list__row[data-hue] {
      box-shadow: inset var(--ds-space-1) 0 0 0 var(--ds-list-row-hue);
    }

    .ds-list__row[data-hue="red"] { --ds-list-row-hue: var(--ds-hue-red-edge); }
    .ds-list__row[data-hue="orange"] { --ds-list-row-hue: var(--ds-hue-orange-edge); }
    .ds-list__row[data-hue="amber"] { --ds-list-row-hue: var(--ds-hue-amber-edge); }
    .ds-list__row[data-hue="yellow"] { --ds-list-row-hue: var(--ds-hue-yellow-edge); }
    .ds-list__row[data-hue="lime"] { --ds-list-row-hue: var(--ds-hue-lime-edge); }
    .ds-list__row[data-hue="green"] { --ds-list-row-hue: var(--ds-hue-green-edge); }
    .ds-list__row[data-hue="teal"] { --ds-list-row-hue: var(--ds-hue-teal-edge); }
    .ds-list__row[data-hue="cyan"] { --ds-list-row-hue: var(--ds-hue-cyan-edge); }
    .ds-list__row[data-hue="blue"] { --ds-list-row-hue: var(--ds-hue-blue-edge); }
    .ds-list__row[data-hue="indigo"] { --ds-list-row-hue: var(--ds-hue-indigo-edge); }
    .ds-list__row[data-hue="violet"] { --ds-list-row-hue: var(--ds-hue-violet-edge); }
    .ds-list__row[data-hue="pink"] { --ds-list-row-hue: var(--ds-hue-pink-edge); }

    /* ── Arranged by hand ─────────────────────────────────────────────────────
       A list whose order is the reader's wears a grip at the head of every row:
       the same mark, the same size and the same two arrows a `Sortable` has,
       because it is the same gesture and the same machine underneath.

       The grip and not the row, for the reason the Sortable gives: a row can
       hold a field, and `draggable` on the element around one takes away
       selecting the text inside it. */
    .ds-list__grip {
      position: relative;
      display: inline-flex;
      flex: none;
      align-items: center;
      justify-content: center;
      font: inherit;
      inline-size: 1lh;
      block-size: 1lh;
      padding: 0;
      border: 0;
      background: none;
      --ds-list-grip-ink: var(--ds-text-tertiary-neutral);
      color: var(--ds-list-grip-ink);
      cursor: grab;
      transition: color var(--ds-duration) var(--ds-ease);
    }

    .ds-list__grip:active {
      cursor: grabbing;
    }

    .ds-list__grip:hover,
    .ds-list__grip:focus-visible {
      --ds-list-grip-ink: var(--ds-text-primary-neutral);
      color: var(--ds-list-grip-ink);
    }

    /* Where it lands: a line in the gap above the row it is over, drawn on the
       row because there is no element in a gap to draw on. */
    .ds-list__row--over::before {
      content: "";
      position: absolute;
      inset-inline: 0;
      inset-block-start: -1px;
      block-size: 0;
      border-block-start: 2px solid var(--ds-border-active);
    }

    /* ── The row that opens ───────────────────────────────────────────────────
       A row can carry a panel under it, and the caret at its head turns rather
       than swapping for another mark: a chevron pointing down is the same
       control saying it is open. The panel is an item of the list itself, so
       what it holds reads in order with the rows around it. */
    .ds-list__caret {
      display: inline-flex;
      flex: none;
      align-items: center;
      justify-content: center;
      font: inherit;
      inline-size: 1lh;
      block-size: 1lh;
      padding: 0;
      border: 0;
      background: none;
      --ds-list-caret-ink: var(--ds-text-tertiary-neutral);
      color: var(--ds-list-caret-ink);
      cursor: pointer;
      transition:
        color var(--ds-duration) var(--ds-ease),
        rotate var(--ds-duration) var(--ds-ease);
    }

    .ds-list__caret:hover,
    .ds-list__caret:focus-visible {
      --ds-list-caret-ink: var(--ds-text-primary-neutral);
      color: var(--ds-list-caret-ink);
    }

    .ds-list__caret[aria-expanded="true"] {
      rotate: 90deg;
    }

    .ds-list__detail {
      padding: 0 var(--ds-list-pad-inline) var(--ds-list-pad-block);
    }

    /* ==========================================================================
       Project card

       The CSS behind <ProjectCard>. Private, like every other component's.

       A parent and how far along it is, with its children under it when opened:
       the tasks of a project, the steps of a plan. A card, because the name, the
       badges and the bar are read together and shut; a fold, because what it
       holds is a list the reader opens for the detail.

       ── The sheet is the card's, the fold is the collapsible's ───────────────
       The box reads the same tokens a Card reads, so a grid of these and a grid
       of cards sit level; the fold animates the way the Collapsible does, a grid
       row from 0fr to 1fr, so nothing is measured and written back. Neither is
       borrowed by class: this component declares its own parts and points them
       at the same decisions.

       ── The children are a bare list ─────────────────────────────────────────
       The card is already the sheet, so the List inside it comes without one
       (`frame="bare"`): its rows keep their own padding, which is the card's,
       and a second box inside the first was the defect the owner's rule about
       tables in cards is about.
       ========================================================================== */

    .ds-project-card {
      --ds-project-card-pad: var(--ds-space-4);
      position: relative;
      display: flex;
      flex-direction: column;
      min-inline-size: 0;
      background-color: var(--ds-surface-1);
      border: 1px solid var(--ds-border);
      border-radius: var(--ds-radius-3);
      overflow: hidden;
    }

    /* The head row: the fold's button, and beside it what stands at the row's
       end outside the button, since a control inside a control is one nobody can
       reach. The button takes the rest of the row. */
    .ds-project-card__row {
      display: flex;
      align-items: center;
      gap: var(--ds-space-2);
      padding: var(--ds-space-2) var(--ds-project-card-pad) 0;
    }

    .ds-project-card__row > * {
      flex: 1;
      min-inline-size: 0;
    }

    .ds-project-card__heading {
      margin: 0;
      font: inherit;
    }

    /* The whole row is the button, which is the difference between reaching for
       a chevron and reaching for a name. One tier, the collapsible's medium. */
    .ds-project-card__head {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. */
      --ds-project-card-head-face: var(--ds-font-body-face);
      --ds-project-card-head-size: var(--ds-font-body-size);
      --ds-project-card-head-leading: var(--ds-font-body-leading);
      --ds-project-card-head-tracking: var(--ds-font-body-tracking);
      --ds-project-card-head-weight: var(--ds-emphasis-weight);
      --ds-project-card-tier: var(--ds-size-3);
      display: flex;
      align-items: center;
      gap: var(--ds-space-3);
      inline-size: 100%;
      min-block-size: var(--ds-project-card-tier);
      padding: var(--ds-space-1) 0;
      border: 0;
      background: none;
      font-family: var(--ds-project-card-head-face);
      font-size: var(--ds-project-card-head-size);
      line-height: var(--ds-project-card-head-leading);
      letter-spacing: var(--ds-project-card-head-tracking);
      font-weight: var(--ds-project-card-head-weight);
      --ds-project-card-head-ink: var(--ds-text-primary-neutral);
      color: var(--ds-project-card-head-ink);
      text-align: start;
      cursor: pointer;
    }

    .ds-project-card__head:focus-visible {
      outline: 2px solid var(--ds-focus);
      outline-offset: 2px;
      border-radius: var(--ds-radius-1);
    }

    /* The name holds its line and gives way with an ellipsis: a card is one
       thing, and one thing has one line for its name. */
    .ds-project-card__title {
      flex: 1;
      min-inline-size: 0;
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
    }

    /* The count's column, a tier wide with the badge centred in it, so what
       stands beside it ends on one vertical down a column of cards whether the
       count is one digit or two: the collapsible's own ruling. */
    .ds-project-card__count {
      display: flex;
      flex: none;
      justify-content: center;
      min-inline-size: var(--ds-size-1);
    }

    .ds-project-card__chevron {
      display: flex;
      flex: none;
      inline-size: 1lh;
      block-size: 1lh;
      --ds-project-card-chevron-ink: var(--ds-text-tertiary-neutral);
      color: var(--ds-project-card-chevron-ink);
      transition: transform var(--ds-duration-fast) var(--ds-ease);
    }

    .ds-project-card__chevron > svg {
      inline-size: 100%;
      block-size: 100%;
    }

    .ds-project-card__head[aria-expanded="true"] .ds-project-card__chevron {
      transform: rotate(180deg);
    }

    .ds-project-card__end {
      display: flex;
      flex: none;
      align-items: center;
      gap: var(--ds-space-2);
    }

    /* What the thing is, as badges under its name: a row that wraps, outside the
       button so each badge can be its own control. */
    .ds-project-card__meta {
      display: flex;
      flex-wrap: wrap;
      align-items: center;
      gap: var(--ds-space-2);
      padding: 0 var(--ds-project-card-pad);
    }

    /* How far along it is, at the width of the card. It closes the head with the
       same step the head opened on, so a shut card is one sheet with even air. */
    .ds-project-card__progress {
      display: block;
      padding: var(--ds-space-2) var(--ds-project-card-pad) var(--ds-space-3);
    }

    /* A card with neither badges nor a bar closes its head on the row's own
       step, so it is not a box with a gap under its name. */
    .ds-project-card__row:last-of-type {
      padding-block-end: var(--ds-space-2);
    }

    /* The fold: a grid row from nothing to the content's own height, which is
       animatable where `height: auto` never was. */
    .ds-project-card__panel {
      display: grid;
      grid-template-rows: 0fr;
      transition: grid-template-rows var(--ds-duration) var(--ds-ease-out);
    }

    .ds-project-card__panel[data-open="true"] {
      grid-template-rows: 1fr;
    }

    .ds-project-card__inner {
      overflow: hidden;
    }

    /* The children under a line: what the card holds starts where the head
       ends, and a bare list's first row sits directly on the line. */
    .ds-project-card__content {
      display: block;
      border-block-start: 1px solid var(--ds-border-subtle);
    }

    /* ==========================================================================
       Roadmap

       The CSS behind <Roadmap>. Private, like every other component's.

       The phases a thing goes through, in order: a line through numbered stops,
       filled to the stop the work has reached, and under each stop the phase's
       name, its words, its bar and its deliverables. Phases, and never dates on
       an axis: the columns are equal because a phase is a step and not a span.

       ── The line is drawn by the stops ───────────────────────────────────────
       Each phase draws its own half of the line to either side of its stop, so
       the path runs through the gaps between columns and needs no measuring: a
       done phase fills both halves, the current one fills the half behind it,
       and the first and last phases leave their outer half undrawn.

       ── The deliverables are a bare list ─────────────────────────────────────
       The column is the sheet, so the List inside it comes without one; its rows
       keep the list's own gutter, and the column pulls the list out by that
       gutter so the first word of a row stands under the first letter of the
       phase's name.
       ========================================================================== */

    .ds-roadmap {
      --ds-roadmap-gap: var(--ds-space-6);
      --ds-roadmap-column: 16rem;
      --ds-roadmap-pad: var(--ds-space-4);
      display: flex;
      flex-direction: column;
      min-inline-size: 0;
    }

    /* ── The sheet ────────────────────────────────────────────────────────────
       The surface and the edge a table has, for a roadmap standing among
       others. A sheet and never a card: the first product put its roadmaps in
       cards, and a card leads somewhere, so the whole of it washed under the
       hand while the thing inside held a dozen controls (owner, 10 September
       2026, "pessima scelta"). Nothing here answers the pointer but the controls
       in it. */
    .ds-roadmap--sheet {
      background-color: var(--ds-surface-1);
      border: 1px solid var(--ds-border);
      border-radius: var(--ds-radius-3);
      overflow: hidden;
    }

    /* The sheet's header: whose roadmap it is, and what acts on it at the row's
       end. Named apart from the phase's own `__head`, which it once shadowed:
       for one release every phase's name and words were centred under their
       stop, because two parts had been given one name. */
    .ds-roadmap__header {
      display: flex;
      align-items: center;
      gap: var(--ds-space-3);
      padding: var(--ds-roadmap-pad) var(--ds-roadmap-pad) 0;
    }

    .ds-roadmap__name {
      display: flex;
      flex: 1;
      flex-direction: column;
      gap: var(--ds-space-1);
      min-inline-size: 0;
    }

    .ds-roadmap__name-title {
      --ds-roadmap-name-face: var(--ds-font-body-face);
      --ds-roadmap-name-size: var(--ds-font-body-size);
      --ds-roadmap-name-leading: var(--ds-font-body-leading);
      --ds-roadmap-name-tracking: var(--ds-font-body-tracking);
      --ds-roadmap-name-weight: var(--ds-emphasis-weight);
      --ds-roadmap-name-ink: var(--ds-text-primary-neutral);
      margin: 0;
      font-family: var(--ds-roadmap-name-face);
      font-size: var(--ds-roadmap-name-size);
      line-height: var(--ds-roadmap-name-leading);
      letter-spacing: var(--ds-roadmap-name-tracking);
      font-weight: var(--ds-roadmap-name-weight);
      color: var(--ds-roadmap-name-ink);
    }

    .ds-roadmap__description {
      --ds-roadmap-description-face: var(--ds-font-small-face);
      --ds-roadmap-description-size: var(--ds-font-small-size);
      --ds-roadmap-description-leading: var(--ds-font-small-leading);
      --ds-roadmap-description-tracking: var(--ds-font-small-tracking);
      --ds-roadmap-description-weight: var(--ds-font-small-weight);
      --ds-roadmap-description-ink: var(--ds-text-tertiary-neutral);
      margin: 0;
      font-family: var(--ds-roadmap-description-face);
      font-size: var(--ds-roadmap-description-size);
      line-height: var(--ds-roadmap-description-leading);
      letter-spacing: var(--ds-roadmap-description-tracking);
      font-weight: var(--ds-roadmap-description-weight);
      color: var(--ds-roadmap-description-ink);
    }

    .ds-roadmap__end {
      display: flex;
      flex: none;
      align-items: center;
      gap: var(--ds-space-2);
    }

    /* What the reader edits about the whole path, between the head and the stops. */
    .ds-roadmap__fields {
      display: block;
      padding: var(--ds-space-4) var(--ds-roadmap-pad) 0;
    }

    /* The stops scroll sideways on their own, so the head stays put; and they
       take a step more above and below than at the sides, so a sheet of them
       breathes between its head and its foot. */
    .ds-roadmap__phases {
      overflow-x: auto;
      padding-block: var(--ds-space-5);
    }

    .ds-roadmap__phases {
      display: grid;
      grid-auto-flow: column;
      grid-auto-columns: minmax(var(--ds-roadmap-column), 1fr);
      gap: var(--ds-roadmap-gap);
      margin: 0;
      padding: var(--ds-roadmap-pad);
      list-style: none;
    }

    .ds-roadmap__phase {
      position: relative;
      display: flex;
      flex-direction: column;
      /* A step, not a line: the stop, the name, the bar and the rows are four
         things read one after another, and at 12 they read as one block (owner,
         10 September 2026, "spacing verticale da aumentare un po'"). */
      gap: var(--ds-space-4);
      min-inline-size: 0;
    }

    /* The phase a carried row is over says so with the ring the board's column
       wears, drawn around the whole column so the reader sees where the row
       will land before letting go. */
    .ds-roadmap__phase[data-over="true"] {
      outline: 2px solid var(--ds-border-active);
      outline-offset: var(--ds-space-2);
      border-radius: var(--ds-radius-2);
    }

    /* The stop, and the two halves of the line it draws. */
    .ds-roadmap__stop {
      position: relative;
      display: flex;
      align-items: center;
      block-size: var(--ds-size-2);
    }

    .ds-roadmap__stop::before,
    .ds-roadmap__stop::after {
      content: "";
      position: absolute;
      inset-block-start: 50%;
      block-size: 2px;
      transform: translateY(-50%);
      --ds-roadmap-line: var(--ds-border);
      background-color: var(--ds-roadmap-line);
    }

    /* The half behind runs from the gap's far edge to the column's start, where
       the node stands; the half ahead from the node's far edge to the gap's far
       edge. Measured from the column's middle at first, so the line began a
       hundred pixels to the right of the node and read as a missing piece
       (owner, 10 September 2026: "come se mancasse un pezzo"). */
    .ds-roadmap__stop::before {
      inset-inline: calc(var(--ds-roadmap-gap) * -1) 100%;
    }

    .ds-roadmap__stop::after {
      inset-inline: var(--ds-size-2) calc(var(--ds-roadmap-gap) * -1);
    }

    .ds-roadmap__phase:first-child .ds-roadmap__stop::before,
    .ds-roadmap__phase:last-child .ds-roadmap__stop::after {
      display: none;
    }

    .ds-roadmap__phase[data-state="done"] .ds-roadmap__stop::before,
    .ds-roadmap__phase[data-state="done"] .ds-roadmap__stop::after,
    .ds-roadmap__phase[data-state="current"] .ds-roadmap__stop::before {
      --ds-roadmap-line: var(--ds-success);
    }

    /* The node: a circle a tier wide, the number in the label's type, the tick
       once done. Current wears a ring, drawn as two shadows so it reads on any
       surface without a second element. */
    .ds-roadmap__node {
      --ds-roadmap-node-face: var(--ds-font-label-face);
      --ds-roadmap-node-size: var(--ds-font-label-size);
      --ds-roadmap-node-leading: var(--ds-font-label-leading);
      --ds-roadmap-node-tracking: var(--ds-font-label-tracking);
      --ds-roadmap-node-weight: var(--ds-emphasis-weight);
      --ds-roadmap-node-ink: var(--ds-text-secondary-neutral);
      --ds-roadmap-node-edge: var(--ds-border-strong);
      --ds-roadmap-node-fill: var(--ds-surface-1);
      position: relative;
      z-index: 1;
      display: inline-flex;
      align-items: center;
      justify-content: center;
      inline-size: var(--ds-size-2);
      block-size: var(--ds-size-2);
      border-radius: var(--ds-radius-full);
      border: 2px solid var(--ds-roadmap-node-edge);
      background-color: var(--ds-roadmap-node-fill);
      color: var(--ds-roadmap-node-ink);
      font-family: var(--ds-roadmap-node-face);
      font-size: var(--ds-roadmap-node-size);
      line-height: var(--ds-roadmap-node-leading);
      letter-spacing: var(--ds-roadmap-node-tracking);
      font-weight: var(--ds-roadmap-node-weight);
      font-variant-numeric: tabular-nums;
    }

    .ds-roadmap__node > svg {
      inline-size: 1lh;
      block-size: 1lh;
    }

    .ds-roadmap__phase[data-state="done"] .ds-roadmap__node {
      --ds-roadmap-node-edge: var(--ds-success);
      --ds-roadmap-node-fill: var(--ds-success);
      --ds-roadmap-node-ink: var(--ds-on-success);
    }

    .ds-roadmap__phase[data-state="current"] .ds-roadmap__node {
      --ds-roadmap-node-edge: var(--ds-text-primary-neutral);
      --ds-roadmap-node-ink: var(--ds-text-primary-neutral);
      box-shadow:
        0 0 0 4px var(--ds-surface-1),
        0 0 0 5px var(--ds-border);
    }

    /* The name and its words. */
    .ds-roadmap__head {
      display: flex;
      flex-direction: column;
      gap: var(--ds-space-1);
      min-inline-size: 0;
    }

    .ds-roadmap__title {
      --ds-roadmap-title-face: var(--ds-font-body-face);
      --ds-roadmap-title-size: var(--ds-font-body-size);
      --ds-roadmap-title-leading: var(--ds-font-body-leading);
      --ds-roadmap-title-tracking: var(--ds-font-body-tracking);
      --ds-roadmap-title-weight: var(--ds-emphasis-weight);
      --ds-roadmap-title-ink: var(--ds-text-primary-neutral);
      margin: 0;
      font-family: var(--ds-roadmap-title-face);
      font-size: var(--ds-roadmap-title-size);
      line-height: var(--ds-roadmap-title-leading);
      letter-spacing: var(--ds-roadmap-title-tracking);
      font-weight: var(--ds-roadmap-title-weight);
      color: var(--ds-roadmap-title-ink);
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
    }

    .ds-roadmap__meta {
      --ds-roadmap-meta-face: var(--ds-font-small-face);
      --ds-roadmap-meta-size: var(--ds-font-small-size);
      --ds-roadmap-meta-leading: var(--ds-font-small-leading);
      --ds-roadmap-meta-tracking: var(--ds-font-small-tracking);
      --ds-roadmap-meta-weight: var(--ds-font-small-weight);
      --ds-roadmap-meta-ink: var(--ds-text-tertiary-neutral);
      margin: 0;
      font-family: var(--ds-roadmap-meta-face);
      font-size: var(--ds-roadmap-meta-size);
      line-height: var(--ds-roadmap-meta-leading);
      letter-spacing: var(--ds-roadmap-meta-tracking);
      font-weight: var(--ds-roadmap-meta-weight);
      color: var(--ds-roadmap-meta-ink);
    }

    /* What lies ahead is quieter as a whole, so the eye lands on the stop the
       path has reached. */
    .ds-roadmap__phase[data-state="upcoming"] .ds-roadmap__title {
      --ds-roadmap-title-ink: var(--ds-text-secondary-neutral);
    }

    .ds-roadmap__progress {
      display: block;
    }

    /* The bare list pulled out by its own gutter, so its rows' first words stand
       under the phase's name; the list's token is read, not its number. */
    .ds-roadmap__items {
      display: block;
      margin-inline: calc(var(--ds-list-pad-inline, var(--ds-space-4)) * -1);
    }

    /* ==========================================================================
       Tag field

       The CSS behind <TagField>. Private, like every other component's.

       A list of words the reader writes and arranges: pills in a field's frame,
       and after them the box a new word is typed into. The frame is the field's,
       multiline, so the pills wrap and the field stands level with the others in
       a form; what this file adds is the wrap, the pills' gap and the two states
       of a pill in the air.
       ========================================================================== */

    .ds-tag-field .ds-field__frame {
      flex-wrap: wrap;
      row-gap: var(--ds-space-2);
      /* One row of pills stands exactly as tall as the field beside it: the pill
         is the smallest badge, a rung of 24, and the room above and below it is
         what the field's own tier leaves. Measured before this rule: 56px for a
         medium field whose neighbours are 40. The multiline shell's inset would
         pad the box for text; this is a box of pills. */
      --ds-tag-field-tier: var(--ds-size-3);
      /* Less the frame's own line on each side: the frame is border-box, and
         without the two pixels one row of pills measured 42 in a form of 40s,
         found 11 September 2026 when the combobox borrowed this rule. */
      padding-block: calc((var(--ds-tag-field-tier) - var(--ds-size-1)) / 2 - 1px);
    }

    .ds-tag-field.ds-field--xsmall .ds-field__frame { --ds-tag-field-tier: var(--ds-size-1); }
    .ds-tag-field.ds-field--small .ds-field__frame { --ds-tag-field-tier: var(--ds-size-2); }
    .ds-tag-field.ds-field--large .ds-field__frame { --ds-tag-field-tier: var(--ds-size-4); }
    .ds-tag-field.ds-field--xlarge .ds-field__frame { --ds-tag-field-tier: var(--ds-size-5); }
    .ds-tag-field.ds-field--xxlarge .ds-field__frame { --ds-tag-field-tier: var(--ds-size-6); }

    .ds-tag-field__pills {
      display: flex;
      flex-wrap: wrap;
      align-items: center;
      gap: var(--ds-space-2);
      margin: 0;
      padding: 0;
      list-style: none;
      min-inline-size: 0;
    }

    .ds-tag-field__pills:empty {
      display: none;
    }

    /* A pill is where a drag lands, so it is a block with the badge in it and
       nothing of its own to draw. */
    .ds-tag-field__pill {
      display: inline-flex;
      cursor: grab;
    }

    .ds-tag-field__pill--carried,
    .ds-tag-field__pill.ds-sortable__item--dragging {
      opacity: 0.5;
    }

    .ds-tag-field__pill--over {
      outline: 2px solid var(--ds-focus);
      outline-offset: 2px;
      border-radius: var(--ds-radius-full);
    }

    /* Bare, the pills stand flush with whatever stands around them: the frame
       has no edge to keep them off, and a row of pills indented by an inset no
       line explains read as a disaster of padding beside the name above them
       (owner, 10 September 2026, on the roadmap's phases). */
    .ds-tag-field.ds-field--bare .ds-field__frame {
      padding-inline: 0;
    }

    /* The box takes what the pills leave, and never less than a word. */
    .ds-tag-field__input {
      flex: 1 1 8ch;
      min-inline-size: 8ch;
      align-self: center;
      block-size: var(--ds-size-1);
    }

    /* ==========================================================================
       Combobox

       The CSS behind <Combobox>. Private, like every other component's.

       A field that filters a list as it is typed into. The frame is the
       field's, so it stands level with every other field in a form; the lines
       under it are the popover's, so they read like every other list that drops
       from a control. What this file adds is the pills of a box that takes
       several answers, the arrow at the box's end, and the room the box keeps.
       ========================================================================== */

    .ds-combobox .ds-field__frame {
      flex-wrap: wrap;
      row-gap: var(--ds-space-2);
    }

    /* One row of pills stands as tall as the field beside it, the tag field's
       own ruling: the pill is the smallest badge, a rung of 24. */
    .ds-combobox.ds-field--multiline .ds-field__frame {
      --ds-combobox-tier: var(--ds-size-3);
      /* Less the frame's own line on each side, as the tag field's: measured 42
         against the single box's 40 before the two pixels came off. */
      padding-block: calc((var(--ds-combobox-tier) - var(--ds-size-1)) / 2 - 1px);
    }

    .ds-combobox.ds-field--small.ds-field--multiline .ds-field__frame { --ds-combobox-tier: var(--ds-size-2); }
    .ds-combobox.ds-field--large.ds-field--multiline .ds-field__frame { --ds-combobox-tier: var(--ds-size-4); }

    .ds-combobox__pills {
      display: flex;
      flex-wrap: wrap;
      align-items: center;
      gap: var(--ds-space-2);
      margin: 0;
      padding: 0;
      list-style: none;
      min-inline-size: 0;
    }

    .ds-combobox__pill {
      display: inline-flex;
    }

    /* The box takes what the pills leave, and never less than a word. */
    .ds-combobox__input {
      flex: 1 1 8ch;
      min-inline-size: 8ch;
      align-self: center;
    }

    .ds-combobox.ds-field--multiline .ds-combobox__input {
      block-size: var(--ds-size-1);
    }

    /* The arrow that says a list drops from here, the select's own, at the
       box's end and outside the typing. */
    .ds-combobox__arrow {
      --ds-combobox-arrow-ink: var(--ds-text-tertiary-neutral);
      display: inline-flex;
      flex: none;
      align-self: center;
      inline-size: 1lh;
      block-size: 1lh;
      color: var(--ds-combobox-arrow-ink);
      pointer-events: none;
    }

    .ds-combobox__arrow > svg {
      inline-size: 100%;
      block-size: 100%;
    }

    /* ==========================================================================
       Editor

       The CSS behind <Editor>. Private, like every other component's.

       A sheet in three parts: the toolbar in the head, the text at the left in
       Prose's own faces, and at the right a rail that says what can be added
       and what is already there. The handle at a block's left is the engine's,
       drawn over the text when the pointer is on a block.

       ── Why a sheet and not a field's frame ──────────────────────────────────
       The owner chose it by eye on 11 September 2026 out of six drawings: a
       field's frame holds a line or four, and a document with a toolbar and a
       rail is a sheet. The label, hint and message are still the field's, so
       an editor in a form stands under a label like everything beside it.

       ── The rail folds by its own width ──────────────────────────────────────
       Under `--ds-break-phone` the rail is gone and the text takes the sheet;
       the toolbar's last button brings it back over the text. A container
       query, because an editor in a drawer does not know the window.
       ========================================================================== */

    .ds-editor {
      container-type: inline-size;
      container-name: ds-editor;
    }

    .ds-editor__sheet {
      display: flex;
      flex-direction: column;
      min-inline-size: 0;
      background-color: var(--ds-surface-1);
      border: 1px solid var(--ds-border);
      border-radius: var(--ds-radius-3);
      overflow: hidden;
    }

    .ds-field--invalid .ds-editor__sheet {
      border-color: var(--ds-danger);
    }

    /* The head: the buttons in groups with a rule between the groups, the
       type button reading the caret's block, and at the end the rail's switch. */
    .ds-editor__bar {
      display: flex;
      align-items: center;
      flex-wrap: wrap;
      gap: var(--ds-space-1);
      padding: var(--ds-space-2) var(--ds-space-3);
      border-block-end: 1px solid var(--ds-border-subtle);
    }

    .ds-editor__group {
      display: inline-flex;
      gap: var(--ds-space-1);
    }

    .ds-editor__rule {
      display: inline-block;
      inline-size: 1px;
      block-size: var(--ds-size-1);
      background-color: var(--ds-border-subtle);
      margin-inline: var(--ds-space-1);
    }

    .ds-editor__space {
      flex: 1;
    }

    /* The words on the type button carry the block's name and hold their line. */
    .ds-editor__type-word {
      display: inline-block;
      min-inline-size: 6ch;
      text-align: start;
    }

    /* The two columns: the text, and the rail at a fixed width beside it. */
    .ds-editor__main {
      position: relative;
      display: grid;
      grid-template-columns: minmax(0, 1fr) var(--ds-editor-rail-width);
      --ds-editor-rail-width: 15.5rem;
    }

    .ds-editor__main:has(> .ds-editor__rail[hidden]) {
      grid-template-columns: minmax(0, 1fr);
    }

    /* The text, in Prose's faces at the sheet's full width: a measure is for a
       page, and this is a box whose width the reader chose. The room at the
       left is the handle's. */
    .ds-editor__body {
      --ds-editor-body-gutter: var(--ds-space-6);
      --ds-editor-body-min: 12rem;
      display: block;
      min-inline-size: 0;
      min-block-size: var(--ds-editor-body-min);
      max-inline-size: none;
      padding: var(--ds-space-5) var(--ds-space-5) var(--ds-space-6) calc(var(--ds-editor-body-gutter) + var(--ds-space-4));
      outline: none;
      cursor: text;
    }

    .ds-editor__body:focus-visible {
      outline: none;
    }

    /* La stessa risposta che da' un campo: la propria linea, in colore fuoco. Era
       un anello di 2px staccato di altri 2, cioe' quattro volte il peso del bordo
       di una InputField a fuoco nella stessa pagina (proprietario, 15 settembre
       2026). E' la ripetizione della decisione del 5 settembre su `field.css`: il
       colore dice il fuoco, il peso resta quello della cornice. */
    .ds-editor__sheet:has(.ds-editor__body:focus-visible) {
      border-color: var(--ds-focus);
    }

    .ds-editor__body[contenteditable="false"] {
      cursor: default;
    }

    /* An empty editor says what to do in the first line's place: one paragraph
       holding nothing but the line break the caret stands on. */
    .ds-editor__body[data-placeholder] > p:only-child:has(> br:only-child)::before {
      --ds-editor-placeholder-ink: var(--ds-text-tertiary-neutral);
      content: attr(data-placeholder);
      color: var(--ds-editor-placeholder-ink);
      pointer-events: none;
    }

    /* A figure's caption is always there while the text is being written, even
       with nothing in it: a caption you cannot see is a caption you cannot write,
       and this is the button for it that does not exist. The same placeholder the
       empty body wears, and the same test — a line holding only a `br`. The value
       never carries an empty one: `clean()` drops it. */
    .ds-editor__body figcaption[data-placeholder]:has(> br:only-child)::before {
      --ds-editor-placeholder-ink: var(--ds-text-tertiary-neutral);
      content: attr(data-placeholder);
      color: var(--ds-editor-placeholder-ink);
      pointer-events: none;
    }

    /* Le fotografie che la finestra sta modificando, in piccolo e in riga: senza
       vederle si sta cambiando qualcosa a memoria. */
    .ds-editor__figure-shot {
      display: flex;
      flex-wrap: wrap;
      gap: var(--ds-space-2);
    }

    .ds-editor__figure-shot img {
      max-block-size: 5rem;
      max-inline-size: 100%;
      border-radius: var(--ds-radius-2);
    }

    /* Cambiare la fotografia e toglierla: verbi che riguardano lo scatto, quindi
       stanno accanto allo scatto e non nel piede, che e' per chiudere o
       confermare. */
    .ds-editor__figure-acts {
      display: flex;
      gap: var(--ds-space-2);
    }

    /* The figure the caret is in. A caret shows itself in words; inside a figure
       there are no words to show it in, so without this a reader clicks a picture,
       something IS selected, and nothing on the screen says so — least of all the
       toolbar button quietly changing from «Text» to «In the column». The ring is
       the focus colour because that is what this is: where you are. */
    .ds-editor__body figure[data-here] {
      outline: 2px solid var(--ds-focus);
      outline-offset: 3px;
      border-radius: var(--ds-radius-1);
    }

    /* The picture is the block, and the block is dragged by its handle: without
       this the browser's own drag of the image starts instead, and what moves is
       a ghost of the picture while the text stands still. */
    .ds-editor__body figure img {
      -webkit-user-drag: none;
    }

    .ds-editor__body > [data-dragging] {
      opacity: 0.4;
    }

    .ds-editor__body > [data-over="before"] {
      box-shadow: 0 -2px 0 0 var(--ds-focus);
    }

    .ds-editor__body > [data-over="after"] {
      box-shadow: 0 2px 0 0 var(--ds-focus);
    }

    /* The handle: a plus and a grip at the block's left, over the gutter,
       shown while the pointer is on a block. */
    .ds-editor__handle {
      --ds-editor-handle-ink: var(--ds-text-tertiary-neutral);
      position: absolute;
      display: flex;
      gap: 0;
      transform: translateX(calc(-100% - var(--ds-space-1)));
      color: var(--ds-editor-handle-ink);
    }

    .ds-editor__handle[hidden] {
      display: none;
    }

    .ds-editor__handle-add,
    .ds-editor__handle-grip {
      display: inline-flex;
      align-items: center;
      justify-content: center;
      inline-size: var(--ds-size-1);
      block-size: var(--ds-size-1);
      padding: 0;
      border: 0;
      border-radius: var(--ds-radius-1);
      background: none;
      color: inherit;
      cursor: pointer;
      transition:
        background-color var(--ds-duration) var(--ds-ease),
        color var(--ds-duration) var(--ds-ease);
    }

    .ds-editor__handle-grip {
      cursor: grab;
      touch-action: none;
    }

    .ds-editor__handle-add:hover,
    .ds-editor__handle-grip:hover {
      --ds-editor-handle-hover-ink: var(--ds-text-primary-neutral);
      background-color: var(--ds-surface-hover);
      color: var(--ds-editor-handle-hover-ink);
    }

    .ds-editor__handle-add:focus-visible,
    .ds-editor__handle-grip:focus-visible {
      outline: 2px solid var(--ds-focus);
      outline-offset: 1px;
    }

    .ds-editor__handle svg {
      inline-size: var(--ds-space-4);
      block-size: var(--ds-space-4);
    }

    /* The rail: what can be added, as tiles two across; what is there, as one
       line per block; and a hint about `/` at the foot. */
    .ds-editor__rail {
      display: grid;
      align-content: start;
      gap: var(--ds-space-6);
      padding: var(--ds-space-5) var(--ds-space-4) var(--ds-space-6);
      border-inline-start: 1px solid var(--ds-border-subtle);
      min-inline-size: 0;
    }

    .ds-editor__rail[hidden] {
      display: none;
    }

    .ds-editor__rail-title {
      --ds-editor-rail-title-face: var(--ds-font-overline-face);
      --ds-editor-rail-title-size: var(--ds-font-overline-size);
      --ds-editor-rail-title-leading: var(--ds-font-overline-leading);
      --ds-editor-rail-title-tracking: var(--ds-font-overline-tracking);
      --ds-editor-rail-title-weight: var(--ds-font-overline-weight);
      --ds-editor-rail-title-ink: var(--ds-text-tertiary-neutral);
      margin: 0 0 var(--ds-space-3);
      font-family: var(--ds-editor-rail-title-face);
      font-size: var(--ds-editor-rail-title-size);
      line-height: var(--ds-editor-rail-title-leading);
      letter-spacing: var(--ds-editor-rail-title-tracking);
      font-weight: var(--ds-editor-rail-title-weight);
      text-transform: uppercase;
      color: var(--ds-editor-rail-title-ink);
    }

    .ds-editor__tiles {
      display: grid;
      grid-template-columns: repeat(2, minmax(0, 1fr));
      gap: var(--ds-space-2);
    }

    .ds-editor__tile {
      --ds-editor-tile-face: var(--ds-font-small-face);
      --ds-editor-tile-size: var(--ds-font-small-size);
      --ds-editor-tile-leading: var(--ds-font-small-leading);
      --ds-editor-tile-tracking: var(--ds-font-small-tracking);
      --ds-editor-tile-weight: var(--ds-font-small-weight);
      --ds-editor-tile-ink: var(--ds-text-primary-neutral);
      --ds-editor-tile-mark: var(--ds-text-secondary-neutral);
      display: grid;
      justify-items: start;
      gap: var(--ds-space-2);
      padding: var(--ds-space-3);
      border: 1px solid var(--ds-border-subtle);
      border-radius: var(--ds-radius-2);
      background: none;
      font-family: var(--ds-editor-tile-face);
      font-size: var(--ds-editor-tile-size);
      line-height: var(--ds-editor-tile-leading);
      letter-spacing: var(--ds-editor-tile-tracking);
      font-weight: var(--ds-editor-tile-weight);
      color: var(--ds-editor-tile-ink);
      text-align: start;
      cursor: pointer;
      transition: background-color var(--ds-duration) var(--ds-ease);
    }

    .ds-editor__tile:hover {
      background-color: var(--ds-surface-hover);
    }

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

    .ds-editor__tile > svg {
      inline-size: var(--ds-space-4);
      block-size: var(--ds-space-4);
      color: var(--ds-editor-tile-mark);
    }

    .ds-editor__outline {
      display: grid;
      gap: var(--ds-space-1);
    }

    .ds-editor__outline-line {
      --ds-editor-outline-face: var(--ds-font-small-face);
      --ds-editor-outline-size: var(--ds-font-small-size);
      --ds-editor-outline-leading: var(--ds-font-small-leading);
      --ds-editor-outline-tracking: var(--ds-font-small-tracking);
      --ds-editor-outline-weight: var(--ds-font-small-weight);
      --ds-editor-outline-ink: var(--ds-text-secondary-neutral);
      --ds-editor-outline-mark: var(--ds-text-tertiary-neutral);
      --ds-editor-outline-tier: var(--ds-size-1);
      display: flex;
      align-items: center;
      gap: var(--ds-space-2);
      min-block-size: var(--ds-editor-outline-tier);
      padding: 0 var(--ds-space-2);
      border: 0;
      border-radius: var(--ds-radius-1);
      background: none;
      font-family: var(--ds-editor-outline-face);
      font-size: var(--ds-editor-outline-size);
      line-height: var(--ds-editor-outline-leading);
      letter-spacing: var(--ds-editor-outline-tracking);
      font-weight: var(--ds-editor-outline-weight);
      color: var(--ds-editor-outline-ink);
      text-align: start;
      cursor: pointer;
      min-inline-size: 0;
      transition:
        background-color var(--ds-duration) var(--ds-ease),
        color var(--ds-duration) var(--ds-ease);
    }

    .ds-editor__outline-line:hover {
      background-color: var(--ds-surface-hover);
    }

    .ds-editor__outline-line:focus-visible {
      outline: 2px solid var(--ds-focus);
      outline-offset: 1px;
    }

    .ds-editor__outline-line[aria-current="true"] {
      --ds-editor-outline-current-ink: var(--ds-text-primary-neutral);
      background-color: var(--ds-surface-2);
      color: var(--ds-editor-outline-current-ink);
    }

    .ds-editor__outline-line > svg {
      flex: none;
      inline-size: var(--ds-space-4);
      block-size: var(--ds-space-4);
      color: var(--ds-editor-outline-mark);
    }

    .ds-editor__outline-text {
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
    }

    .ds-editor__rail-hint {
      --ds-editor-rail-hint-face: var(--ds-font-small-face);
      --ds-editor-rail-hint-size: var(--ds-font-small-size);
      --ds-editor-rail-hint-leading: var(--ds-font-small-leading);
      --ds-editor-rail-hint-tracking: var(--ds-font-small-tracking);
      --ds-editor-rail-hint-weight: var(--ds-font-small-weight);
      --ds-editor-rail-hint-ink: var(--ds-text-tertiary-neutral);
      margin: 0;
      font-family: var(--ds-editor-rail-hint-face);
      font-size: var(--ds-editor-rail-hint-size);
      line-height: var(--ds-editor-rail-hint-leading);
      letter-spacing: var(--ds-editor-rail-hint-tracking);
      font-weight: var(--ds-editor-rail-hint-weight);
      color: var(--ds-editor-rail-hint-ink);
    }

    /* The popover the engine fills: the block menu, or the URL field. Its
       lines are the popover's own; the form is this component's. */
    .ds-editor__menu {
      min-inline-size: 16rem;
    }

    /* Picking a file, above the address field: two ways to the same block, and
       the one a writer takes is whichever they have. A rule under it, because
       these are alternatives and not a sequence. */
    .ds-editor__pick {
      display: flex;
      flex-direction: column;
      gap: var(--ds-space-2);
      padding: var(--ds-space-2);
      border-block-end: 1px solid var(--ds-border-subtle);
    }

    /* Why the last try failed, said where the try was made. */
    .ds-editor__pick-note {
      --ds-editor-note-size: var(--ds-font-label-size);
      --ds-editor-note-leading: var(--ds-font-label-leading);
      --ds-editor-note-ink: var(--ds-danger-ink);
      margin: 0;
      font-size: var(--ds-editor-note-size);
      line-height: var(--ds-editor-note-leading);
      color: var(--ds-editor-note-ink);
    }

    /* While the files are going up. The button is already disabled; this says
       that the wait is work and not a jam. */
    .ds-editor__pick [data-busy] {
      cursor: progress;
    }

    .ds-editor__url {
      display: flex;
      align-items: center;
      gap: var(--ds-space-2);
      padding: var(--ds-space-2);
    }

    .ds-editor__url-field {
      --ds-editor-url-face: var(--ds-font-body-face);
      --ds-editor-url-size: var(--ds-font-body-size);
      --ds-editor-url-leading: var(--ds-font-body-leading);
      --ds-editor-url-tracking: var(--ds-font-body-tracking);
      --ds-editor-url-weight: var(--ds-font-body-weight);
      --ds-editor-url-tier: var(--ds-size-2);
      --ds-editor-url-ink: var(--ds-text-primary-neutral);
      flex: 1;
      min-inline-size: 12rem;
      min-block-size: var(--ds-editor-url-tier);
      padding: 0 var(--ds-space-3);
      border: 1px solid var(--ds-border);
      border-radius: var(--ds-radius-2);
      background-color: var(--ds-surface-0);
      font-family: var(--ds-editor-url-face);
      font-size: var(--ds-editor-url-size);
      line-height: var(--ds-editor-url-leading);
      letter-spacing: var(--ds-editor-url-tracking);
      font-weight: var(--ds-editor-url-weight);
      color: var(--ds-editor-url-ink);
    }

    .ds-editor__url-field:focus-visible {
      outline: 2px solid var(--ds-focus);
      outline-offset: 1px;
    }

    /* ── The narrow fold ──────────────────────────────────────────────────────
       Under the fold the rail is gone and the text takes the sheet. The width
       is `--ds-break-phone`, written out because a container query cannot read
       a token in its condition; the check holds the two in step. */
    @container ds-editor (width <= 40rem) {
      .ds-editor__main {
        grid-template-columns: minmax(0, 1fr);
      }

      .ds-editor__rail {
        display: none;
      }
    }

    /* ── Full screen ──────────────────────────────────────────────────────────
       A class and not `requestFullscreen`: the browser's API puts the element in
       the top layer, and the popovers and dialogs this editor opens — the block
       menu, an image's window — would land BEHIND it. */
    .ds-editor--fullscreen {
      position: fixed;
      inset: 0;
      z-index: 60;
      padding: var(--ds-space-4);
      background-color: var(--ds-surface-0);
    }

    /* The bar stays reachable while writing at the foot: only the column scrolls. */
    .ds-editor--fullscreen .ds-editor__sheet {
      display: flex;
      flex-direction: column;
      block-size: 100%;
    }

    .ds-editor--fullscreen .ds-editor__main {
      flex: 1;
      min-block-size: 0;
      overflow-y: auto;
    }

    /* And the page underneath holds still. An attribute on the root and not a
       class: a class there would carry no component's name, which is the one
       thing every class in this sheet has to do. */
    :root[data-ds-editor-fullscreen] {
      overflow: hidden;
    }

    /* A line carries its block: dragged, or moved with the arrows when focused. */
    .ds-editor__outline-line {
      cursor: grab;
    }

    .ds-editor__outline-line[data-carried] {
      opacity: 0.5;
    }

    /* ==========================================================================
       Chat

       The CSS behind <Chat>. Private, like every other component's.

       A conversation and the box that continues it: the thread above, scrolling
       inside its own height, the composer at the foot. What the reader said
       stands at the end side in a soft bubble; what was answered stands at the
       start in Prose's own faces and no bubble, since an answer is read like a
       page and a question is read like a note. Under an answer, the steps it
       took: one line each, a tick when done, a link where one leads somewhere.

       ── The height is whatever it stands in ──────────────────────────────────
       A drawer gives it the drawer's; a page gives it a column. The thread takes
       what the composer leaves and scrolls; the composer never moves.
       ========================================================================== */

    .ds-chat {
      display: flex;
      flex-direction: column;
      min-block-size: 0;
      block-size: 100%;
      min-inline-size: 0;
    }

    .ds-chat__thread {
      flex: 1 1 auto;
      min-block-size: 0;
      overflow-y: auto;
      display: flex;
      flex-direction: column;
      gap: var(--ds-space-4);
      padding: var(--ds-space-4) var(--ds-space-4) var(--ds-space-2);
      scroll-behavior: smooth;
    }

    .ds-chat__empty {
      --ds-chat-empty-face: var(--ds-font-body-face);
      --ds-chat-empty-size: var(--ds-font-body-size);
      --ds-chat-empty-leading: var(--ds-font-body-leading);
      --ds-chat-empty-tracking: var(--ds-font-body-tracking);
      --ds-chat-empty-weight: var(--ds-font-body-weight);
      --ds-chat-empty-ink: var(--ds-text-tertiary-neutral);
      display: block;
      margin: auto;
      padding: var(--ds-space-6) var(--ds-space-4);
      max-inline-size: 32ch;
      text-align: center;
      font-family: var(--ds-chat-empty-face);
      font-size: var(--ds-chat-empty-size);
      line-height: var(--ds-chat-empty-leading);
      letter-spacing: var(--ds-chat-empty-tracking);
      font-weight: var(--ds-chat-empty-weight);
      color: var(--ds-chat-empty-ink);
    }

    /* A message: a column of its body and its steps, no wider than most of the
       thread so a line stays a line. */
    .ds-chat__message {
      display: flex;
      flex-direction: column;
      gap: var(--ds-space-2);
      max-inline-size: 90%;
    }

    .ds-chat__message--user {
      align-self: flex-end;
    }

    .ds-chat__message--assistant {
      align-self: flex-start;
      max-inline-size: 100%;
    }

    /* The reader's words in a soft bubble at the end side; the answer bare. */
    .ds-chat__body {
      --ds-chat-body-face: var(--ds-font-body-face);
      --ds-chat-body-size: var(--ds-font-body-size);
      --ds-chat-body-leading: var(--ds-font-body-leading);
      --ds-chat-body-tracking: var(--ds-font-body-tracking);
      --ds-chat-body-weight: var(--ds-font-body-weight);
      --ds-chat-body-ink: var(--ds-text-primary-neutral);
      display: block;
      min-inline-size: 0;
      max-inline-size: none;
      font-family: var(--ds-chat-body-face);
      font-size: var(--ds-chat-body-size);
      line-height: var(--ds-chat-body-leading);
      letter-spacing: var(--ds-chat-body-tracking);
      font-weight: var(--ds-chat-body-weight);
      color: var(--ds-chat-body-ink);
      overflow-wrap: anywhere;
    }

    .ds-chat__message--user .ds-chat__body {
      display: block;
      padding: var(--ds-space-2) var(--ds-space-3);
      border-radius: var(--ds-radius-3);
      border-end-end-radius: var(--ds-radius-1);
      background-color: var(--ds-surface-2);
    }

    /* The steps an answer took: small lines with a tick, the link where one
       leads to a record. */
    .ds-chat__steps {
      display: grid;
      gap: var(--ds-space-1);
      margin: 0;
      padding: 0;
      list-style: none;
    }

    .ds-chat__step {
      --ds-chat-step-face: var(--ds-font-small-face);
      --ds-chat-step-size: var(--ds-font-small-size);
      --ds-chat-step-leading: var(--ds-font-small-leading);
      --ds-chat-step-tracking: var(--ds-font-small-tracking);
      --ds-chat-step-weight: var(--ds-font-small-weight);
      --ds-chat-step-ink: var(--ds-text-secondary-neutral);
      --ds-chat-step-mark: var(--ds-success);
      display: flex;
      align-items: center;
      gap: var(--ds-space-2);
      font-family: var(--ds-chat-step-face);
      font-size: var(--ds-chat-step-size);
      line-height: var(--ds-chat-step-leading);
      letter-spacing: var(--ds-chat-step-tracking);
      font-weight: var(--ds-chat-step-weight);
      color: var(--ds-chat-step-ink);
    }

    .ds-chat__step-mark {
      display: inline-flex;
      flex: none;
      inline-size: 1lh;
      block-size: 1lh;
      color: var(--ds-chat-step-mark);
    }

    .ds-chat__step[data-state="running"] .ds-chat__step-mark {
      --ds-chat-step-mark: var(--ds-text-tertiary-neutral);
    }

    .ds-chat__step-mark > svg {
      inline-size: 100%;
      block-size: 100%;
    }

    .ds-chat__step a {
      color: inherit;
      text-decoration: underline;
      text-underline-offset: 0.15em;
    }

    /* Three dots while the answer is on its way: a change of state, faded. */
    .ds-chat__thinking {
      --ds-chat-thinking-ink: var(--ds-text-tertiary-neutral);
      display: inline-flex;
      gap: var(--ds-space-1);
      align-self: flex-start;
      padding: var(--ds-space-2) 0;
      color: var(--ds-chat-thinking-ink);
    }

    .ds-chat__thinking > span {
      display: block;
      inline-size: var(--ds-space-2);
      block-size: var(--ds-space-2);
      border-radius: var(--ds-radius-full);
      background-color: currentColor;
      animation: ds-chat-dot var(--ds-duration-loop) var(--ds-ease-in-out) infinite;
    }

    .ds-chat__thinking > span:nth-child(2) {
      animation-delay: calc(var(--ds-duration-loop) / 6);
    }

    .ds-chat__thinking > span:nth-child(3) {
      animation-delay: calc(var(--ds-duration-loop) / 3);
    }

    @keyframes ds-chat-dot {
      0%,
      100% {
        opacity: 0.3;
      }

      50% {
        opacity: 1;
      }
    }

    /* The composer: a box that grows with the words up to a few lines, and the
       one button beside it, send or stop. */
    .ds-chat__composer {
      display: flex;
      align-items: flex-end;
      gap: var(--ds-space-2);
      padding: var(--ds-space-3) var(--ds-space-4) var(--ds-space-4);
      border-block-start: 1px solid var(--ds-border-subtle);
    }

    .ds-chat__input {
      --ds-chat-input-face: var(--ds-font-body-face);
      --ds-chat-input-size: var(--ds-font-body-size);
      --ds-chat-input-leading: var(--ds-font-body-leading);
      --ds-chat-input-tracking: var(--ds-font-body-tracking);
      --ds-chat-input-weight: var(--ds-font-body-weight);
      --ds-chat-input-ink: var(--ds-text-primary-neutral);
      --ds-chat-input-tier: var(--ds-size-3);
      --ds-chat-input-ceiling: calc(var(--ds-font-body-leading) * 6);
      flex: 1;
      min-inline-size: 0;
      min-block-size: var(--ds-chat-input-tier);
      max-block-size: var(--ds-chat-input-ceiling);
      padding: calc((var(--ds-chat-input-tier) - var(--ds-chat-input-leading) - 2px) / 2) var(--ds-space-3);
      border: 1px solid var(--ds-border);
      border-radius: var(--ds-radius-3);
      background-color: var(--ds-surface-0);
      font-family: var(--ds-chat-input-face);
      font-size: var(--ds-chat-input-size);
      line-height: var(--ds-chat-input-leading);
      letter-spacing: var(--ds-chat-input-tracking);
      font-weight: var(--ds-chat-input-weight);
      color: var(--ds-chat-input-ink);
      resize: none;
      field-sizing: content;
      transition: border-color var(--ds-duration) var(--ds-ease);
    }

    .ds-chat__input::placeholder {
      --ds-chat-input-placeholder-ink: var(--ds-text-tertiary-neutral);
      color: var(--ds-chat-input-placeholder-ink);
    }

    .ds-chat__input:focus-visible {
      outline: 2px solid var(--ds-focus);
      outline-offset: 1px;
      border-color: var(--ds-border);
    }

    /* ==========================================================================
       Board

       The CSS behind <Board>. Private, like every other component's.

       Columns of cards where a card carried to another column changes a value.
       The lanes are places and not things: they sit on the page ground with a
       name and a count, and the cards are the objects, each a small sheet the
       way a sortable's item is. The gap a card will fall into is a line above
       the card under the pointer, or across the column's floor, the way the
       sortable draws it: a shaded card says the wrong card is moving.
       ========================================================================== */

    .ds-board {
      display: flex;
      flex-direction: column;
      gap: var(--ds-space-3);
      /* The narrowest a lane may be: a card wants a title of a few words in two
         lines at most and a row of badges under it. Beyond the box the lanes
         scroll sideways. */
      --ds-board-column-width: 18rem;
    }

    .ds-board__lanes {
      display: flex;
      align-items: flex-start;
      gap: var(--ds-space-4);
      overflow-x: auto;
      overscroll-behavior-x: contain;
      padding-block-end: var(--ds-space-2);
    }

    .ds-board__column {
      display: flex;
      flex: 0 0 var(--ds-board-column-width);
      flex-direction: column;
      gap: var(--ds-space-2);
      min-inline-size: 0;
    }

    .ds-board__head {
      display: flex;
      align-items: center;
      gap: var(--ds-space-2);
      min-block-size: var(--ds-size-2);
    }

    .ds-board__label {
      --ds-board-label-face: var(--ds-font-label-face);
      --ds-board-label-size: var(--ds-font-label-size);
      --ds-board-label-leading: var(--ds-font-label-leading);
      --ds-board-label-tracking: var(--ds-font-label-tracking);
      --ds-board-label-weight: var(--ds-emphasis-weight);
      font-family: var(--ds-board-label-face);
      font-size: var(--ds-board-label-size);
      line-height: var(--ds-board-label-leading);
      letter-spacing: var(--ds-board-label-tracking);
      font-weight: var(--ds-board-label-weight);
      --ds-board-label-ink: var(--ds-text-primary-neutral);
      color: var(--ds-board-label-ink);
    }

    /* The floor: tall enough to drop on when the column is empty, and the line
       across it when the gap is at the end. */
    .ds-board__cards {
      position: relative;
      display: flex;
      flex-direction: column;
      gap: var(--ds-space-2);
      min-block-size: var(--ds-size-4);
      margin: 0;
      padding: 0;
      list-style: none;
    }

    .ds-board__cards--over::after {
      content: "";
      position: absolute;
      inset-inline: 0;
      inset-block-end: calc(var(--ds-space-2) / -2 - 1px);
      block-size: 0;
      border-block-start: 2px solid var(--ds-border-active);
    }

    .ds-board__card {
      position: relative;
      display: flex;
      flex-direction: column;
      gap: var(--ds-space-2);
      /* One step on all four sides. It was 8 down and 12 across, which is the
         padding of a ROW: a row is read along its length and wants air at the
         ends of it, a card is read as a block and wants the same margin all the
         way round. Owner, 12 September 2026. */
      padding: var(--ds-space-3);
      background-color: var(--ds-surface-1);
      border: 1px solid var(--ds-border);
      border-radius: var(--ds-radius-2);
      transition: opacity var(--ds-duration) var(--ds-ease), background-color var(--ds-duration) var(--ds-ease);
    }

    .ds-board__card--link {
      cursor: pointer;
    }

    .ds-board__card:hover {
      background-color: var(--ds-surface-hover);
    }

    /* The card in the air stays where it was, faded, so nothing under the
       pointer moves out from under it. */
    .ds-board__card--dragging {
      opacity: 0.4;
    }

    .ds-board__card--over::before {
      content: "";
      position: absolute;
      inset-inline: 0;
      inset-block-start: calc(var(--ds-space-2) / -2 - 1px);
      block-size: 0;
      border-block-start: 2px solid var(--ds-border-active);
    }

    /* The box a card is ticked with, at the head beside the grip, always there:
       the box a table row wears is always there, and a box that appears under
       the pointer is a control the reader cannot know exists until they happen
       to find it. Owner's ruling, 10 September 2026, on a board whose boxes were
       "off" until hovered; it was shown on hover here and on the list and the
       card since they were written, and the table never did. */
    .ds-board__select {
      display: flex;
      flex: none;
      align-items: center;
    }

    /* The card it is on, said the way the table and the list say it, and it stays
       said under the pointer. */
    .ds-board__card--selected,
    .ds-board__card--selected:hover {
      background-color: var(--ds-surface-active);
    }

    /* The card's strip of controls: what is done TO the card, kept off the line
       that says what the card IS. The box and the grip stand together at the
       start because they are both hands on the same object, and the menu at the
       end. */
    .ds-board__card-head {
      display: flex;
      align-items: center;
      gap: var(--ds-space-1);
    }

    .ds-board__actions {
      display: inline-flex;
      flex: none;
      margin-inline-start: auto;
    }

    /* The grip is a button, always visible: the keyboard's way in and the one
       sign the cards can be arranged. */
    .ds-board__grip {
      display: inline-flex;
      flex: none;
      align-items: center;
      justify-content: center;
      inline-size: var(--ds-size-1);
      block-size: var(--ds-size-1);
      margin: 0;
      padding: 0;
      background: none;
      border: 0;
      border-radius: var(--ds-radius-1);
      --ds-board-grip-ink: var(--ds-text-tertiary-neutral);
      color: var(--ds-board-grip-ink);
      cursor: grab;
    }

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

    /* A line of its own, under the controls rather than between them. In an 18rem
       lane the head left it 166 of the 288 and a title of six words wrapped inside
       a row of four controls; on its own line it has the card's width. Owner,
       12 September 2026.

       The rung a Card gives a title, and for the reason a Card has it: this title
       WRAPS. The label rung is a one-line rung, which is why the list row's title
       truncates at it, and its 16px of leading under 14px of text reads as two
       lines stuck together the moment a title needs two. */
    .ds-board__title {
      --ds-board-title-face: var(--ds-font-body-face);
      --ds-board-title-size: var(--ds-font-body-size);
      --ds-board-title-leading: var(--ds-font-body-leading);
      --ds-board-title-tracking: var(--ds-font-body-tracking);
      --ds-board-title-weight: var(--ds-emphasis-weight);
      /* A title can be one word nobody can break: an address, an id, a filename. */
      overflow-wrap: anywhere;
      font-family: var(--ds-board-title-face);
      font-size: var(--ds-board-title-size);
      line-height: var(--ds-board-title-leading);
      letter-spacing: var(--ds-board-title-tracking);
      font-weight: var(--ds-board-title-weight);
      --ds-board-title-ink: var(--ds-text-primary-neutral);
      color: var(--ds-board-title-ink);
    }

    .ds-board__link {
      text-decoration: none;
    }

    .ds-board__link:focus-visible {
      outline: 2px solid var(--ds-focus);
      outline-offset: 2px;
      border-radius: var(--ds-radius-1);
    }

    /* Under the title, what the view chose to show: badges, a line, a face. */
    .ds-board__card-body {
      display: flex;
      flex-wrap: wrap;
      align-items: center;
      gap: var(--ds-space-1);
      --ds-board-body-face: var(--ds-font-small-face);
      --ds-board-body-size: var(--ds-font-small-size);
      --ds-board-body-weight: var(--ds-font-small-weight);
      --ds-board-body-leading: var(--ds-font-small-leading);
      --ds-board-body-tracking: var(--ds-font-small-tracking);
      font-family: var(--ds-board-body-face);
      font-size: var(--ds-board-body-size);
      font-weight: var(--ds-board-body-weight);
      line-height: var(--ds-board-body-leading);
      letter-spacing: var(--ds-board-body-tracking);
      --ds-board-body-ink: var(--ds-text-secondary-neutral);
      color: var(--ds-board-body-ink);
    }

    .ds-board__empty {
      --ds-board-empty-face: var(--ds-font-small-face);
      --ds-board-empty-size: var(--ds-font-small-size);
      --ds-board-empty-weight: var(--ds-font-small-weight);
      --ds-board-empty-leading: var(--ds-font-small-leading);
      --ds-board-empty-tracking: var(--ds-font-small-tracking);
      /* The card's own padding: it stands where a card would. */
      padding: var(--ds-space-3);
      font-family: var(--ds-board-empty-face);
      font-size: var(--ds-board-empty-size);
      font-weight: var(--ds-board-empty-weight);
      line-height: var(--ds-board-empty-leading);
      letter-spacing: var(--ds-board-empty-tracking);
      --ds-board-empty-ink: var(--ds-text-tertiary-neutral);
      color: var(--ds-board-empty-ink);
    }

    /* The line that adds a card where it is typed, the list's own. */
    .ds-board__add {
      display: flex;
      align-items: center;
      gap: var(--ds-space-2);
      padding-inline: var(--ds-space-3);
      --ds-board-add-ink: var(--ds-text-secondary-neutral);
      color: var(--ds-board-add-ink);
    }

    .ds-board__add-mark {
      display: inline-flex;
      flex: none;
      inline-size: var(--ds-space-4);
      block-size: var(--ds-space-4);
    }

    /* Read aloud and not seen. */
    .ds-board__said {
      position: absolute;
      inline-size: 1px;
      block-size: 1px;
      overflow: hidden;
      clip-path: inset(50%);
      white-space: nowrap;
    }

    /* ── The card's hue ───────────────────────────────────────────────────────
       A card can carry the colour of the record it stands for, out of the same
       tokens a `Card` uses: the tint of that hue over the card's own ground and
       the same hue as an edge down the leading side. A board whose columns are
       one state and whose tint is another reads both at once.

       The tint is a layer over the surface, so the card's own hover and its
       carried state still move the ground underneath it. */
    .ds-board__card[data-hue] {
      background-image: linear-gradient(var(--ds-board-card-hue-tint), var(--ds-board-card-hue-tint));
      box-shadow: inset var(--ds-space-1) 0 0 0 var(--ds-board-card-hue-edge);
    }

    .ds-board__card[data-hue="red"] { --ds-board-card-hue-tint: var(--ds-hue-red-tint); --ds-board-card-hue-edge: var(--ds-hue-red-edge); }
    .ds-board__card[data-hue="orange"] { --ds-board-card-hue-tint: var(--ds-hue-orange-tint); --ds-board-card-hue-edge: var(--ds-hue-orange-edge); }
    .ds-board__card[data-hue="amber"] { --ds-board-card-hue-tint: var(--ds-hue-amber-tint); --ds-board-card-hue-edge: var(--ds-hue-amber-edge); }
    .ds-board__card[data-hue="yellow"] { --ds-board-card-hue-tint: var(--ds-hue-yellow-tint); --ds-board-card-hue-edge: var(--ds-hue-yellow-edge); }
    .ds-board__card[data-hue="lime"] { --ds-board-card-hue-tint: var(--ds-hue-lime-tint); --ds-board-card-hue-edge: var(--ds-hue-lime-edge); }
    .ds-board__card[data-hue="green"] { --ds-board-card-hue-tint: var(--ds-hue-green-tint); --ds-board-card-hue-edge: var(--ds-hue-green-edge); }
    .ds-board__card[data-hue="teal"] { --ds-board-card-hue-tint: var(--ds-hue-teal-tint); --ds-board-card-hue-edge: var(--ds-hue-teal-edge); }
    .ds-board__card[data-hue="cyan"] { --ds-board-card-hue-tint: var(--ds-hue-cyan-tint); --ds-board-card-hue-edge: var(--ds-hue-cyan-edge); }
    .ds-board__card[data-hue="blue"] { --ds-board-card-hue-tint: var(--ds-hue-blue-tint); --ds-board-card-hue-edge: var(--ds-hue-blue-edge); }
    .ds-board__card[data-hue="indigo"] { --ds-board-card-hue-tint: var(--ds-hue-indigo-tint); --ds-board-card-hue-edge: var(--ds-hue-indigo-edge); }
    .ds-board__card[data-hue="violet"] { --ds-board-card-hue-tint: var(--ds-hue-violet-tint); --ds-board-card-hue-edge: var(--ds-hue-violet-edge); }
    .ds-board__card[data-hue="pink"] { --ds-board-card-hue-tint: var(--ds-hue-pink-tint); --ds-board-card-hue-edge: var(--ds-hue-pink-edge); }

    /* ==========================================================================
       Calendar

       The CSS behind <Calendar>. Private, like every other component's.

       A month of days with items in the days, in a sheet of its own like the
       table and the list. The date field draws a month to choose from; this
       draws one to read, so a day is a box with room for its items rather than
       a square to press, and an item is a pill in its value's hue, the tint
       and the ink a badge of that hue would wear. Six weeks always, the days
       either side of the month quiet, today with an edge.
       ========================================================================== */

    .ds-calendar {
      display: flex;
      flex-direction: column;
      background-color: var(--ds-surface-1);
      border: 1px solid var(--ds-border);
      border-radius: var(--ds-radius-3);
      overflow: hidden;
      /* The shortest a day may be: a number and three pills. */
      --ds-calendar-day-size: 7rem;
    }

    .ds-calendar__head {
      display: flex;
      align-items: center;
      gap: var(--ds-space-2);
      padding: var(--ds-space-3) var(--ds-space-4);
      border-block-end: 1px solid var(--ds-border-subtle);
    }

    .ds-calendar__title {
      --ds-calendar-title-face: var(--ds-font-label-face);
      --ds-calendar-title-size: var(--ds-font-label-size);
      --ds-calendar-title-leading: var(--ds-font-label-leading);
      --ds-calendar-title-tracking: var(--ds-font-label-tracking);
      --ds-calendar-title-weight: var(--ds-emphasis-weight);
      min-inline-size: 10rem;
      margin: 0;
      font-family: var(--ds-calendar-title-face);
      font-size: var(--ds-calendar-title-size);
      line-height: var(--ds-calendar-title-leading);
      letter-spacing: var(--ds-calendar-title-tracking);
      font-weight: var(--ds-calendar-title-weight);
      text-align: center;
      --ds-calendar-title-ink: var(--ds-text-primary-neutral);
      color: var(--ds-calendar-title-ink);
    }

    /* The arrows and Today: a link or a button, dressed the same, on the
       smallest tier of the ramp. */
    .ds-calendar__page,
    .ds-calendar__today,
    .ds-calendar__add {
      display: inline-flex;
      align-items: center;
      justify-content: center;
      gap: var(--ds-space-1);
      min-inline-size: var(--ds-size-1);
      block-size: var(--ds-size-1);
      margin: 0;
      padding: 0;
      background: none;
      border: 0;
      border-radius: var(--ds-radius-1);
      --ds-calendar-page-ink: var(--ds-text-secondary-neutral);
      color: var(--ds-calendar-page-ink);
      text-decoration: none;
      cursor: pointer;
      transition:
        background-color var(--ds-duration) var(--ds-ease),
        color var(--ds-duration) var(--ds-ease),
        opacity var(--ds-duration) var(--ds-ease);
    }

    .ds-calendar__page:hover,
    .ds-calendar__today:hover,
    .ds-calendar__add:hover {
      background-color: var(--ds-surface-hover);
      --ds-calendar-page-ink: var(--ds-text-primary-neutral);
      color: var(--ds-calendar-page-ink);
    }

    .ds-calendar__page:focus-visible,
    .ds-calendar__today:focus-visible,
    .ds-calendar__add:focus-visible,
    .ds-calendar__pill:focus-visible {
      outline: 2px solid var(--ds-focus);
      outline-offset: 2px;
    }

    .ds-calendar__today {
      --ds-calendar-today-face: var(--ds-font-small-face);
      --ds-calendar-today-size: var(--ds-font-small-size);
      --ds-calendar-today-weight: var(--ds-emphasis-weight);
      --ds-calendar-today-leading: var(--ds-font-small-leading);
      --ds-calendar-today-tracking: var(--ds-font-small-tracking);
      margin-inline-start: auto;
      padding-inline: var(--ds-space-2);
      font-family: var(--ds-calendar-today-face);
      font-size: var(--ds-calendar-today-size);
      font-weight: var(--ds-calendar-today-weight);
      line-height: var(--ds-calendar-today-leading);
      letter-spacing: var(--ds-calendar-today-tracking);
    }

    .ds-calendar__count {
      --ds-calendar-count-face: var(--ds-font-small-face);
      --ds-calendar-count-size: var(--ds-font-small-size);
      --ds-calendar-count-weight: var(--ds-font-small-weight);
      --ds-calendar-count-leading: var(--ds-font-small-leading);
      --ds-calendar-count-tracking: var(--ds-font-small-tracking);
      font-family: var(--ds-calendar-count-face);
      font-size: var(--ds-calendar-count-size);
      font-weight: var(--ds-calendar-count-weight);
      line-height: var(--ds-calendar-count-leading);
      letter-spacing: var(--ds-calendar-count-tracking);
      --ds-calendar-count-ink: var(--ds-text-tertiary-neutral);
      color: var(--ds-calendar-count-ink);
    }

    .ds-calendar__grid {
      display: grid;
      grid-template-columns: repeat(7, minmax(0, 1fr));
    }

    .ds-calendar__weekday {
      --ds-calendar-weekday-face: var(--ds-font-small-face);
      --ds-calendar-weekday-size: var(--ds-font-small-size);
      --ds-calendar-weekday-weight: var(--ds-font-small-weight);
      --ds-calendar-weekday-leading: var(--ds-font-small-leading);
      --ds-calendar-weekday-tracking: var(--ds-font-small-tracking);
      padding: var(--ds-space-2);
      font-family: var(--ds-calendar-weekday-face);
      font-size: var(--ds-calendar-weekday-size);
      font-weight: var(--ds-calendar-weekday-weight);
      line-height: var(--ds-calendar-weekday-leading);
      letter-spacing: var(--ds-calendar-weekday-tracking);
      text-align: center;
      --ds-calendar-weekday-ink: var(--ds-text-tertiary-neutral);
      color: var(--ds-calendar-weekday-ink);
      border-block-end: 1px solid var(--ds-border-subtle);
    }

    /* A day: a hairline on two sides, so the grid is lines and not boxes, and
       the sheet's own edge closes the last row and column. */
    .ds-calendar__day {
      display: flex;
      flex-direction: column;
      gap: var(--ds-space-1);
      min-block-size: var(--ds-calendar-day-size);
      padding: var(--ds-space-1) var(--ds-space-2);
      border-block-end: 1px solid var(--ds-border-subtle);
      border-inline-end: 1px solid var(--ds-border-subtle);
      transition: background-color var(--ds-duration) var(--ds-ease);
    }

    .ds-calendar__day:nth-child(7n + 7) {
      border-inline-end: 0;
    }

    .ds-calendar__day:nth-last-child(-n + 7) {
      border-block-end: 0;
    }

    .ds-calendar__day--weekend {
      background-color: var(--ds-surface-2);
    }

    .ds-calendar__day--over {
      background-color: var(--ds-surface-active);
    }

    .ds-calendar__number {
      display: flex;
      align-items: center;
      justify-content: space-between;
      min-block-size: var(--ds-size-1);
    }

    .ds-calendar__figure {
      --ds-calendar-figure-face: var(--ds-font-small-face);
      --ds-calendar-figure-size: var(--ds-font-small-size);
      --ds-calendar-figure-weight: var(--ds-emphasis-weight);
      --ds-calendar-figure-leading: var(--ds-font-small-leading);
      --ds-calendar-figure-tracking: var(--ds-font-small-tracking);
      display: inline-flex;
      align-items: center;
      justify-content: center;
      inline-size: var(--ds-size-1);
      block-size: var(--ds-size-1);
      border: 1px solid transparent;
      border-radius: var(--ds-radius-full);
      font-family: var(--ds-calendar-figure-face);
      font-size: var(--ds-calendar-figure-size);
      font-weight: var(--ds-calendar-figure-weight);
      line-height: var(--ds-calendar-figure-leading);
      letter-spacing: var(--ds-calendar-figure-tracking);
      font-variant-numeric: tabular-nums;
      --ds-calendar-figure-ink: var(--ds-text-primary-neutral);
      color: var(--ds-calendar-figure-ink);
    }

    .ds-calendar__day--outside .ds-calendar__figure {
      --ds-calendar-figure-ink: var(--ds-text-tertiary-neutral);
      color: var(--ds-calendar-figure-ink);
    }

    /* Today: an edge and not a fill, the date field's own rule. */
    .ds-calendar__day--today .ds-calendar__figure {
      border-color: var(--ds-border-active);
    }

    /* The add shows itself under the pointer and to the keyboard, since a plus
       on forty-two days is forty-two pluses. */
    .ds-calendar__add {
      opacity: 0;
    }

    .ds-calendar__day:hover .ds-calendar__add,
    .ds-calendar__add:focus-visible {
      opacity: 1;
    }

    @media (hover: none) {
      .ds-calendar__add {
        opacity: 1;
      }
    }

    .ds-calendar__items {
      display: flex;
      flex-direction: column;
      gap: var(--ds-space-1);
      margin: 0;
      padding: 0;
      list-style: none;
      min-inline-size: 0;
    }

    /* An item: a pill in its hue, the tint and the ink of a badge of that hue,
       and the neutral pair where the value has none. */
    .ds-calendar__item {
      --ds-calendar-item-tint: var(--ds-surface-3);
      --ds-calendar-item-ink: var(--ds-text-primary-neutral);
      display: flex;
      min-inline-size: 0;
      transition: opacity var(--ds-duration) var(--ds-ease);
    }

    .ds-calendar__item--dragging {
      opacity: 0.4;
    }

    .ds-calendar__pill {
      --ds-calendar-pill-face: var(--ds-font-small-face);
      --ds-calendar-pill-size: var(--ds-font-small-size);
      --ds-calendar-pill-weight: var(--ds-emphasis-weight);
      --ds-calendar-pill-leading: var(--ds-font-small-leading);
      --ds-calendar-pill-tracking: var(--ds-font-small-tracking);
      display: block;
      flex: 1;
      min-inline-size: 0;
      padding: var(--ds-space-1) var(--ds-space-2);
      border-radius: var(--ds-radius-1);
      background-color: var(--ds-calendar-item-tint);
      font-family: var(--ds-calendar-pill-face);
      font-size: var(--ds-calendar-pill-size);
      font-weight: var(--ds-calendar-pill-weight);
      line-height: var(--ds-calendar-pill-leading);
      letter-spacing: var(--ds-calendar-pill-tracking);
      color: var(--ds-calendar-item-ink);
      text-decoration: none;
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
      cursor: grab;
      transition: box-shadow var(--ds-duration) var(--ds-ease);
    }

    /* The pill deepens its own tint under the pointer, which is the answer a
       badge already gives: it is a tinted word on a ground, and a line under it
       is a second way of saying pressable that this package does not use. */
    .ds-calendar__pill:hover {
      box-shadow: inset 0 0 0 100px color-mix(in srgb, currentColor 12%, transparent);
    }

    .ds-calendar__item--hue-red {
      --ds-calendar-item-tint: var(--ds-hue-red-tint);
      --ds-calendar-item-ink: var(--ds-hue-red-ink);
    }

    .ds-calendar__item--hue-orange {
      --ds-calendar-item-tint: var(--ds-hue-orange-tint);
      --ds-calendar-item-ink: var(--ds-hue-orange-ink);
    }

    .ds-calendar__item--hue-amber {
      --ds-calendar-item-tint: var(--ds-hue-amber-tint);
      --ds-calendar-item-ink: var(--ds-hue-amber-ink);
    }

    .ds-calendar__item--hue-yellow {
      --ds-calendar-item-tint: var(--ds-hue-yellow-tint);
      --ds-calendar-item-ink: var(--ds-hue-yellow-ink);
    }

    .ds-calendar__item--hue-lime {
      --ds-calendar-item-tint: var(--ds-hue-lime-tint);
      --ds-calendar-item-ink: var(--ds-hue-lime-ink);
    }

    .ds-calendar__item--hue-green {
      --ds-calendar-item-tint: var(--ds-hue-green-tint);
      --ds-calendar-item-ink: var(--ds-hue-green-ink);
    }

    .ds-calendar__item--hue-teal {
      --ds-calendar-item-tint: var(--ds-hue-teal-tint);
      --ds-calendar-item-ink: var(--ds-hue-teal-ink);
    }

    .ds-calendar__item--hue-cyan {
      --ds-calendar-item-tint: var(--ds-hue-cyan-tint);
      --ds-calendar-item-ink: var(--ds-hue-cyan-ink);
    }

    .ds-calendar__item--hue-blue {
      --ds-calendar-item-tint: var(--ds-hue-blue-tint);
      --ds-calendar-item-ink: var(--ds-hue-blue-ink);
    }

    .ds-calendar__item--hue-indigo {
      --ds-calendar-item-tint: var(--ds-hue-indigo-tint);
      --ds-calendar-item-ink: var(--ds-hue-indigo-ink);
    }

    .ds-calendar__item--hue-violet {
      --ds-calendar-item-tint: var(--ds-hue-violet-tint);
      --ds-calendar-item-ink: var(--ds-hue-violet-ink);
    }

    .ds-calendar__item--hue-pink {
      --ds-calendar-item-tint: var(--ds-hue-pink-tint);
      --ds-calendar-item-ink: var(--ds-hue-pink-ink);
    }

    .ds-calendar__more {
      --ds-calendar-more-face: var(--ds-font-small-face);
      --ds-calendar-more-size: var(--ds-font-small-size);
      --ds-calendar-more-weight: var(--ds-font-small-weight);
      --ds-calendar-more-leading: var(--ds-font-small-leading);
      --ds-calendar-more-tracking: var(--ds-font-small-tracking);
      padding-inline: var(--ds-space-2);
      font-family: var(--ds-calendar-more-face);
      font-size: var(--ds-calendar-more-size);
      font-weight: var(--ds-calendar-more-weight);
      line-height: var(--ds-calendar-more-leading);
      letter-spacing: var(--ds-calendar-more-tracking);
      --ds-calendar-more-ink: var(--ds-text-tertiary-neutral);
      color: var(--ds-calendar-more-ink);
    }

    /* Read aloud and not seen. */
    .ds-calendar__said {
      position: absolute;
      inline-size: 1px;
      block-size: 1px;
      overflow: hidden;
      clip-path: inset(50%);
      white-space: nowrap;
    }

    /* ==========================================================================
       Schedule

       The CSS behind <Schedule>. Private, like every other component's.

       Records as bars on an axis of dates, with the links between them drawn:
       what must finish before what can start. The Roadmap draws phases and says
       so in its own comment, because a phase is a step and not a span; this is
       the other half of that sentence, and it is where the dates go.

       ── Everything is a percentage of the window ─────────────────────────────
       The window is the first day drawn to the last, and a bar's start and width
       are that span read as a share of it, exactly as `Chart` works in a 100 by
       100 space. Nothing here measures anything, so the two renderers can each
       arrive at the same markup from the same dates, and a bar keeps its place
       when the box is resized with no script listening.

       ── A link is ONE box with two borders ───────────────────────────────────
       An elbow out of the blocker's end, down the rows, and into the blocked
       one's start. Drawn as a single absolutely positioned box carrying a
       border on one inline edge and one on the block end, which is an L; the
       arrow head is its own pseudo-element at the far corner.

       Where the blocked record starts BEFORE its blocker ends, the box is
       mirrored and the horizontal run goes backwards. That reads as what it is,
       a link pointing the wrong way, and is the one state a schedule with no
       shifting can be left in.

       ── The links lie over the tracks, never over the names ──────────────────
       The body is one grid of two columns and one row per record; the link layer
       is a single box in the second column spanning every row, so a link can
       cross rows without any row needing to know.
       ========================================================================== */

    .ds-schedule {
      --ds-schedule-names: 12rem;
      --ds-schedule-row: var(--ds-size-3);
      --ds-schedule-bar: var(--ds-size-1);
      /* The narrowest a bar is drawn, whatever its dates say. */
      --ds-schedule-least: var(--ds-size-4);
      --ds-schedule-gap: var(--ds-space-1);
      --ds-schedule-pad: var(--ds-space-4);
      --ds-schedule-arrow: var(--ds-space-2);
      /* The gutter the connector turns in, before the bar it arrives at. */
      --ds-schedule-turn: var(--ds-space-4);
      /* What a bar is painted in, and the ink that reads on it.

         A bar is a CARD and it is neutral: a surface, an edge and the page's own
         ink, which is how Notion draws one and it is right for the reason its
         drawing shows. A filled slab is a block of colour; a card with an edge
         reads as an object with a name on it, which is what a record is.

         And it is neutral because it is a thing on a plan, not a thing to press, and
         brand is the colour of the one action a screen wants taken: a plan drawn
         in it asks for twenty. Colour arrives only when the record carries a
         value that has one, which is how Notion does it too: bars take a colour
         from what the view is grouped by, a status or a person, and no grouping
         means no colour. Passing a hue for a record whose colour means nothing
         is four colours a reader tries to read and cannot. */
      --ds-schedule-fill: var(--ds-surface-2);
      --ds-schedule-ink: var(--ds-text-primary-neutral);
      --ds-schedule-link: var(--ds-border-strong);
      /* What a cord turns under the hand, and the halo behind the chosen one.
         Not danger: a cord you are pointing at is not a cord you have destroyed,
         and red on hover is a plan that shouts at every pass of the mouse. */
      --ds-schedule-cord-live: var(--ds-interactive);
      --ds-schedule-cord-halo: var(--ds-interactive-tint);
      /* And what the cross turns under the hand: the one red on this drawing that
         is about an action, arriving at the moment it is about to be taken. */
      --ds-schedule-cut: var(--ds-danger);
      --ds-schedule-cut-ink: var(--ds-danger-on-inverse);
      --ds-schedule-edge: var(--ds-border);
      --ds-schedule-today: var(--ds-danger);
      --ds-schedule-title-ink: var(--ds-text-primary-neutral);
      --ds-schedule-description-ink: var(--ds-text-secondary-neutral);
      --ds-schedule-name-ink: var(--ds-text-primary-neutral);
      --ds-schedule-tick-ink: var(--ds-text-tertiary-neutral);
      --ds-schedule-rule: var(--ds-border-subtle);
      /* The type, under this component's own name, so a reader moving one number
         knows which one they are moving. */
      --ds-schedule-title-face: var(--ds-font-heading-4-face);
      --ds-schedule-title-size: var(--ds-font-heading-4-size);
      --ds-schedule-title-leading: var(--ds-font-heading-4-leading);
      --ds-schedule-title-weight: var(--ds-font-heading-4-weight);
      --ds-schedule-title-tracking: var(--ds-font-heading-4-tracking);
      --ds-schedule-description-face: var(--ds-font-copy-face);
      --ds-schedule-description-size: var(--ds-font-copy-size);
      --ds-schedule-description-leading: var(--ds-font-copy-leading);
      --ds-schedule-description-weight: var(--ds-font-copy-weight);
      --ds-schedule-description-tracking: var(--ds-font-copy-tracking);
      --ds-schedule-name-face: var(--ds-font-body-face);
      --ds-schedule-name-size: var(--ds-font-body-size);
      --ds-schedule-name-leading: var(--ds-font-body-leading);
      --ds-schedule-name-weight: var(--ds-font-body-weight);
      --ds-schedule-name-tracking: var(--ds-font-body-tracking);
      --ds-schedule-label-face: var(--ds-font-small-face);
      --ds-schedule-label-size: var(--ds-font-small-size);
      --ds-schedule-label-leading: var(--ds-font-small-leading);
      --ds-schedule-label-weight: var(--ds-font-small-weight);
      --ds-schedule-label-tracking: var(--ds-font-small-tracking);
      --ds-schedule-tick-face: var(--ds-font-small-face);
      --ds-schedule-tick-size: var(--ds-font-small-size);
      --ds-schedule-tick-leading: var(--ds-font-small-leading);
      --ds-schedule-tick-weight: var(--ds-font-small-weight);
      --ds-schedule-tick-tracking: var(--ds-font-small-tracking);
      display: flex;
      flex-direction: column;
      min-inline-size: 0;
    }

    /* ── The sheet ────────────────────────────────────────────────────────────
       The surface and the edge a table has, for a schedule standing among other
       things on a page. A sheet and never a card, for the reason the roadmap
       gives: a card leads somewhere and this holds controls. */
    .ds-schedule--sheet {
      background: var(--ds-surface-1);
      border: 1px solid var(--ds-border);
      border-radius: var(--ds-radius-3);
      padding: var(--ds-schedule-pad);
      gap: var(--ds-space-3);
    }

    .ds-schedule--bare {
      gap: var(--ds-space-3);
    }

    /* ── The head: whose it is, and the tools ─────────────────────────────── */




    /* ── The scrollport ───────────────────────────────────────────────────────
       A window of months does not fit a phone, and the names have to stay
       readable while the tracks move, so the whole grid scrolls sideways in its
       own box and the name column is stuck to the start edge. */
    .ds-schedule__scroller {
      overflow-x: auto;
      overflow-y: hidden;
      min-inline-size: 0;
    }

    .ds-schedule__grid {
      position: relative;
      display: grid;
      grid-template-columns: var(--ds-schedule-names) minmax(24rem, 1fr);
      align-items: center;
      row-gap: var(--ds-schedule-gap);
      min-inline-size: max-content;
    }

    /* ── The axis ─────────────────────────────────────────────────────────── */

    .ds-schedule__ticks {
      grid-column: 2;
      position: relative;
      display: block;
      block-size: var(--ds-size-3);
      border-block-end: 1px solid var(--ds-border);
    }

    .ds-schedule__tick {
      position: absolute;
      inset-block: 0;
      display: flex;
      align-items: center;
      font-family: var(--ds-schedule-tick-face);
      font-size: var(--ds-schedule-tick-size);
      line-height: var(--ds-schedule-tick-leading);
      font-weight: var(--ds-schedule-tick-weight);
      letter-spacing: var(--ds-schedule-tick-tracking);
      color: var(--ds-schedule-tick-ink);
      white-space: nowrap;
      /* The words stand OFF their own line, not against it. Measured at 4px and
         the date read as stuck to the rule; a step of the scale is what puts a
         label beside a line everywhere else in this package. */
      padding-inline-start: var(--ds-space-2);
      border-inline-start: 1px solid var(--ds-border-subtle);
    }

    /* The floor of the plot, answering the rule under the dates.

       Everything drawn in the tracks ends at the last row's bottom, and measured
       they all end together to the pixel: the today line, the weekend washes, the
       tracks themselves. It still read as a line that had been CUT, because a
       hairline stopping in the middle of a sheet stops at nothing. It stops at a
       rule now, the same one the dates stand on, and the plot is a thing with two
       edges instead of a thing that fades out. */
    .ds-schedule__floor {
      grid-column: 1 / -1;
      block-size: 0;
      border-block-start: 1px solid var(--ds-border);
    }

    /* ── The layer behind and over the tracks ─────────────────────────────────
       One box down the whole track column: the closed days are washed down it
       and the links are drawn across it, so a wash runs the full height and a
       link crosses rows without any row knowing.

       It is taken OUT of the grid and placed against it, and that is the third
       answer rather than a preference. As a grid item spanning `2 / -1` it
       measured zero, because a negative line counts back from the last line of
       the EXPLICIT grid and this grid declares columns and no rows. Written as
       `2 / span <records>` it measured 60 in the right place and pushed every
       track below itself, because a box holding column 2 for four rows leaves
       auto-placement nowhere to put the tracks.

       Absolute against the grid, it fights nothing: the inset is the names
       column and the height of the ticks, both of which are tokens, so this
       still measures nothing at runtime. Read 13 September 2026: the layer sits
       at the first track's top to the last track's bottom, exactly. */
    .ds-schedule__layer {
      position: absolute;
      inset-block-start: calc(var(--ds-size-3) + var(--ds-schedule-gap));
      inset-block-end: 0;
      inset-inline-start: var(--ds-schedule-names);
      inset-inline-end: 0;
      pointer-events: none;
    }

    /* The line under each tick, run down the whole height: without it the bars
       float and a reader cannot put one against a date. Quieter than a bar and
       quieter than a link, because it is the paper and not the drawing. */
    .ds-schedule__rule {
      position: absolute;
      inset-block: 0;
      inline-size: 0;
      border-inline-start: 1px solid var(--ds-schedule-rule);
      pointer-events: none;
    }

    /* A day nobody works: the weekend, and any day the caller calls closed.
       The faintest thing here, and that is the whole point of the order: a
       weekend painted at the strength of a surface read as the structure of the
       drawing and the bars read as what was floating over it.

       The number is 18% and it was measured rather than chosen. Contrast alone
       said the wash was already quieter than a bar, 1.17 against 2.67, and it
       still ran the drawing: two days by the full height, three times over, is
       more paper than every bar put together, and AREA is what the eye weighs.
       At 18% the wash reads about 1.07 against the sheet, under the rules at
       1.20, which puts it where it belongs: the last thing seen, and seen. */
    .ds-schedule__closed {
      position: absolute;
      inset-block: 0;
      background: color-mix(in srgb, var(--ds-surface-3) 18%, transparent);
      pointer-events: none;
    }

    /* A row lays nothing out of its own: its two halves are cells of the grid
       above, so the row is only there to keep them together in the markup. */
    /* A row lays nothing out of its own: its two halves are cells of the grid
       above, so the row exists only to keep them together in the markup. */
    .ds-schedule__row {
      display: contents;
    }

    /* ── The names ────────────────────────────────────────────────────────── */

    .ds-schedule__name {
      grid-column: 1;
      display: flex;
      align-items: center;
      gap: var(--ds-space-2);
      min-inline-size: 0;
      block-size: var(--ds-schedule-row);
      padding-inline-end: var(--ds-space-3);
      font-family: var(--ds-schedule-name-face);
      font-size: var(--ds-schedule-name-size);
      line-height: var(--ds-schedule-name-leading);
      font-weight: var(--ds-schedule-name-weight);
      letter-spacing: var(--ds-schedule-name-tracking);
      color: var(--ds-schedule-name-ink);
    }

    .ds-schedule__name > * {
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
    }

    /* ── The tracks and the bars ──────────────────────────────────────────── */

    .ds-schedule__track {
      grid-column: 2;
      position: relative;
      block-size: var(--ds-schedule-row);
    }

    .ds-schedule__bar {
      position: absolute;
      inset-block-start: calc((var(--ds-schedule-row) - var(--ds-schedule-bar)) / 2);
      block-size: var(--ds-schedule-bar);
      border-radius: var(--ds-radius-1);
      background: var(--ds-schedule-fill);
      border: 1px solid var(--ds-schedule-edge);
      display: flex;
      align-items: center;
      padding-inline: var(--ds-space-2);
      gap: var(--ds-space-1);
      font-family: var(--ds-schedule-label-face);
      font-size: var(--ds-schedule-label-size);
      line-height: var(--ds-schedule-label-leading);
      font-weight: var(--ds-schedule-label-weight);
      letter-spacing: var(--ds-schedule-label-tracking);
      color: var(--ds-schedule-ink);
      box-sizing: border-box;
      /* The bar does NOT clip. It did, to cut its own name, and that is the
         label's own job: `> .ds-schedule__label-wrap` below is a bounded box and
         cuts there. The clip here took the link dots with it, because they sit ON
         the bar's edges and half of each one is outside the bar: what showed was
         two crescents. */
    }

    /* The bar carries the record's name, cut where the bar ends. A bar wide
       enough to hold a word says what it is without the reader crossing back to
       the column of names; a bar too narrow says nothing and the tooltip does.

       The tooltip WRAPS the label, so it is the card's flex child and the label
       is a box inside it: two boxes, and both have to be bounded or the dots
       never come. It carries the component's own class beside the tooltip's,
       and the rule names both, because one class against one class is decided by
       which stylesheet was imported last and that is not a thing to rest on. */
    .ds-schedule__bar > .ds-schedule__label-wrap {
      flex: 1;
      min-inline-size: 0;
      display: block;
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
    }


    /* A bar that leads somewhere answers the hand; one that only says something
       does not. */
    a.ds-schedule__bar,
    button.ds-schedule__bar {
      border: 0;
      cursor: pointer;
      text-decoration: none;
      font: inherit;
      font-family: var(--ds-schedule-label-face);
      font-size: var(--ds-schedule-label-size);
      font-weight: var(--ds-schedule-label-weight);
      letter-spacing: var(--ds-schedule-label-tracking);
      color: var(--ds-schedule-ink);
    }

    /* A hue declares a tint, an ink and an edge, and no hover: the calendar
       darkens an item by laying its own ink over it at a tenth, which works
       whatever the fill underneath is. The same answer here. */
    a.ds-schedule__bar,
    button.ds-schedule__bar {
      transition: box-shadow var(--ds-duration) var(--ds-ease);
    }

    a.ds-schedule__bar:hover,
    button.ds-schedule__bar:hover {
      box-shadow: inset 0 0 0 100px color-mix(in srgb, currentColor 12%, transparent);
    }

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

    /* A record of ONE DAY is a bar like any other, only narrow.

       It was a round mark with its name beside it, which was invented here and
       nowhere else: Notion draws a one-day record as the same card as a ten-day
       one. The invention cost what an invention costs — the mark took the width
       of a DAY, which is a share of the window, so at three weeks across it
       measured 31 against a height of 24 and came out a pill; and the name, with
       nowhere inside to go, ran off the end of the sheet.

       A floor on the width is the whole of what a short record needs. It makes
       the bar wider than its one day, and that is the trade: a day drawn to
       scale over three months is a hairline nobody can hit or read. */
    .ds-schedule__bar {
      min-inline-size: var(--ds-schedule-least);
    }

    /* ── The links ────────────────────────────────────────────────────────── */

    /* The connectors, as one drawing.

       They were three boxes and two borders, which is a right angle, and a right
       angle is what a GRID is made of: the eye read them as another rule of the
       paper rather than as one record waiting on another. Notion curves them and
       that is why. A curve is a relationship.

       So they are one SVG in the space `Chart` already draws in: 100 by 100,
       stretched to the box, so a coordinate is a percentage and neither renderer
       measures anything. `vector-effect: non-scaling-stroke` is what keeps the
       line one pixel while the space around it is stretched unevenly, which is
       the same line `Chart` relies on. */
    .ds-schedule__links {
      position: absolute;
      inset: 0;
      inline-size: 100%;
      block-size: 100%;
      overflow: visible;
      pointer-events: none;
      /* Over the tracks, and that is what makes a cord reachable at all. The
         layer is written before the rows, so the rows paint on top of it: asked
         what was under a point ON a curve, the page answered `ds-schedule__track`
         and every cord was a drawing nobody could touch. A hover that never fires
         is a cross that never appears.

         Only the drawing is lifted, not the whole layer: the closed days and the
         rules stay behind the bars, where paper belongs. */
      z-index: 1;
    }

    .ds-schedule__link {
      fill: none;
      stroke: var(--ds-schedule-link);
      stroke-width: 1;
      vector-effect: non-scaling-stroke;
      transition:
        stroke var(--ds-duration) var(--ds-ease),
        stroke-width var(--ds-duration) var(--ds-ease);
    }

    /* ── Taking a cord away ───────────────────────────────────────────────────
       A cross at the middle of a cord, drawn only while that cord is under the
       hand or the caret. Nothing is parked on the plan at rest; the one control
       appears where the reader is already looking, and one click does it.

       The two attempts before this are both in the history and both instructive.
       A red disc with a cross in it, on every cord always, was the loudest mark on
       the drawing standing for the rarest thing anyone does to a plan ("FA
       CAGARE"). Then there was no mark at all: choose the cord, press Delete,
       which is Notion's own way and which nobody found ("non c'è piu nessun modo
       per toglierlo"). A gesture with no mark is a gesture that does not exist.

       So: a mark, but a quiet one, and only where the hand is. Red arrives at the
       last moment, on the cross itself, because that is when the reader is about
       to destroy something and not before. */
    .ds-schedule__cord--cuttable {
      pointer-events: auto;
      cursor: pointer;
    }

    .ds-schedule__grab {
      fill: none;
      stroke: transparent;
      stroke-width: var(--ds-size-1);
      vector-effect: non-scaling-stroke;
    }

    /* The cord and its cross come up together, which no selector can say: one is
       a shape inside the drawing and the other is markup beside it. The engine
       marks the pair, off the two records they both name. */
    .ds-schedule__cord--cuttable[data-ds-schedule-near] .ds-schedule__link,
    .ds-schedule__cord--cuttable:focus-visible .ds-schedule__link {
      stroke: var(--ds-schedule-cord-live);
      stroke-width: 2;
    }

    /* The browser draws NO ring here, in any state of focus. A focus ring on a
       `<g>` is a rectangle around the bounding box of a CURVE: it reaches from one
       bar's edge to the other's and from one row to the next, and what it encloses
       is mostly empty plan. Chrome paints that ring white over blue in a dark
       theme, which is a large blue slab appearing on a click and is what the owner
       photographed. The cord shows focus as ITSELF, brought forward with its halo,
       which is the rule the rest of this stylesheet follows. */
    .ds-schedule__cord--cuttable:focus,
    .ds-schedule__cord--cuttable:focus-visible {
      outline: none;
    }

    .ds-schedule__cord--cuttable:focus-visible .ds-schedule__grab {
      stroke: var(--ds-schedule-cord-halo);
      stroke-width: var(--ds-space-3);
    }

    .ds-schedule__cut {
      position: absolute;
      /* Placed at the middle of the curve and centred on it: the percentages set
         its top left corner, so half of itself back in both directions is what
         puts the middle of the cross ON the line. */
      translate: -50% -50%;
      z-index: 3;
      inline-size: var(--ds-space-4);
      block-size: var(--ds-space-4);
      display: grid;
      place-items: center;
      padding: 0;
      border: var(--ds-space-px) solid var(--ds-schedule-cord-live);
      border-radius: var(--ds-radius-full);
      background: var(--ds-surface-1);
      color: var(--ds-schedule-cord-live);
      cursor: pointer;
      -webkit-appearance: none;
      appearance: none;
      /* Not there until its cord is. `visibility` and not `display`, so the box
         keeps its place and nothing reflows when it arrives. */
      opacity: 0;
      visibility: hidden;
      pointer-events: none;
      transition:
        opacity var(--ds-duration) var(--ds-ease),
        background var(--ds-duration) var(--ds-ease),
        border-color var(--ds-duration) var(--ds-ease),
        color var(--ds-duration) var(--ds-ease);
    }

    .ds-schedule__cut[data-ds-schedule-near],
    .ds-schedule__cut:focus-visible {
      opacity: 1;
      visibility: visible;
      pointer-events: auto;
    }

    .ds-schedule__cut svg {
      inline-size: 100%;
      block-size: 100%;
      fill: none;
      stroke: currentColor;
      stroke-width: 1.5;
      stroke-linecap: round;
      /* The button is what a click is ON, not whichever arm of the cross the
         pointer happened to be over. */
      pointer-events: none;
    }

    /* And red, here, at the moment the hand is on the thing that destroys and not
       a moment earlier. */
    .ds-schedule__cut:hover,
    .ds-schedule__cut:focus-visible {
      background: var(--ds-schedule-cut);
      border-color: var(--ds-schedule-cut);
      color: var(--ds-schedule-cut-ink);
    }

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

    /* The dot where a cord arrives, and it is a LINE of no length rather than a
       circle: a `<circle>` in a box stretched to the width of a plan is an
       ellipse. A zero length stroke with a round cap that does not scale is four
       pixels of dot at any width. */
    .ds-schedule__head-mark {
      stroke: var(--ds-schedule-link);
      stroke-width: 4;
      stroke-linecap: round;
      vector-effect: non-scaling-stroke;
      transition: stroke var(--ds-duration) var(--ds-ease);
    }

    /* ── Today ────────────────────────────────────────────────────────────────
       The one mark that is allowed to be loud, because it is the only thing on
       the drawing that is not a record: where the reader is standing.

       TWO pieces in two bands, and that is the whole of what makes it look put
       there rather than left there. The head sits in the strip of dates with its
       bottom flush to the strip's own edge; the line runs from that edge down the
       tracks. One piece straddling the edge, which is what this was, is a dot
       aligned to nothing: half of it in the dates and half in the plan, touching
       neither (owner, 13 September 2026: "è disallineata"). */
    .ds-schedule__now {
      position: absolute;
      /* Centred ON the rule, not resting on top of it. Sitting above it the head
         was whole and the grey rule ran between it and the start of its own line,
         so the line looked like it came out from under the paper rather than out
         of the head. Half the head either side of the rule and the rule passes
         behind it: the rule is the strip's own border, a pixel tall under the
         strip's content, so its middle is half a pixel past that. */
      inset-block-end: calc(var(--ds-space-2) / -2 - 0.5px);
      inline-size: var(--ds-space-2);
      block-size: var(--ds-space-2);
      /* Half a border to the right, and it is the last half pixel of this mark.
         A rule here is a `border-inline-start` on a box of no width, so its stroke
         does not sit ON the day, it sits in the pixel AFTER it: measured, the line
         occupied 609.6 to 610.6 while the head was centred on 609.6, so the head
         was centred on the line's left EDGE and read a hair left of it. Centred on
         the stroke, it agrees with the grey rules too, which are drawn the same
         way. */
      translate: calc(-50% + 0.5px) 0;
      border-radius: var(--ds-radius-full);
      background: var(--ds-schedule-today);
      pointer-events: none;
    }

    /* And the line starts at that same rule, not at the top of the tracks. The
       layer it lives in begins a row-gap below the dates, so the line began four
       pixels under the head with nothing between them: measured, head bottom 316,
       rule 317, line top 320. It reaches back up by exactly the gap and the rule
       it has to cross. */
    .ds-schedule__today {
      position: absolute;
      inset-block-start: calc((var(--ds-schedule-gap) + 1px) * -1);
      inset-block-end: 0;
      inline-size: 0;
      border-inline-start: 1px solid var(--ds-schedule-today);
      pointer-events: none;
    }

    /* ── Adding on a day ──────────────────────────────────────────────────────
       The plus follows the pointer along the empty stretch of a row and stands on
       the day under it, so what is asked for is the day the reader is pointing at.
       It is not there until the engine has a day to give it, and it leaves the
       moment the pointer is over a bar: a plus over an existing record is an offer
       to do something this gesture cannot do. */
    .ds-schedule__add {
      position: absolute;
      inset-block-start: 50%;
      translate: -50% -50%;
      inline-size: var(--ds-size-1);
      block-size: var(--ds-size-1);
      display: grid;
      place-items: center;
      padding: 0;
      border: var(--ds-space-px) dashed var(--ds-schedule-edge);
      border-radius: var(--ds-radius-1);
      background: var(--ds-surface-1);
      color: var(--ds-schedule-tick-ink);
      cursor: pointer;
      -webkit-appearance: none;
      appearance: none;
      opacity: 0;
      visibility: hidden;
      transition:
        opacity var(--ds-duration) var(--ds-ease),
        color var(--ds-duration) var(--ds-ease),
        border-color var(--ds-duration) var(--ds-ease);
    }

    /* The button is what a click is ON, not the mark inside it. */
    .ds-schedule__add svg {
      pointer-events: none;
    }

    .ds-schedule__add[data-ds-schedule-at] {
      opacity: 1;
      visibility: visible;
    }

    .ds-schedule__add:hover,
    .ds-schedule__add:focus-visible {
      border-style: solid;
      border-color: var(--ds-schedule-cord-live);
      color: var(--ds-schedule-ink);
    }

    .ds-schedule__add:focus-visible {
      outline: 2px solid var(--ds-focus);
      outline-offset: 2px;
      opacity: 1;
      visibility: visible;
    }

    /* ── The box beside a name ────────────────────────────────────────────────
       Where `List` and `Table` put theirs: at the head of the row's own line, so
       a reader ticking several records runs down one column and not across the
       drawing. */
    .ds-schedule__pick {
      flex: none;
    }

    /* ── Nothing to draw ──────────────────────────────────────────────────── */

    .ds-schedule__empty {
      grid-column: 1 / -1;
    }

    /* ── The hand ─────────────────────────────────────────────────────────────
       A schedule given a handler answers the hand, and says so before it is
       touched: the bar takes the grab cursor, and its two ends take the one that
       means a length. The grips are the ends themselves, wide enough to hit at
       the target size the access tokens set and drawn as nothing, because a bar
       with two visible handles on it is a bar with three things to read. */
    /* A plan that answers the hand is not prose. Every gesture on it begins with
       the pointer going down and travelling, which is also how a browser is told
       to SELECT TEXT: a drag that starts on a bar and crosses two more painted the
       rows in the selection colour, and a click that travelled a pixel left a
       ragged highlight behind. The names are still readable by a reader who wants
       them: the column of names outside the tracks is untouched. */
    .ds-schedule__grid--grabbable {
      user-select: none;
      -webkit-user-select: none;
    }

    .ds-schedule__grid--grabbable .ds-schedule__name {
      user-select: text;
      -webkit-user-select: text;
    }

    .ds-schedule__grid--grabbable .ds-schedule__bar {
      cursor: grab;
      touch-action: none;
      /* The browser carries neither the element nor the words in it while a hand
         is on it: a native link drag steals the pointer and a text selection
         turns a slow drag into a highlight. */
      user-select: none;
      -webkit-user-drag: none;
    }

    .ds-schedule__grid--grabbable .ds-schedule__bar:active {
      cursor: grabbing;
    }

    /* The ends, to take. The box is the target and the line inside it is what a
       reader sees: 12px of it was under the smallest target WCAG allows and
       invisible besides, so the gesture existed and nobody could find it. The
       target is a tier of the ramp; the line is drawn on the way past and while
       the hand is down. */
    .ds-schedule__grip {
      position: absolute;
      inset-block: 0;
      inline-size: var(--ds-size-1);
      cursor: ew-resize;
      display: flex;
      align-items: center;
      justify-content: center;
    }

    .ds-schedule__grip::after {
      content: "";
      display: block;
      inline-size: var(--ds-space-px);
      block-size: 50%;
      border-radius: var(--ds-radius-full);
      background: var(--ds-schedule-ink);
      outline: var(--ds-space-px) solid var(--ds-schedule-ink);
      opacity: 0;
      transition: opacity var(--ds-duration) var(--ds-ease);
    }

    .ds-schedule__bar:hover .ds-schedule__grip::after,
    .ds-schedule__bar:focus-visible .ds-schedule__grip::after,
    .ds-schedule__bar[data-ds-schedule-held] .ds-schedule__grip::after {
      opacity: 1;
    }

    .ds-schedule__grip--start {
      inset-inline-start: 0;
    }

    .ds-schedule__grip--end {
      inset-inline-end: 0;
    }

    /* The one grip that IS drawn, because pulling a line out of a bar is not a
       gesture anybody guesses: a mark off the bar's end, shown on the way past
       and to the keyboard. */
    /* Inside the bar's own end, because the bar clips what leaves it. */
    /* ── Making a cord ────────────────────────────────────────────────────────
       A dot at each end of a bar, always drawn: take one and pull it onto another
       bar to say the first has to finish before the second starts. Which end you
       took says which way the cord runs.

       ON the edge, centred, because that point is exactly where a cord leaves and
       arrives: the bar's own left edge is `x(start)` and a cord into it ends at
       `x(start)`, the same number from the same line of arithmetic. Beside the
       bar instead, which is where these stood, the cord met the dot's SIDE and
       the join read as two things that happen to touch ("la linea si collega a
       quelli, altrimenti è tutto un bordello").

       Small, and that is the point of it. This is a mark on a thread, drawn on
       every end of every row: at the size of a control it is a plan wearing its
       own controls. The box around it is bigger than the ring, so the hand has
       something to catch that the eye does not have to look at.

       The box is 16 and not the 24 a target usually takes, because half of it
       lies over the bar and the resize grip is under there: 24 would eat half
       that grip. What buys the difference back is that the dot is never hidden —
       the version that WAS hidden until hover is the one that could not be hit. */
    .ds-schedule__link-grip {
      position: absolute;
      inset-block-start: 50%;
      inline-size: var(--ds-space-4);
      block-size: var(--ds-space-4);
      display: grid;
      place-items: center;
      /* `grab`, not `crosshair`. A crosshair is a "+" under the hand and it reads
         as "now pick a point, then pick another": the owner read it exactly that
         way. This dot is taken hold of and pulled. */
      cursor: grab;
      /* Over the bar's own grips and over the cords, so the pointer meets the dot
         and not the handle for a length underneath it. */
      z-index: 2;
    }

    /* The mark, which is not the target. */
    .ds-schedule__link-grip::after {
      content: "";
      display: block;
      inline-size: var(--ds-space-2);
      block-size: var(--ds-space-2);
      box-sizing: border-box;
      border-radius: var(--ds-radius-full);
      background: var(--ds-surface-1);
      border: var(--ds-space-px) solid var(--ds-schedule-link);
      transition:
        background var(--ds-duration) var(--ds-ease),
        border-color var(--ds-duration) var(--ds-ease),
        scale var(--ds-duration) var(--ds-ease);
    }

    .ds-schedule__link-grip--start {
      inset-inline-start: 0;
      translate: -50% -50%;
    }

    .ds-schedule__link-grip--end {
      inset-inline-end: 0;
      translate: 50% -50%;
    }

    .ds-schedule__bar[data-ds-schedule-held] .ds-schedule__link-grip,
    .ds-schedule__link-grip:active {
      cursor: grabbing;
    }

    /* The rope, while the hand is pulling it out of a dot. It is the cord that is
       about to exist, drawn with the same elbow, so what the reader is dragging is
       the shape they will get. Dashed, because it is not a cord yet. */
    .ds-schedule__rope {
      fill: none;
      stroke: var(--ds-schedule-cord-live);
      stroke-width: 2;
      stroke-linecap: round;
      stroke-dasharray: 4 4;
      vector-effect: non-scaling-stroke;
      pointer-events: none;
      opacity: 0;
    }

    .ds-schedule__rope[data-ds-schedule-drawn] {
      opacity: 1;
    }

    .ds-schedule__link-grip:hover::after,
    .ds-schedule__link-grip:focus-visible::after {
      background: var(--ds-schedule-cord-live);
      border-color: var(--ds-schedule-cord-live);
      scale: 1.5;
    }

    .ds-schedule__bar[data-ds-schedule-held] {
      --ds-schedule-edge: var(--ds-border-active);
      outline: 2px solid var(--ds-focus);
      outline-offset: 2px;
      cursor: grabbing;
    }

    /* The bar a line is being pulled onto, while the hand is still down. */
    .ds-schedule__bar[data-ds-schedule-over] {
      outline: 2px solid var(--ds-focus);
      outline-offset: 2px;
    }

    @media (prefers-reduced-motion: reduce) {
      .ds-schedule__link-grip,
      .ds-schedule__grip::after,
      .ds-schedule__link-grip::after,
      .ds-schedule__cut,
      .ds-schedule__head-mark,
      .ds-schedule__link,
      a.ds-schedule__bar,
      button.ds-schedule__bar {
        transition: none;
      }
    }

    /* ==========================================================================
       Capture

       The CSS behind <Capture>. Private, like every other component's.

       Two panes: the notes, narrow, and the editor taking the rest; one under
       the other where the room is narrow. The notes are a column of cards, one
       per note, each the whole target: the note's first picture small before
       two lines of its words, and the open one carrying the interactive line
       and the selected wash. The editor is a column of the form's parts, and
       the open note's pictures stand in a row of squares, each with the way to
       take it away in its corner.
       ========================================================================== */

    .ds-capture {
      display: block;
      container-type: inline-size;
      /* The notes' pane. 320 holds the card: the padding, the line, the 40 of
         picture and the gutter leave about thirty-four characters a line at the
         label's size, and sixty-eight over the two. */
      --ds-capture-notes-width: 20rem;
    }

    .ds-capture__panes {
      display: grid;
      grid-template-columns: minmax(0, var(--ds-capture-notes-width)) minmax(0, 1fr);
      gap: var(--ds-space-4);
      align-items: start;
    }

    /* Under the phone break the panes stand one under the other. The width is
       `--ds-break-phone`, written out because a container query cannot read a
       token in its condition; the check holds the two in step. */
    @container (width <= 40rem) {
      .ds-capture__panes {
        grid-template-columns: minmax(0, 1fr);
      }
    }

    /* The pane. The New button stands over the column where `action` has taken
       the title's slot, on the same step that separates the cards. */
    .ds-capture__notes {
      display: flex;
      flex-direction: column;
      gap: var(--ds-space-2);
    }

    .ds-capture__note-list {
      display: flex;
      flex-direction: column;
      gap: var(--ds-space-2);
      margin: 0;
      padding: 0;
      list-style: none;
    }

    /* One note, and the whole card is the target: an `a` where the note has an
       address and a `button` where the press is the caller's. Both are dressed
       here because a button brings its own face, its own grey and its own
       centred text from the user agent, and a bordered box that is one tag on
       one page and the other on the next has to be border-box or the two come
       out different widths in the same slot. */
    .ds-capture__note {
      box-sizing: border-box;
      display: flex;
      align-items: center;
      gap: var(--ds-space-3);
      inline-size: 100%;
      padding: var(--ds-space-3);
      font: inherit;
      text-align: start;
      text-decoration: none;
      color: inherit;
      background-color: var(--ds-surface-1);
      border: 1px solid var(--ds-border);
      border-radius: var(--ds-radius-3);
      cursor: pointer;
      transition:
        background-color var(--ds-duration) var(--ds-ease),
        border-color var(--ds-duration) var(--ds-ease);
    }

    .ds-capture__note:hover {
      background-color: var(--ds-surface-2);
    }

    /* The ring goes round the card, since the card is what the keyboard has
       arrived on, and outside it, since the cards do not touch. */
    .ds-capture__note:focus-visible {
      outline: 2px solid var(--ds-focus);
      outline-offset: 2px;
    }

    /* The one that is open: the line carries it and the wash backs it up, the
       card's own chosen state. The hover twin is not a duplicate: a plain
       :hover outweighs a modifier, so without it, pointing at the open note
       repainted it and it read as no longer open. */
    .ds-capture__note--current,
    .ds-capture__note--current:hover {
      border-color: var(--ds-border-active);
      background-color: var(--ds-surface-active);
    }

    /* The note itself, and the card's name with it. Primary, because it is the
       only line on the card: a column of secondary text with nothing primary in
       it says nothing about which note is which. */
    .ds-capture__preview {
      /* This component's own, pointed at the role. The label's size and not the
         small one: this line is read rather than glanced at. */
      --ds-capture-preview-face: var(--ds-font-label-face);
      --ds-capture-preview-size: var(--ds-font-label-size);
      --ds-capture-preview-weight: var(--ds-font-label-weight);
      --ds-capture-preview-leading: var(--ds-font-label-leading);
      --ds-capture-preview-tracking: var(--ds-font-label-tracking);
      font-family: var(--ds-capture-preview-face);
      font-size: var(--ds-capture-preview-size);
      font-weight: var(--ds-capture-preview-weight);
      line-height: var(--ds-capture-preview-leading);
      letter-spacing: var(--ds-capture-preview-tracking);
      --ds-capture-preview-ink: var(--ds-text-primary-neutral);
      color: var(--ds-capture-preview-ink);
      /* Two lines, so a column of notes fits a screen and every card is the same
         height whatever the note says. One line loses the sentence that tells
         two notes apart; three turns the column into a page of prose. */
      --ds-capture-preview-lines: 2;
      display: -webkit-box;
      -webkit-box-orient: vertical;
      -webkit-line-clamp: var(--ds-capture-preview-lines);
      line-clamp: var(--ds-capture-preview-lines);
      overflow: hidden;
      flex: 1;
      min-inline-size: 0;
    }

    .ds-capture__thumb {
      display: block;
      flex: none;
      inline-size: var(--ds-size-3);
      block-size: var(--ds-size-3);
      object-fit: cover;
      border-radius: var(--ds-radius-1);
    }

    .ds-capture__editor {
      display: block;
    }

    .ds-capture__pictures {
      display: grid;
      grid-template-columns: repeat(auto-fill, minmax(var(--ds-size-6), 1fr));
      gap: var(--ds-space-2);
      margin: 0;
      padding: 0;
      list-style: none;
    }

    .ds-capture__picture {
      position: relative;
      display: block;
      aspect-ratio: 1;
      overflow: hidden;
      border: 1px solid var(--ds-border);
      border-radius: var(--ds-radius-2);
      background-color: var(--ds-surface-2);
    }

    .ds-capture__picture img {
      display: block;
      inline-size: 100%;
      block-size: 100%;
      object-fit: cover;
    }

    .ds-capture__picture-act {
      position: absolute;
      inset-block-start: var(--ds-space-1);
      inset-inline-end: var(--ds-space-1);
    }

    /* ==========================================================================
       Card

       The CSS behind <Card> and the parts it is made of. Private, like every
       other component's.

       One thing, boxed: a piece of content with an edge round it, so a page of
       them reads as a set rather than as a run of paragraphs. An article in a
       list, a plan on a pricing page, a summary on a dashboard.

       ── There is no shadow here ───────────────────────────────────────────────
       A card is a surface and a border, and never a drop shadow. A shadow is a
       claim about where the light comes from, and a system that makes it once has
       to keep making it in every theme, on every ground, at every size. The
       border says the same thing and answers to the palette.

       ── The whole card is the link, and there is still only one ───────────────
       Where a card leads somewhere, the real link is on its title, and the card
       stretches that one link over itself with a pseudo-element. So the markup
       holds a single link with the title as its name, the reader can hit anything
       from the picture to the corner, and a second link inside the card is still
       its own link rather than a hole in the first one.
       ========================================================================== */

    .ds-card {
      /* This component's own. The role says what the words are; this says that
         THIS component wants a heavier hand than the role has, which is a
         decision the component makes and nobody else. Pointed at the semantic
         rather than holding a number, so moving the system moves every component
         that has not been moved on purpose, and moving one moves one. */
      /* The ground for the stretched link, and the reason the media can reach the
         edge without leaving the corners. */
      position: relative;
      display: flex;
      flex-direction: column;
      min-inline-size: 0;
      /* Three, and each earns its place. `normal` is a card of prose, `tight` a
         card of rows, where the padding of each row is the padding of the card
         and two together is a gutter nobody asked for, and `none` a card filled
         by one thing: a table, a picture, a map. */
      --ds-card-pad: var(--ds-space-4);
      background-color: var(--ds-surface-1);
      /* `--ds-border` says it in its own note: the line around a resting card.
         The subtle one is the same colour as this surface, which is right for a
         divider drawn inside a box and leaves the box itself with no edge at all
         the moment it is put on a panel of its own colour. */
      border: 1px solid var(--ds-border);
      /* 12 and not 16. The bigger step is for a sheet that takes half the screen;
         a card in a grid of six reads as a soft blob at that radius, and the row
         of them loses the straight line down the side that made it a grid. */
      border-radius: var(--ds-radius-3);
      overflow: hidden;
      transition: background-color var(--ds-duration) var(--ds-ease);
    }

    .ds-card--tight {
      --ds-card-pad: var(--ds-space-3);
    }

    /* ── Bare ─────────────────────────────────────────────────────────────────
       The card without its sheet, for a grid of covers: a border on a grid of
       pictures competes with the picture instead of containing it, which is the
       argument the first consumer wrote over its own post card. The parts keep
       their order, the seams become one gap, and the media keeps the corners
       the sheet no longer draws for it. The stretched link still works, because
       its ground is the card's position, not its box. */
    .ds-card--bare {
      --ds-card-pad: 0;
      gap: var(--ds-space-4);
      background-color: transparent;
      border: 0;
      border-radius: 0;
      overflow: visible;
    }

    .ds-card--bare .ds-card__media {
      border-radius: var(--ds-radius-2);
      overflow: hidden;
    }

    .ds-card--none {
      --ds-card-pad: 0;
    }

    .ds-card__header,
    .ds-card__body,
    .ds-card__footer {
      padding: var(--ds-card-pad);
    }

    /* Stacked, they would pay the padding twice over at every seam, so the ones
       that follow keep the sides and lose the top. */
    .ds-card__header + .ds-card__body,
    .ds-card__header + .ds-card__footer,
    .ds-card__body + .ds-card__footer {
      padding-block-start: 0;
    }

    .ds-card__header {
      display: flex;
      align-items: flex-start;
      gap: var(--ds-space-3);
    }

    /* The face before the words, at the size a face wants: the header's other
       slot for a picture, beside `__icon`, which is a mark the height of the
       title's line. It centres on the block of words rather than on the first
       line, since a face beside a name and a handle belongs to both. */
    .ds-card__avatar {
      display: flex;
      flex: none;
      align-self: center;
    }

    /* The column of title and description, next to whatever the header keeps at
       its end: an avatar, a badge, a menu. */
    .ds-card__heading {
      display: flex;
      flex: 1;
      flex-direction: column;
      /* One step, and one step only. The leading of the two lines already holds
         them apart by about five pixels; nothing at all reads as a title with a
         caption stuck to its underside, and a full step reads as two paragraphs
         that happen to be near each other. */
      gap: var(--ds-space-1);
      min-inline-size: 0;
    }

    /* The first question, answered before the title is read: an event's date,
       a thing's kind. The metadata voice, in the lesser ink, so it introduces
       the title instead of competing with it. */
    .ds-card__eyebrow {
      --ds-card-eyebrow-face: var(--ds-font-micro-face);
      --ds-card-eyebrow-size: var(--ds-font-micro-size);
      --ds-card-eyebrow-weight: var(--ds-font-micro-weight);
      --ds-card-eyebrow-leading: var(--ds-font-micro-leading);
      --ds-card-eyebrow-tracking: var(--ds-font-micro-tracking);
      --ds-card-eyebrow-ink: var(--ds-text-secondary-neutral);
      margin: 0;
      font-family: var(--ds-card-eyebrow-face);
      font-size: var(--ds-card-eyebrow-size);
      font-weight: var(--ds-card-eyebrow-weight);
      line-height: var(--ds-card-eyebrow-leading);
      letter-spacing: var(--ds-card-eyebrow-tracking);
      text-transform: uppercase;
      color: var(--ds-card-eyebrow-ink);
    }

    /* The mark and the name on one line, inside the column: a title under an
       eyebrow is still the line the mark belongs to, and a mark beside the whole
       header would sit level with the eyebrow instead. Top-aligned rather than
       centred, so a title that wraps keeps the mark on its first line. */
    .ds-card__name {
      display: flex;
      align-items: flex-start;
      gap: var(--ds-space-2);
      min-inline-size: 0;
    }

    /* The mark before the name. It takes the TITLE's size and leading, declared
       here again because the title's own tokens live on the title and a sibling
       cannot read them, so that `1lh` is the title's line and not the card's:
       the sidebar's rule, that a mark in a row is a drawing on its own grid
       sitting beside the words, at the height of the line it stands next to.
       Passed unsized, and the box sizes it. */
    .ds-card__icon {
      --ds-card-icon-size: var(--ds-font-body-size);
      --ds-card-icon-leading: var(--ds-font-body-leading);
      --ds-card-icon-ink: var(--ds-text-primary-neutral);
      display: inline-flex;
      flex: none;
      font-size: var(--ds-card-icon-size);
      line-height: var(--ds-card-icon-leading);
      inline-size: 1lh;
      block-size: 1lh;
      color: var(--ds-card-icon-ink);
    }

    .ds-card__icon > svg {
      inline-size: 100%;
      block-size: 100%;
    }

    .ds-card__title {
      --ds-card-title-weight: var(--ds-emphasis-weight);
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-card-title-face: var(--ds-font-body-face);
      --ds-card-title-tracking: var(--ds-font-body-tracking);
      --ds-card-title-size: var(--ds-font-body-size);
      --ds-card-title-leading: var(--ds-font-body-leading);
      margin: 0;
      font-family: var(--ds-card-title-face);
      font-size: var(--ds-card-title-size);
      letter-spacing: var(--ds-card-title-tracking);
      line-height: var(--ds-card-title-leading);
      font-weight: var(--ds-card-title-weight);
      --ds-card-title-ink: var(--ds-text-primary-neutral);
      color: var(--ds-card-title-ink);
    }

    /* No gap of its own between the two: they carry their own leading, and a gap
       on top of it reads as two separate things rather than a title and what it
       says. */
    .ds-card__description {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-card-description-face: var(--ds-font-label-face);
      --ds-card-description-tracking: var(--ds-font-label-tracking);
      --ds-card-description-size: var(--ds-font-label-size);
      --ds-card-description-weight: var(--ds-font-label-weight);
      --ds-card-description-leading: var(--ds-font-label-leading);
      /* 14 and not 12. This line is read, not glanced at: 12 is the size of a
         count in a badge or a unit after a number, and a card whose second line is
         that small reads as a title with a caption stuck under it. */
      font-family: var(--ds-card-description-face);
      font-size: var(--ds-card-description-size);
      letter-spacing: var(--ds-card-description-tracking);
      font-weight: var(--ds-card-description-weight);
      line-height: var(--ds-card-description-leading);
      --ds-card-description-ink: var(--ds-text-secondary-neutral);
      color: var(--ds-card-description-ink);
    }

    /* Cut to a fixed number of lines, so a grid of cards keeps its rows level
       whatever the copy does. Off unless a number is given: clamping by default
       would hide the end of a sentence nobody asked to shorten. */
    .ds-card__description[data-lines] {
      display: -webkit-box;
      -webkit-box-orient: vertical;
      -webkit-line-clamp: var(--ds-card-lines);
      line-clamp: var(--ds-card-lines);
      overflow: hidden;
    }

    .ds-card__body {
      flex: 1;
      min-inline-size: 0;
      --ds-card-body-ink: var(--ds-text-primary-neutral);
      color: var(--ds-card-body-ink);
    }

    /* What you do with the card, at the bottom, in reading order. Wraps rather
       than squeezing, because two buttons at a narrow width is two lines.

       Pushed to the bottom of the card and not left where the text stops. In a
       grid the cards are one height and the titles are not one length, so a footer
       that follows the text lands at a different level in every card and the row
       reads as three cards that were laid out separately. */
    .ds-card__footer {
      margin-block-start: auto;
      display: flex;
      flex-wrap: wrap;
      align-items: center;
      gap: var(--ds-space-2);
    }

    /* ── Beside ───────────────────────────────────────────────────────────────
       The picture at the side instead of on top, for a list of cards read one
       under the other: a row of stacked cards is a gallery, a column of them is a
       list, and a list wants its pictures small and its titles on the left edge
       where the eye is already running down.

       A grid and not a row of flex items, because the parts after the media have
       to stack in the second column rather than line up beside it. */
    .ds-card--beside {
      /* The picture is taken out of the flow and laid down the side, and the card
         keeps a strip of padding the same width for it to sit in. Spanning it
         across grid rows was the first attempt and it does not hold: `-1` counts
         from the end of the EXPLICIT grid, and a card whose rows are all implicit
         has one line to count to, so the picture covered the header and stopped. A
         card has a header, or a header and a footer, or three parts and a menu,
         and none of them should have to be counted. */
      padding-inline-start: var(--ds-card-aside, 33%);
    }

    .ds-card--beside .ds-card__media {
      position: absolute;
      inset-block: 0;
      inset-inline-start: 0;
      inline-size: var(--ds-card-aside, 33%);
      /* The height is the row's, so the declared ratio steps aside. */
      aspect-ratio: auto;
    }

    /* ── Chosen ───────────────────────────────────────────────────────────────
       A card that has been picked, in a set where picking one is the point: a
       plan, a delivery date, a workspace to sign in to. The border carries it and
       a wash of the interactive colour backs it up, because a border alone at the
       width of a hairline is a state somebody can miss. */
    .ds-card--chosen {
      border-color: var(--ds-border-active);
      background-color: var(--ds-surface-active);
    }

    /* ── Ticked ───────────────────────────────────────────────────────────────
       A card that can be ticked wears a box in its top corner, the box a table
       row wears, and it is always there, as the table's is: a box that appears
       under the pointer is a control the reader cannot know exists until they
       happen to find it. Owner's ruling, 10 September 2026; until then it was
       hidden until hovered. */
    .ds-card--selectable {
      /* Where the box sits from the corner: the card's own padding, so the box
         stands where the first word of a header would, and clear of the corner's
         curve. It was 8, and on a 12px corner the box sat in the bend (owner,
         11 September 2026: "posizionata bene rispetto al bordo?"). The room the
         header keeps for it is the inset, the box and a gap, as one rung. */
      --ds-card-select-inset: var(--ds-card-pad);
      --ds-card-select-room: var(--ds-size-3);
    }

    .ds-card__select {
      position: absolute;
      inset-block-start: var(--ds-card-select-inset);
      inset-inline-start: var(--ds-card-select-inset);
      /* Above the stretched link and above the media. */
      z-index: 2;
    }

    /* A header at the top of the card would put its first word under the box,
       so it starts past it. Media at the top needs nothing: the box floats over
       the picture. */
    .ds-card__select + .ds-card__header {
      padding-inline-start: var(--ds-card-select-room);
    }

    /* ── Media ────────────────────────────────────────────────────────────────
       Edge to edge, and the card's corners cut it. The ratio is declared rather
       than left to the file, or a row of cards steps up and down as each picture
       arrives at whatever shape it happens to be. */
    .ds-card__media {
      position: relative;
      /* Never stretched and never squeezed by the column it is in: the ratio is
         the whole point of the slot. */
      flex: none;
      inline-size: 100%;
      aspect-ratio: var(--ds-card-ratio, 16 / 9);
      background-color: var(--ds-surface-2);
    }

    /* Taken out of the flow, or the picture decides the height. A flex item sizes
       its main axis from its content, so an image left in the flow reported its
       own shape and the declared ratio lost to it: a square photograph in a slot
       asking for 16 by 9 came out square.

       An `iframe` is on this list because media arrives from another origin as
       often as it arrives as a file: a video player, a map, a chart somebody else
       draws. Left in the flow it reports the replaced-element default and drew
       304 by 154 in a slot measuring 646 by 363, so the player sat in the top
       corner and 209 pixels of the declared ratio stayed empty under it. Measured
       27 August 2026 on a 648 wide card at 16 by 9. */
    .ds-card__media iframe,
    .ds-card__media img,
    .ds-card__media video {
      position: absolute;
      inset: 0;
      display: block;
      inline-size: 100%;
      block-size: 100%;
      object-fit: cover;
    }

    /* The frame's own chrome, off. An iframe carries a 2px inset border by
       default and this package sizes boxes per component rather than resetting
       `box-sizing` once, so that border is drawn outside the slot: the same card
       measured 650 by 367 inside 646 by 363, two pixels past every edge the card
       is clipping. */
    .ds-card__media iframe {
      border: 0;
    }

    /* ── The link ─────────────────────────────────────────────────────────────
       One link, on the title, stretched over the whole card: under the text, so a
       word can still be selected, and under anything else that is clickable, so a
       button in the footer keeps its own hit area instead of becoming a hole. */
    .ds-card__link {
      color: inherit;
      text-decoration: none;
    }

    .ds-card__link::after {
      content: "";
      position: absolute;
      inset: 0;
      z-index: 1;
    }

    /* A guard, not an invitation: the page says to pick one, a card that leads
       somewhere or a card with controls in it, because a control inside a link is
       a hole in the middle of the target and screen readers announce the pair
       badly. Where one turns up anyway, it stays its own target rather than
       becoming dead ground.

       Only what can be pressed comes up through the stretched link, and only in a
       card that has one: lifting the whole footer would leave its empty half dead
       in a card meant to be clickable everywhere, and lifting anything at all in a
       card with no stretched link is a position nobody asked for. A native input
       is left alone on purpose. The ones in this system are placed by their own
       component, and `position: relative` on top of that put the box of every
       checkbox above its label instead of beside it. */
    .ds-card:has(.ds-card__link) :is(a:not(.ds-card__link), button, label, [tabindex]:not([tabindex="-1"])) {
      position: relative;
      z-index: 2;
    }

    /* Hovering anywhere on a card that leads somewhere. `:has` asks the card
       about a link it contains, which is the question the mouse is asking. */
    .ds-card:has(.ds-card__link:hover) {
      background-color: var(--ds-surface-2);
    }

    /* The ring goes round the card and not round the words of its title, because
       the card is what the keyboard has arrived on; and round a card that is
       itself a stop, in a grid the arrows walk. */
    .ds-card:focus-visible,
    .ds-card:has(.ds-card__link:focus-visible) {
      outline: 2px solid var(--ds-focus);
      outline-offset: 2px;
    }

    .ds-card__link:focus-visible {
      outline: none;
    }

    /* ── The hue ──────────────────────────────────────────────────────────────
       A card can carry the colour of the thing it stands for: the tint a badge of
       that hue wears, and the same hue as an edge down the leading side at the
       width of a signal rather than a border.

       The tint and not the solid, for the reason the badge uses it: a card is
       read THROUGH, and a filled ground takes the words with it. It is painted as
       a gradient layer over the surface rather than as the surface itself, so the
       card's own hover still moves the ground underneath it and a tinted card
       answers the pointer like every other one. The edge is an inset shadow and
       not a border, so a card that gains a colour does not gain a pixel and a
       grid of them stays on its lines.

       Asked for by the first product's view that lets a reader say which field
       lends a record its colour, 12 September 2026: the old system drew a hex
       with an alpha suffix, which no theme ever checked. */
    .ds-card[data-hue] {
      background-image: linear-gradient(var(--ds-card-hue-tint), var(--ds-card-hue-tint));
      box-shadow: inset var(--ds-space-1) 0 0 0 var(--ds-card-hue-edge);
    }

    .ds-card[data-hue="red"] { --ds-card-hue-tint: var(--ds-hue-red-tint); --ds-card-hue-edge: var(--ds-hue-red-edge); }
    .ds-card[data-hue="orange"] { --ds-card-hue-tint: var(--ds-hue-orange-tint); --ds-card-hue-edge: var(--ds-hue-orange-edge); }
    .ds-card[data-hue="amber"] { --ds-card-hue-tint: var(--ds-hue-amber-tint); --ds-card-hue-edge: var(--ds-hue-amber-edge); }
    .ds-card[data-hue="yellow"] { --ds-card-hue-tint: var(--ds-hue-yellow-tint); --ds-card-hue-edge: var(--ds-hue-yellow-edge); }
    .ds-card[data-hue="lime"] { --ds-card-hue-tint: var(--ds-hue-lime-tint); --ds-card-hue-edge: var(--ds-hue-lime-edge); }
    .ds-card[data-hue="green"] { --ds-card-hue-tint: var(--ds-hue-green-tint); --ds-card-hue-edge: var(--ds-hue-green-edge); }
    .ds-card[data-hue="teal"] { --ds-card-hue-tint: var(--ds-hue-teal-tint); --ds-card-hue-edge: var(--ds-hue-teal-edge); }
    .ds-card[data-hue="cyan"] { --ds-card-hue-tint: var(--ds-hue-cyan-tint); --ds-card-hue-edge: var(--ds-hue-cyan-edge); }
    .ds-card[data-hue="blue"] { --ds-card-hue-tint: var(--ds-hue-blue-tint); --ds-card-hue-edge: var(--ds-hue-blue-edge); }
    .ds-card[data-hue="indigo"] { --ds-card-hue-tint: var(--ds-hue-indigo-tint); --ds-card-hue-edge: var(--ds-hue-indigo-edge); }
    .ds-card[data-hue="violet"] { --ds-card-hue-tint: var(--ds-hue-violet-tint); --ds-card-hue-edge: var(--ds-hue-violet-edge); }
    .ds-card[data-hue="pink"] { --ds-card-hue-tint: var(--ds-hue-pink-tint); --ds-card-hue-edge: var(--ds-hue-pink-edge); }

  /* After the card, because the inbox dresses a card and both name a class
     on the same element. */
    /* ==========================================================================
       Inbox

       The CSS behind <Inbox>. Private, like every other component's.

       The days are sections of the page, each a name with how many are in it
       over a column of cards; nothing here folds. The card is the Card's, so
       its ground, its edge, its wash and its stretched link are the system's
       and not this screen's. What lives here is the arrangement inside the
       card — the box, the parent over the name, the day and the one act every
       item shares under it, the rest at the end — and the two states a card
       can be in: the day that has passed, and the item that is done.
       ========================================================================== */

    .ds-inbox {
      display: block;
    }

    /* The days take the room the filters drawer leaves: a flex child is content
       width until it is told otherwise, and the column of cards is the thing
       that should grow. */
    .ds-inbox__body {
      flex: 1;
      min-inline-size: 0;
    }

    /* The strip stands on the page here and not in a sheet, so it gives up the
       gutter a sheet would have paid for it and lines up with the cards. */
    .ds-inbox__strip {
      --ds-toolbar-gutter: 0;
    }

    .ds-inbox__days {
      display: flex;
      flex-direction: column;
      gap: var(--ds-space-6);
      /* The cards' own width decides how they arrange, not the window's: a
         drawer beside the days narrows them while the window stays wide. */
      container-type: inline-size;
      container-name: ds-inbox;
    }

    .ds-inbox__day {
      display: flex;
      flex-direction: column;
      gap: var(--ds-space-2);
    }

    /* The day's name, in the overline role: the voice this package already uses
       for a heading over a set, in the sidebar and in the footer. */
    .ds-inbox__heading {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. */
      --ds-inbox-heading-face: var(--ds-font-overline-face);
      --ds-inbox-heading-size: var(--ds-font-overline-size);
      --ds-inbox-heading-weight: var(--ds-font-overline-weight);
      --ds-inbox-heading-leading: var(--ds-font-overline-leading);
      --ds-inbox-heading-tracking: var(--ds-font-overline-tracking);
      display: flex;
      align-items: center;
      gap: var(--ds-space-2);
      margin: 0;
      font-family: var(--ds-inbox-heading-face);
      font-size: var(--ds-inbox-heading-size);
      font-weight: var(--ds-inbox-heading-weight);
      line-height: var(--ds-inbox-heading-leading);
      letter-spacing: var(--ds-inbox-heading-tracking);
      text-transform: uppercase;
      --ds-inbox-heading-ink: var(--ds-text-tertiary-neutral);
      color: var(--ds-inbox-heading-ink);
    }

    /* How many are in the day, beside its name and quieter than it: a number,
       not a badge, since the heading is already the quiet voice. */
    .ds-inbox__count {
      --ds-inbox-count-face: var(--ds-font-small-face);
      --ds-inbox-count-size: var(--ds-font-small-size);
      --ds-inbox-count-weight: var(--ds-font-small-weight);
      --ds-inbox-count-leading: var(--ds-font-small-leading);
      --ds-inbox-count-tracking: var(--ds-font-small-tracking);
      font-family: var(--ds-inbox-count-face);
      font-size: var(--ds-inbox-count-size);
      font-weight: var(--ds-inbox-count-weight);
      line-height: var(--ds-inbox-count-leading);
      letter-spacing: var(--ds-inbox-count-tracking);
      font-variant-numeric: tabular-nums;
      --ds-inbox-count-ink: var(--ds-text-tertiary-neutral);
      color: var(--ds-inbox-count-ink);
    }

    /* An item is a card of its own, one under another, not a row in a shared
       sheet: the box, the ground and the edge are the card's, and what lives
       here is the arrangement inside it. */
    .ds-inbox__cards {
      display: flex;
      flex-direction: column;
      gap: var(--ds-space-3);
      margin: 0;
      padding: 0;
      list-style: none;
    }

    .ds-inbox__line {
      display: flex;
      align-items: center;
      gap: var(--ds-space-3);
      min-inline-size: 0;
    }

    .ds-inbox__box {
      display: flex;
      flex: none;
      align-items: center;
    }

    /* Three lines and not two: the parent over the name, the name, and the day
       with the one act every item shares under it. */
    .ds-inbox__words {
      display: flex;
      flex: 1;
      flex-direction: column;
      gap: var(--ds-space-1);
      min-inline-size: 0;
    }

    .ds-inbox__meta {
      display: flex;
      align-items: center;
      gap: var(--ds-space-2);
      min-inline-size: 0;
    }

    .ds-inbox__end {
      display: flex;
      flex: none;
      align-items: center;
      gap: var(--ds-space-2);
    }

    /* What the item stands under, over its name. A link where the parent has an
       address, and it does not answer the pointer on its own: the card is what
       the pointer is over and the card is what washes. */
    .ds-inbox__parent {
      --ds-inbox-parent-face: var(--ds-font-small-face);
      --ds-inbox-parent-size: var(--ds-font-small-size);
      --ds-inbox-parent-weight: var(--ds-font-small-weight);
      --ds-inbox-parent-leading: var(--ds-font-small-leading);
      --ds-inbox-parent-tracking: var(--ds-font-small-tracking);
      display: block;
      font-family: var(--ds-inbox-parent-face);
      font-size: var(--ds-inbox-parent-size);
      font-weight: var(--ds-inbox-parent-weight);
      line-height: var(--ds-inbox-parent-leading);
      letter-spacing: var(--ds-inbox-parent-tracking);
      --ds-inbox-parent-ink: var(--ds-text-secondary-neutral);
      color: var(--ds-inbox-parent-ink);
      text-decoration: none;
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
    }

    .ds-inbox__title {
      --ds-inbox-title-face: var(--ds-font-label-face);
      --ds-inbox-title-size: var(--ds-font-label-size);
      --ds-inbox-title-weight: var(--ds-emphasis-weight);
      --ds-inbox-title-leading: var(--ds-font-label-leading);
      --ds-inbox-title-tracking: var(--ds-font-label-tracking);
      display: block;
      font-family: var(--ds-inbox-title-face);
      font-size: var(--ds-inbox-title-size);
      font-weight: var(--ds-inbox-title-weight);
      line-height: var(--ds-inbox-title-leading);
      letter-spacing: var(--ds-inbox-title-tracking);
      --ds-inbox-title-ink: var(--ds-text-primary-neutral);
      color: var(--ds-inbox-title-ink);
      text-decoration: none;
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
    }

    /* The day, in the metadata voice and not the name's: the line under the
       name is about the item, and reading it as loud as the name is what makes
       a column of cards hard to scan. */
    .ds-inbox__when {
      --ds-inbox-when-face: var(--ds-font-small-face);
      --ds-inbox-when-size: var(--ds-font-small-size);
      --ds-inbox-when-weight: var(--ds-font-small-weight);
      --ds-inbox-when-leading: var(--ds-font-small-leading);
      --ds-inbox-when-tracking: var(--ds-font-small-tracking);
      flex: none;
      font-family: var(--ds-inbox-when-face);
      font-size: var(--ds-inbox-when-size);
      font-weight: var(--ds-inbox-when-weight);
      line-height: var(--ds-inbox-when-leading);
      letter-spacing: var(--ds-inbox-when-tracking);
      font-variant-numeric: tabular-nums;
      --ds-inbox-when-ink: var(--ds-text-secondary-neutral);
      color: var(--ds-inbox-when-ink);
    }

    .ds-inbox__when--overdue {
      --ds-inbox-when-ink: var(--ds-danger-ink);
    }

    /* A day that has passed says so twice and no more: the day itself in the
       danger ink, and the word at the card's end. The old screen painted a red
       edge down the card as well, which is one signal drawn three times and a
       card decorated by its state rather than telling you what its state is
       (owner, 7 September 2026). Colour is signal here, never decoration.

       A ticked item: its name struck and quiet, and everything else on the card
       as it was — the same answer the list gives a ticked row. */
    .ds-inbox__card--done .ds-inbox__title {
      --ds-inbox-title-ink: var(--ds-text-secondary-neutral);
      color: var(--ds-inbox-title-ink);
      text-decoration: line-through;
    }

    /* ── The narrow fold ──────────────────────────────────────────────────────
       The box and the words keep the first line and the badges drop to a
       second. The width is `--ds-break-compact`, written out because a
       container query cannot read a token in its condition. */
    @container ds-inbox (width <= 30rem) {
      .ds-inbox__line {
        flex-wrap: wrap;
      }

      .ds-inbox__end {
        inline-size: 100%;
      }
    }

    /* ==========================================================================
       Header

       The CSS behind <Header> and its parts. Private, like every other
       component's.

       The top of the site: the mark on the left, where you can go in the middle,
       what you can do on the right. One per page, above everything, and the first
       thing a keyboard lands on after the link that skips it.

       ── Its height is what it holds ──────────────────────────────────────────
       The tallest thing in it plus its own air, the way the toolbar's is. A
       fixed height would either crop a control at the top of the ramp or leave a
       band of empty surface under a small one, and the day somebody puts a search
       box in it the number would have to be picked again.
       ========================================================================== */

    .ds-header {
      /* This component's own. The role says what the words are; this says that
         THIS component wants a heavier hand than the role has, which is a
         decision the component makes and nobody else. Pointed at the semantic
         rather than holding a number, so moving the system moves every component
         that has not been moved on purpose, and moving one moves one. */
      /* And the one you are ON, which is the other thing this file was
         saying with the same number. */
      --ds-header-weight-current: var(--ds-current-weight);
      /* The container the rules at the foot of this file ask about, and it is the
         HEADER rather than the bar inside it: the rule that has to change below
         the break is `flex-wrap` on the bar, and a container cannot be reached by
         its own query. Its own width, not the window's, because a bar inside a
         shell inside a canvas is neither.

         `inline-size` and not `size`: containing the block size would make the
         bar unable to grow with its own contents. */
      container-type: inline-size;
      /* The page's own ground, because a bar is the frame of a page rather than a
         thing sitting on one: what floats above a page takes the sheet, a card, a
         dialog, a panel; what the page is built out of takes the ground and is
         told apart by its line. */
      background-color: var(--ds-surface-0);
      border-block-end: 1px solid var(--ds-border-subtle);
    }

    /* The row itself, held to a reading width and centred in the page. The header
       spans the window and its contents do not: a mark at the far left of a wide
       screen and a button at the far right is a distance nobody's eye crosses. */
    .ds-header__bar {
      /* The cap counts the padding, or a bar told to hold 72rem holds 72 plus its
         two gutters and the number means nothing. */
      box-sizing: border-box;
      display: flex;
      align-items: center;
      gap: var(--ds-space-5);
      /* It wraps rather than running off the side. A bar is the widest fixed row
         on a page and the first thing to run out of room: this one asked for 932px
         and was given 493 the moment a column was taken off the window, and the
         page scrolled sideways to hold it. A control nobody can reach because it
         is past the edge is worse than a bar two lines tall.

         `row-gap` and not the `gap` above, which is the distance between the
         things in the row: on a second line they need less than the groups need
         from each other. */
      flex-wrap: wrap;
      row-gap: var(--ds-space-2);
      inline-size: 100%;
      /* `none` to let it run the width of the window, which is what an
         application bar usually wants. */
      max-inline-size: var(--ds-header-width, 72rem);
      margin-inline: auto;
      padding-block: var(--ds-space-3);
      /* The gutter is a hook, because the bar has to line up with the page under
         it and only the page knows what its own gutter is. Ours is the middle of
         the ramp; a site that pads its columns by more sets this to the same
         number and the walls of the two boxes meet. */
      padding-inline: var(--ds-header-gutter, var(--ds-space-4));
    }

    /* Edge to edge, for an application: the column under it starts at the edge
       and a bar centred in a reading width would leave the two out of line. */
    .ds-header--app .ds-header__bar {
      max-inline-size: none;
      /* The inset comes down to the step a column of links uses, so what sits in
         the corner of the bar stands over what sits in the column under it. At the
         site's own gutter the two were four pixels apart, which is the distance at
         which a pair reads as unrelated rather than as misaligned. Still a hook: a
         page whose column is padded by more sets it and the walls meet again. */
      padding-inline: var(--ds-header-gutter, var(--ds-space-3));
    }

    /* The middle. It takes what the two ends leave and stops growing at a width,
       so on a wide screen the box does not run from the mark to the avatar; on a
       narrow one the bar wraps and this lands on its own line at full width,
       which is what a phone wants without anything being told about a phone. */
    .ds-header__search {
      flex: 1 1 12rem;
      min-inline-size: 0;
      max-inline-size: var(--ds-header-search-max, 32rem);
      margin-inline: auto;
    }

    /* ── Sticky ───────────────────────────────────────────────────────────────
       Opt in, and never the default. A bar that follows the page takes a band of
       a short window for the whole of the visit, it grows as a share of the
       screen the moment somebody zooms, and it can sit over the very link the
       keyboard has just moved to. Worth it where the navigation is the point of
       the page, wrong where the page is. */
    .ds-header--sticky {
      position: sticky;
      inset-block-start: 0;
      /* Over the page and under anything that opens on top of it. */
      z-index: 20;
      /* The same ground it has standing still. It was four fifths of the surface
         with a blur behind it, which costs a repaint of everything under the bar
         on every frame of every scroll, on every page, for a pane of glass nobody
         asked to look through. A bar that follows the page is a bar you read the
         page THROUGH, not a bar you read the page in.

         What tells it from the page is the line under it, which it has either
         way. */
      background-color: var(--ds-surface-0);
    }

    /* ── The mark ─────────────────────────────────────────────────────────────
       A link home, and the only place in the header where the type is heavier
       than the rest. */
    /* The start of the row, as one thing. A mark that opens the navigation and
       the product's name are a pair, and the bar's gap is the distance between
       groups: side by side as bare siblings they were as far apart as the brand
       was from the search, which reads as three answers rather than one. */
    .ds-header__start {
      display: flex;
      align-items: center;
      gap: var(--ds-space-2);
      min-inline-size: 0;
    }

    .ds-header__brand {
      --ds-header-brand-weight: var(--ds-emphasis-weight);
      display: inline-flex;
      flex: none;
      align-items: center;
      gap: var(--ds-space-2);
      font-weight: var(--ds-header-brand-weight);
      --ds-header-brand-ink: var(--ds-text-primary-neutral);
      color: var(--ds-header-brand-ink);
      text-decoration: none;
      border-radius: var(--ds-radius-2);
    }

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

    /* ── Where you can go ─────────────────────────────────────────────────────
       Takes the room between the mark and the actions, so the actions end up at
       the far end without a margin of their own. */
    .ds-header__nav {
      display: flex;
      /* Only as wide as what it holds. It used to take the free space, which put
         anything after it against the far side of the bar: two doors to the same
         house, one at each end of the row. What pushes the actions to the end is
         their own margin. */
      flex: none;
      align-items: center;
      gap: var(--ds-space-1);
      min-inline-size: 0;
      /* Below the medium breakpoint the row is the mark and the actions only:
         what goes here belongs behind a button at that width, and a nav that
         wraps to a second line takes the header with it. */
    }

    /* ── The way into the navigation when the row is too narrow to show it ─────
       `HeaderMenu` has been exported since it was written and drew three classes
       nothing styled: it stood in the row at every width, beside the links it
       duplicates. A name in the markup with no rule behind it reads as a hook
       somebody can use, and this one read as a component somebody could reach
       for. */
    .ds-header__menu {
      display: none;
      flex: none;
    }

    /* The header's own width and not the window's. It was a media query, and a
       header inside a panel or a device canvas kept a full row of links in 430
       pixels because as far as the window was concerned there was room. The
       header already declares the container two hundred lines up; this was the
       one rule that did not use it. */
    /* The header's own width and not the window's. It was a media query, and a
       header inside a panel or a device canvas kept a full row of links in 430
       pixels because as far as the window was concerned there was room. The
       header already declares the container two hundred lines up; this was the
       one rule that did not use it. */
    @container (width <= 40rem) {
      .ds-header__nav {
        display: none;
      }

      /* And the way in arrives in the same breath. Written as two rules at two
         widths these could drift apart, and a header with no links and no button
         is a header with a navigation nobody can reach: that is what this screen
         shipped as for a day. One block, one width, one decision.

         Two of them, because there are two ways in and a bar takes whichever it
         was given: the mark, which opens the sheet the SHELL holds, and
         `HeaderMenu`, which brings its own. The mark is the one to reach for —
         the application shell has used it from the start — and the rule for it
         lives here rather than in a shell, so a bar on a page with no shell
         around it behaves the same way. */
      .ds-header__menu {
        display: block;
      }
    }

    /* Inside the drawer: the same places, one to a line, because a row of targets
       is what a bar does and a drawer has the height for the plainer thing. */
    .ds-header__sheet {
      display: flex;
      flex-direction: column;
      gap: var(--ds-rhythm-line);
    }

    .ds-header__row {
      --ds-header-row-face: var(--ds-font-body-face);
      --ds-header-row-size: var(--ds-font-body-size);
      --ds-header-row-weight: var(--ds-font-body-weight);
      --ds-header-row-leading: var(--ds-font-body-leading);
      --ds-header-row-tracking: var(--ds-font-body-tracking);
      display: flex;
      align-items: center;
      min-block-size: var(--ds-size-3);
      padding-inline: var(--ds-space-3);
      border-radius: var(--ds-radius-2);
      font-family: var(--ds-header-row-face);
      font-size: var(--ds-header-row-size);
      font-weight: var(--ds-header-row-weight);
      line-height: var(--ds-header-row-leading);
      letter-spacing: var(--ds-header-row-tracking);
      --ds-header-row-ink: var(--ds-text-secondary-neutral);
      color: var(--ds-header-row-ink);
      text-decoration: none;
      transition:
        color var(--ds-duration) var(--ds-ease),
        background-color var(--ds-duration) var(--ds-ease);
    }

    .ds-header__row:hover {
      --ds-header-row-ink: var(--ds-text-primary-neutral);
      background-color: var(--ds-surface-hover);
    }

    .ds-header__row[aria-current] {
      --ds-header-row-ink: var(--ds-text-primary-neutral);
      --ds-header-row-weight: var(--ds-current-weight);
    }

    /* A navigation item is a target, not a sentence: no underline, a hit area
       with room around the word, and the whole thing washed on hover rather than
       the word alone. */
    /* The button that opens a section, and the mark on it. Both classes were in
       the markup with no rule behind them: a name that styles nothing reads as a
       hook somebody can use, and the next person writing a header would have
       found two of them that do nothing.
       The mark is `1lh`, the height of the line beside it, like every mark in the
       package. */
    .ds-header__more {
      gap: var(--ds-space-1);
    }

    .ds-header__chevron {
      display: inline-flex;
      flex: none;
      --ds-header-chevron-ink: var(--ds-text-tertiary-neutral);
      color: var(--ds-header-chevron-ink);
    }

    .ds-header__chevron > svg {
      inline-size: 1lh;
      block-size: 1lh;
    }

    .ds-header__item {
      /* A row of these is a row of links AND, where a section opens a panel, a
         `<button>`. A control inherits neither the face nor the size, so the
         button came out in the user agent's Arial at 13.33px in a row of 16px
         links, and nothing said so because the two sat on separate lines.
         `font: inherit` and not a token: what this wants is the row it stands in,
         which is what the links beside it already have. */
      font: inherit;
      /* And the chrome a `<button>` brings with it. `font: inherit` took the face;
         these take the rest: measured `rgb(239, 239, 239)` on the button that
         opens a section, which is the user agent's own grey, sitting in a row of
         links that have no fill at all. Invisible in dark, where the row is dark
         anyway, and plain in light. */
      background-color: transparent;
      border: 0;
      display: inline-flex;
      align-items: center;
      min-block-size: var(--ds-size-2);
      padding-inline: var(--ds-space-3);
      border-radius: var(--ds-radius-2);
      --ds-header-item-ink: var(--ds-text-secondary-neutral);
      color: var(--ds-header-item-ink);
      /* Cleared, and not merely never asked for: every link in the system carries
         a line from the base sheet, which is right in a paragraph and wrong in a
         row of targets. */
      text-decoration: none;
      transition:
        color var(--ds-duration) var(--ds-ease),
        background-color var(--ds-duration) var(--ds-ease);
    }

    .ds-header__item:hover {
      background-color: var(--ds-surface-hover);
      --ds-header-item-ink: var(--ds-text-primary-neutral);
      color: var(--ds-header-item-ink);
    }

    /* The page you are on, said twice: in the colour and in the weight, because
       colour alone is not a difference everybody can see. */
    .ds-header__item[aria-current="page"] {
      --ds-header-item-ink: var(--ds-text-primary-neutral);
      color: var(--ds-header-item-ink);
      font-weight: var(--ds-header-weight-current);
    }

    /* ── What you can do ──────────────────────────────────────────────────────
       The end of the row. Never wraps: a header that becomes two rows moves the
       whole page down. */
    .ds-header__actions {
      display: flex;
      /* It wraps and it may shrink. `flex: none` held it at its full width whatever
         the bar had left, so a bar that wraps still ran off the side: measured 640
         of actions in 493 of room, and 424 elements past the edge. The bar's own
         wrap is no use while the group inside it refuses to give. */
      flex-wrap: wrap;
      align-items: center;
      gap: var(--ds-space-2);
      margin-inline-start: auto;
      min-inline-size: 0;
    }


    /* ── The marks a bar carries ──────────────────────────────────────────────
       Three, and they are the same object: a target on the control ramp with a
       mark the height of the line in the middle of it. They are not `Button`,
       because a button carries a label and a fill and a tone and these carry
       none of that: they are the bar's own furniture, and giving them a button's
       API would be offering six tiers and five tones to something that has one
       of each. */
    .ds-header__mark {
      box-sizing: border-box;
      display: none;
      flex: none;
      align-items: center;
      justify-content: center;
      inline-size: var(--ds-size-3);
      block-size: var(--ds-size-3);
      padding: 0;
      border: 0;
      border-radius: var(--ds-radius-2);
      background: none;
      --ds-header-mark-ink: var(--ds-text-primary-neutral);
      color: var(--ds-header-mark-ink);
      /* A button inherits neither the face nor the leading, and the mark inside
         is a line tall. */
      font: inherit;
      cursor: pointer;
      transition: background-color var(--ds-duration) var(--ds-ease);
    }

    .ds-header__mark > svg {
      inline-size: 1lh;
      block-size: 1lh;
    }

    .ds-header__mark:hover {
      background-color: var(--ds-surface-hover);
    }

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

    /* ── Which of them is on screen ───────────────────────────────────────────
       One rule per mark and each one says the same thing in a different place:
       a control is on screen where it has something to do.

       The column toggle where there is a column. The menu where there is not.
       The search mark where the box does not fit. Nothing is hidden to make room
       for something else, which is why no rule here has to know about another. */
    /* The default, for a bar with no shell around it: the column toggle, because
       a bar drawn on its own is a bar on a page wide enough to have a column.
       Inside an `AppShell` the shell governs both, and it has to: they change
       hands at the width the column does, and only the shell knows that width. */
    .ds-header__mark--sidebar {
      display: inline-flex;
    }

    /* And the way into the sheet, at the width where the row of links is gone.
       Down here and not up beside the rule that hides the nav, because the base
       rule for every mark is `display: none` and it is written below that one: at
       equal weight the later rule wins, and the button stayed off screen while
       the links it replaces were already gone. The second time this file has
       taught that lesson in an afternoon.
       A shell that governs its own marks overrides this, which is what
       `.ds-app-shell--tablet` does. */
    @container (width <= 40rem) {
      .ds-header__mark--menu {
        display: inline-flex;
      }
    }

    /* The menu at every width, said by the toggle itself: for the site whose
       menu holds more than the bar shows — the small places, the accounts, a
       control — and would hold it at any width. The first consumer's menu is
       meant to grow, and a row of links would not hold it, so its burger stood
       beside the row at every size. The row, if the bar renders one, stays: the
       mark stops being the narrow screen's fallback and becomes the way into
       the rest. */
    .ds-header__mark--always {
      display: inline-flex;
    }

    /* ── The search, at three widths ──────────────────────────────────────────
       Wide: a box in the row. Narrow: a mark. Narrow and pressed: the row.

       The third is the reason the first two are drawn as siblings rather than
       swapped by the page. Everything the bar does about it keys off one
       attribute on one element, so a bar handing its row over is a rule and not
       a piece of state somebody has to lift. */
    .ds-header__field {
      display: contents;
    }

    /* ── What fits, and what stands down ──────────────────────────────────────
       The narrower the bar, the fewer things can be in it, and the order they go
       in is the order of how far away their job can afford to be.

       The row may not wrap. A bar two lines tall on a phone is a quarter of the
       screen spent on furniture, and it was three lines before this rule: brand,
       marks, box. What a row that may not wrap and may not overflow needs is
       parts that give, which is the rest of this block. */
    @container (width <= 40rem) {
      .ds-header__bar {
        flex-wrap: nowrap;
      }

      .ds-header__actions {
        /* Whatever is left of it is one face, and a face is a fixed size. */
        flex: none;
        flex-wrap: nowrap;
      }

      /* The auto margin that pushes the trailing group to the end is handed to
         the search when there IS one: the search is the first thing in that
         group, and two auto margins share the free space between them instead of
         spending it all on the left of the first.

         It used to be handed over whatever the bar held, which made the push
         vanish on every bar with no search in it: measured on a product whose
         bar is a mark, a name and a face, the face sat at 232 of a 375 bar with
         103 pixels of nothing to its right. A rule that assumes a part is there
         is a rule that breaks on the bars that do not have it. */
      .ds-header__bar:has(.ds-header__search) .ds-header__actions {
        margin-inline-start: 0;
      }

      /* The name gives way before the box does: a product is recognised from its
         first letters, and a search box below its floor is a box you cannot read
         what you typed into. */
      .ds-header__brand {
        min-inline-size: 0;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
      }
    }

    /* Narrower still the marks close up: they are ONE run, so the step between
       them comes down to the step inside a group and the search reads as the first
       of the controls at the end rather than as a stray beside them. */
    @container (width <= 40rem) {
      /* The inset comes in with the width. 24 down each side of a 375 bar is an
         eighth of it spent on nothing, and it is the eighth the words needed.

         The padding and not `--ds-header-gutter`: that name is a HOOK, the one a
         page sets to line its bar up with the column under it, and a component
         does not declare the variable other files reach in with. What the header
         is saying here is narrower than the hook: at this width the inset is 16
         whatever the page asked for, because the page asked for a number that
         suited a page and this is not one. */
      .ds-header__bar {
        padding-inline: var(--ds-space-4);
      }
    }

    @container (width <= 30rem) {
      .ds-header__bar {
        column-gap: var(--ds-space-2);
      }
    }

    /* The NAME stays, and it used to go here.
       That rule was written when the row still held a nav at this width and the
       name was what had to give to buy the room. The links leave at 40rem now, so
       what is left below it is a mark, the name, and the controls at the end, and
       there is room for all of it: measured, the row fits from 300, which is
       narrower than anything that reports itself as a phone. A bar with no name on
       it has stopped saying whose site this is, which is the one thing a bar is
       for. It truncates instead, which the rule above already sets up. */

    @container (width <= 40rem) {
      /* The box goes and its mark arrives. `display: none` on the field and not
         a width of nothing: a box nobody can see is still a box a keyboard walks
         into, and that was the defect the first version of this shipped with. */
      .ds-header__search:not([data-searching]) .ds-header__field {
        display: none;
      }

      .ds-header__search:not([data-searching]) {
        flex: none;
        inline-size: var(--ds-size-3);
        /* Beside the other marks, not adrift in the middle. The slot centres
           itself while it is a box, which is right for a box: it is the widest
           thing in the row and the row reads from its middle. As a mark it is one
           of the controls at the end, and a 40px square centred in 375 reads as
           something that lost its way rather than as something you can press.

           The auto margin moves HERE and comes off the actions, rather than being
           added beside theirs: two auto start margins split the free space
           instead of one of them taking it, and the mark landed at 168 of a 375
           bar, which is the middle to a pixel. Taking it off both sent it to the
           left instead. One auto, on the first thing in the trailing group. */
        margin-inline: auto 0;
      }

      .ds-header__mark--search {
        display: inline-flex;
      }

      /* Handed over. The search takes the row and the way out appears at the end
         of it; everything else in the bar stands down, which is the point — a
         row that has become one thing has room for that thing. */
      .ds-header__bar:has(.ds-header__search[data-searching]) > :not(.ds-header__search) {
        display: none;
      }

      .ds-header__search[data-searching] {
        flex: 1 1 0;
        display: flex;
        align-items: center;
        gap: var(--ds-space-2);
      }

      .ds-header__search[data-searching] .ds-header__field {
        display: block;
        flex: 1 1 0;
        min-inline-size: 0;
      }

      .ds-header__search[data-searching] .ds-header__mark--search {
        display: none;
      }

      .ds-header__search[data-searching] .ds-header__mark--close {
        display: inline-flex;
      }
    }

    /* The burger that becomes a cross. `aria-expanded` is what the mark already
       said about itself, so nothing new goes into the markup for this. */
    .ds-header__mark--menu[aria-expanded="true"] .ds-swap__off {
      display: none;
    }

    .ds-header__mark--menu[aria-expanded="true"] .ds-swap__on {
      display: inline-flex;
    }

    /* ==========================================================================
       Sidebar

       The CSS behind <Sidebar>. Private, like every other component's.

       The column of a place: what is in it, grouped, with the one you are on
       marked. It holds the whole navigation of a docs site, an admin panel or a
       settings screen, and it stays where it is while the page beside it moves.

       ── Three bands, and only the middle one scrolls ─────────────────────────
       A head that carries the mark of the place, the list, and a foot for what
       sits under everything else: a version, an account, a way out. The list is
       what scrolls, so a long one does not take the head off the top of the
       screen: before this the docs' own list ran past the bottom of the window
       and the only way to the last link was to scroll the ARTICLE to its end.

       ── Two widths, one number ───────────────────────────────────────────────
       Open it is 16rem and collapsed it is the tier its items stand on plus the
       air either side, so the icons sit in a column exactly as wide as they are.
       One variable holds it, which is what keeps a sidebar and whatever is
       offset beside it moving together.
       ========================================================================== */

    .ds-sidebar {
      /* And the one you are ON, which is the other thing this file was
         saying with the same number. */
      --ds-sidebar-weight-current: var(--ds-current-weight);
      --ds-sidebar-width: 16rem;
      /* The air at the column's edges, in one place, because two things read it:
         the padding itself and the width the column takes when the labels go. */
      --ds-sidebar-inset: var(--ds-space-4);
      /* The page's own ground, the same one the bar above it stands on. Left
         transparent it took whatever it happened to sit in, so a screen came out
         with one band across the top and a different column under it: two grounds
         meeting at a corner, which reads as two applications. A column is the
         frame of a page, and a frame takes the ground and is told apart by its
         line. */
      background-color: var(--ds-surface-0);
      /* The tier the rows stand on, and the number the collapsed width is made
         of. One place, because the two have to agree: a row of 32 in a column of
         56 is a mark that moves when the labels go.

         The middle of the ramp, arrived at from both sides: at 48, which is what
         40 came out as before the box was told to count its own padding, forty-two
         rows were a screen and a half; at 32 the words sat on the row above and
         below with nothing between them. */
      --ds-sidebar-tier: var(--ds-size-3);

      display: flex;
      flex-direction: column;
      inline-size: var(--ds-sidebar-width);
      max-block-size: 100%;
      min-block-size: 0;
      transition: inline-size var(--ds-duration) var(--ds-ease);
    }

    /* Icons only, and as wide as one row needs: the item's tier and the column's
       inset on each side. Nothing is measured by hand and nothing shifts, because
       the row above keeps its own inset rather than centring in whatever is
       left. */
    .ds-sidebar--collapsed {
      --ds-sidebar-width: calc(var(--ds-sidebar-tier) + var(--ds-sidebar-inset) * 2);
    }

    .ds-sidebar__head {
      display: flex;
      align-items: center;
      gap: var(--ds-space-2);
      flex: none;
      min-block-size: var(--ds-size-4);
      padding: var(--ds-sidebar-inset);
    }

    .ds-sidebar--collapsed .ds-sidebar__head,
    .ds-sidebar--collapsed .ds-sidebar__foot {
      justify-content: center;
    }

    /* The search, above the list and outside its scroll, so it stays put while
       the rows go by. The Toolbar's own slot is the precedent: a filter is a
       different thing from a brand, so it is not the head wearing a control. It
       takes the column's inset so the box lines up with the rows under it, and
       the collapsed column hides it, because a filter with nowhere to write its
       answers is furniture. */
    .ds-sidebar__search {
      flex: none;
      padding: 0 var(--ds-sidebar-inset) var(--ds-space-2);
    }

    .ds-sidebar--collapsed .ds-sidebar__search {
      display: none;
    }

    /* The list, and the only part that scrolls. */
    .ds-sidebar__list {
      flex: 1;
      min-block-size: 0;
      overflow-y: auto;
      display: flex;
      flex-direction: column;
      gap: var(--ds-rhythm-element);
      /* A column of links wants air at its edges the way a panel does: at one step
         the rows ran into the walls and the whole column read as a list pressed
         against the page rather than as a part of it. */
      padding: var(--ds-sidebar-inset);
      scrollbar-width: thin;
    }

    .ds-sidebar__group {
      display: flex;
      flex-direction: column;
      gap: var(--ds-space-0-5);
    }

    /* The name over a run of links. The overline treatment, because it is a label
       for a part of the furniture rather than a heading in the document. */
    /* A named run folds on the browser's own details; the only script in it is
       the remembering. The summary is the heading's row with the chevron at its
       end, the marker the user agent draws is taken off, and the chevron turns
       when the fold opens. */
    .ds-sidebar__fold {
      display: block;
    }

    .ds-sidebar__summary {
      display: flex;
      align-items: center;
      justify-content: space-between;
      cursor: pointer;
      list-style: none;
    }

    .ds-sidebar__summary::-webkit-details-marker {
      display: none;
    }

    .ds-sidebar__chev {
      display: inline-flex;
      --ds-sidebar-chev-ink: var(--ds-text-tertiary-neutral);
      color: var(--ds-sidebar-chev-ink);
      transition: rotate var(--ds-duration) var(--ds-ease);
      rotate: -90deg;
    }

    .ds-sidebar__fold[open] > .ds-sidebar__summary .ds-sidebar__chev {
      rotate: 0deg;
    }

    /* The collapsed column has no words to fold. */
    .ds-sidebar--collapsed .ds-sidebar__summary {
      display: none;
    }

    .ds-sidebar__heading {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-sidebar-heading-face: var(--ds-font-overline-face);
      --ds-sidebar-heading-size: var(--ds-font-overline-size);
      --ds-sidebar-heading-leading: var(--ds-font-overline-leading);
      --ds-sidebar-heading-tracking: var(--ds-font-overline-tracking);
      margin: 0;
      padding: var(--ds-space-1) var(--ds-space-2);
      font-family: var(--ds-sidebar-heading-face);
      font-size: var(--ds-sidebar-heading-size);
      /* The label's leading, because the overline has none of its own: it is 14 on
         the web surface and 13 on the other, and 20 is the box the system pairs
         with 14 everywhere else. Written rather than left to inherit, since a
         heading that takes the page's leading is a heading whose height changes
         with whatever it happens to sit inside. */
      line-height: var(--ds-sidebar-heading-leading);
      font-weight: var(--ds-font-overline-weight);
      letter-spacing: var(--ds-sidebar-heading-tracking);
      text-transform: uppercase;
      --ds-sidebar-heading-ink: var(--ds-text-tertiary-neutral);
      color: var(--ds-sidebar-heading-ink);
    }

    /* Collapsed, a heading is a word nobody can read in 4rem, and hiding it would
       leave the groups running into each other. It becomes the line that separates
       them, which is what it was doing anyway. */
    .ds-sidebar--collapsed .ds-sidebar__heading {
      display: block;
      block-size: 0;
      padding: 0;
      margin-block: var(--ds-space-2);
      overflow: hidden;
      text-indent: -100vw;
      /* Drawn as a border and not as a background on a one-pixel box: a separator
         token painting a box is a line token doing a surface's job, and it is the
         same line the foot of this component already draws. */
      border-block-start: 1px solid var(--ds-border-subtle);
    }

    .ds-sidebar__item {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-sidebar-item-face: var(--ds-font-label-face);
      --ds-sidebar-item-tracking: var(--ds-font-label-tracking);
      --ds-sidebar-item-size: var(--ds-font-label-size);
      --ds-sidebar-item-leading: var(--ds-font-label-leading);
      display: flex;
      align-items: center;
      gap: var(--ds-space-2);
      /* The tier IS the row, so the padding lives inside it. Without this the
         minimum was the CONTENT box and the four pixels either side were added on
         top: a row asked to be 40 measured 48, and forty-two of them made the
         menu a screen and a half of a list that used to be one. */
      box-sizing: border-box;
      min-block-size: var(--ds-sidebar-tier);
      padding: var(--ds-space-1) var(--ds-space-2);
      border-radius: var(--ds-radius-2);
      font-family: var(--ds-sidebar-item-face);
      font-size: var(--ds-sidebar-item-size);
      letter-spacing: var(--ds-sidebar-item-tracking);
      line-height: var(--ds-sidebar-item-leading);
      --ds-sidebar-item-ink: var(--ds-text-secondary-neutral);
      color: var(--ds-sidebar-item-ink);
      text-decoration: none;
      cursor: pointer;
      transition:
        background-color var(--ds-duration) var(--ds-ease),
        color var(--ds-duration) var(--ds-ease);
    }

    .ds-sidebar__item:hover {
      background-color: var(--ds-surface-hover);
      --ds-sidebar-item-ink: var(--ds-text-primary-neutral);
      color: var(--ds-sidebar-item-ink);
    }

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

    /* The one you are on. It is the only thing in the list at full strength: a
       column of secondary text with nothing primary in it tells the reader
       nothing about where they are. */
    .ds-sidebar__item[aria-current] {
      background-color: var(--ds-surface-active);
      --ds-sidebar-item-ink: var(--ds-text-primary-neutral);
      color: var(--ds-sidebar-item-ink);
      font-weight: var(--ds-sidebar-weight-current);
    }

    /* ── A run the reader arranges ─────────────────────────────────────────────
       Each item in a row with a grip at its end. The grip shows under the hand
       and under the keyboard's focus, and not before: the owner's call on this
       one (11 September 2026), against the ruling that kept the boxes always
       drawn, because a column of favourites is read a hundred times a day and
       arranged once, and the row itself is the handle, so the grip is the sign
       and not the door. Its mark is 16 in a 24 target, the add line's mark. The
       row in the air fades, the row it is over draws the line the board's card
       draws. */
    .ds-sidebar__rows {
      display: block;
    }

    .ds-sidebar__row {
      position: relative;
      display: flex;
      align-items: center;
      min-inline-size: 0;
    }

    .ds-sidebar__row > .ds-sidebar__item {
      flex: 1;
      min-inline-size: 0;
    }

    .ds-sidebar__grip {
      --ds-sidebar-grip-ink: var(--ds-text-tertiary-neutral);
      opacity: 0;
      display: inline-flex;
      flex: none;
      align-items: center;
      justify-content: center;
      inline-size: var(--ds-size-1);
      block-size: var(--ds-size-1);
      margin-inline-end: var(--ds-space-1);
      padding: 0;
      border: 0;
      border-radius: var(--ds-radius-1);
      background: none;
      color: var(--ds-sidebar-grip-ink);
      font: inherit;
      cursor: grab;
      transition:
        color var(--ds-duration) var(--ds-ease),
        opacity var(--ds-duration) var(--ds-ease);
    }

    .ds-sidebar__grip > svg {
      inline-size: var(--ds-space-4);
      block-size: var(--ds-space-4);
    }

    .ds-sidebar__row:hover .ds-sidebar__grip,
    .ds-sidebar__row:focus-within .ds-sidebar__grip,
    .ds-sidebar__row--dragging .ds-sidebar__grip {
      opacity: 1;
    }

    .ds-sidebar__row:hover .ds-sidebar__grip,
    .ds-sidebar__grip:focus-visible {
      --ds-sidebar-grip-ink: var(--ds-text-primary-neutral);
    }

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

    .ds-sidebar__row--dragging,
    .ds-sidebar__row.ds-sortable__item--dragging {
      opacity: 0.4;
    }

    .ds-sidebar__row--over::before {
      content: "";
      position: absolute;
      inset-inline: 0;
      inset-block-start: -1px;
      block-size: 0;
      border-block-start: 2px solid var(--ds-border-active);
    }

    /* Always rendered, empty or not, so the labels line up down the list whether
       or not every item has a mark.

       The GRID, and not the line. A mark inside a line of running text is the
       height of that line, which is what a badge or an alert does. A mark in a row
       of a list is not in the line at all: it is a drawing on its own 24 grid,
       sitting beside the words. Squeezed to the 20 of a label's line box it was
       being scaled by five sixths, so a stroke drawn 2 landed at 1.67 and every
       shape came off the pixels it was drawn on. Twenty-four is the grid every set
       in this repo is drawn on, ours and the one the playground borrows. */
    .ds-sidebar__icon {
      display: inline-flex;
      flex: none;
      inline-size: 1lh;
      block-size: 1lh;
    }

    .ds-sidebar__icon > svg {
      inline-size: 100%;
      block-size: 100%;
    }

    /* The name holds its line and the note gives way, which is the order they are
       read in: with both allowed to shrink, "Typography" came out "Typograp..." to
       leave room for the word explaining it. A name long enough to need the whole
       column takes it, and the note goes. */
    .ds-sidebar__label {
      flex: none;
      white-space: nowrap;
      overflow: hidden;
      text-overflow: ellipsis;
    }

    /* ── When the rows are not places ─────────────────────────────────────────
       One line and an ellipsis is what a navigation wants: the places a product
       has are short, and a column of them reads as a list of destinations only
       while every row is one row. That stops being true the moment the rows are
       an INDEX: article titles in the same column come out as five ellipses,
       which names nothing.

       One name and not two. The first try was a hook on `white-space` alone, and
       it changed nothing: the label carries `flex: none` so that the note beside
       it gives way first, and a box that cannot shrink never reaches the width
       where wrapping would happen. Wrapping and shrinking are the same decision
       said twice, so they take the same word.

       `overflow` and `text-overflow` go with them: nothing is left to hide once
       the line can break. Asked for on 26 August 2026 by a blog column built on
       this component. */
    .ds-sidebar--index .ds-sidebar__label {
      flex: 0 1 auto;
      min-inline-size: 0;
      white-space: normal;
      overflow: visible;
      text-overflow: clip;
    }

    /* The other names the same page answers to, or whatever else the caller has to
       say quietly. It gives way before the label does. */
    .ds-sidebar__note {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-sidebar-note-face: var(--ds-font-small-face);
      --ds-sidebar-note-tracking: var(--ds-font-small-tracking);
      --ds-sidebar-note-size: var(--ds-font-small-size);
      --ds-sidebar-note-leading: var(--ds-font-small-leading);
      flex: 1 1 auto;
      min-inline-size: 0;
      overflow: hidden;
      white-space: nowrap;
      text-overflow: ellipsis;
      font-family: var(--ds-sidebar-note-face);
      font-size: var(--ds-sidebar-note-size);
      letter-spacing: var(--ds-sidebar-note-tracking);
      line-height: var(--ds-sidebar-note-leading);
      --ds-sidebar-note-ink: var(--ds-text-tertiary-neutral);
      color: var(--ds-sidebar-note-ink);
    }

    /* Anything at the end of the row that is not the name: a count, a state. */
    .ds-sidebar__end {
      flex: none;
      margin-inline-start: auto;
    }

    .ds-sidebar--collapsed .ds-sidebar__label,
    .ds-sidebar--collapsed .ds-sidebar__note,
    .ds-sidebar--collapsed .ds-sidebar__end,
    .ds-sidebar--collapsed .ds-sidebar__sub {
      display: none;
    }

    /* Collapsed, a row keeps the inset it had open rather than centring in what
       is left. Centred, the mark's place is `(width - mark) / 2`, so it depends
       on the width, so the width has to be computed to put it back where it was —
       and that arithmetic was tuned for one inset and broke at every other one:
       the mark stepped 4px the moment the inset moved from 12 to 16.
       Kept at its own inset the promise holds by construction rather than by
       sum: the mark is at the same distance from the same edge whatever the
       column is doing. */
    .ds-sidebar--collapsed .ds-sidebar__item {
      justify-content: flex-start;
    }

    /* One level under an item, and one only: a tree in a sidebar is a thing
       people close and never open again. The line is where the indent would be,
       so a long name wraps against the same edge the first level starts at. */
    .ds-sidebar__sub {
      display: flex;
      flex-direction: column;
      gap: var(--ds-space-0-5);
      margin-inline-start: calc(var(--ds-space-4) + var(--ds-space-1));
      padding-inline-start: var(--ds-space-3);
      border-inline-start: 1px solid var(--ds-border-subtle);
    }

    .ds-sidebar__foot {
      display: flex;
      align-items: center;
      gap: var(--ds-space-2);
      flex: none;
      padding: var(--ds-sidebar-inset);
      border-block-start: 1px solid var(--ds-border-subtle);
    }

    /* ==========================================================================
       Mobile shell

       The CSS behind <MobileShell>. Private, like every other component's.

       A phone screen: a bar, the screen, the places along the bottom.

       ── Only the middle moves ─────────────────────────────────────────────────
       Which is what every native application does, and what a reader has been
       taught to expect by all of them. On a phone there is no second way back to
       the navigation: if it scrolls away, it is gone until they scroll back.

       That is also why this is not the site shell with a bar at the bottom. A site
       scrolls as one because it is a document; an application does not because it
       is a place.

       ── It is a grid of three rows, and the middle one is `1fr` ───────────────
       `minmax(0, 1fr)` and not `1fr`: a grid row defaults to `auto` for its
       minimum, so a long list would push the tabs off the bottom of the screen
       rather than scrolling inside its own row. That default is the single
       commonest way a layout like this breaks.
       ========================================================================== */

    .ds-mobile {
      box-sizing: border-box;
      display: grid;
      grid-template-rows: auto minmax(0, 1fr) auto auto;
      block-size: 100%;
      min-block-size: 0;
      overflow: hidden;
      background-color: var(--ds-surface-0);
      --ds-mobile-ink: var(--ds-text-primary-neutral);
      color: var(--ds-mobile-ink);
    }

    .ds-mobile__screen {
      min-block-size: 0;
      overflow-y: auto;
      /* The phone's own strip at the sides, for a landscape notch. */
      padding-inline: env(safe-area-inset-left, 0px) env(safe-area-inset-right, 0px);
    }

    /* Above the tabs and below everything else: the one action of this screen, a
       player, a banner. It is not navigation and it does not scroll. */
    .ds-mobile__dock {
      border-block-start: 1px solid var(--ds-border-subtle);
      background-color: var(--ds-surface-0);
      padding: var(--ds-space-3);
    }

    /* With no tabs under it the dock takes the phone's own strip, or its buttons
       sit under the home indicator. */
    .ds-mobile__dock:last-child {
      padding-block-end: calc(var(--ds-space-3) + env(safe-area-inset-bottom, 0px));
    }

    /* ==========================================================================
       SiteMenu

       The CSS behind <SiteMenu>. Private, like every other component's.

       What a website's navigation looks like when it has the whole screen.

       ── Why it is not the drawer ─────────────────────────────────────────────
       The application shell opens a sheet that IS its column: a list of rows, the
       width of the column, with the page dimmed behind it. That is right there
       and wrong here, and it was tried: a site's places came out as a strip of
       list items down one side with a slice of the page showing past them, which
       reads as a drawer somebody left open rather than as the way round a site.

       A site's menu is the screen. It has room, and what it does with the room is
       the design: the places are large enough to read at arm's length and to
       press without aiming, and what a bar can only hold as an afterthought — the
       social accounts, the address, the legal line — is on it rather than behind
       another press.

       ── Three groups, and each is a different kind of thing ──────────────────
       The PLACES are where the site goes, and they are the reason the menu opened.
       The MORE are the small ones that belong at the foot of a page rather than
       in a bar. The SOCIAL are somewhere else entirely, which is why they are
       marks in a row and not words in the list: leaving is a different act from
       moving.
       ========================================================================== */

    .ds-site-menu {
      box-sizing: border-box;
      display: flex;
      flex-direction: column;
      /* The places take the room and the foot takes what it needs, so the list
         sits high and the small print sits on the floor however few places there
         are. */
      justify-content: space-between;
      gap: var(--ds-rhythm-section);
      min-block-size: 100%;
      padding: var(--ds-site-gutter);
    }

    /* ── The places ───────────────────────────────────────────────────────── */

    .ds-site-menu__places {
      display: flex;
      flex-direction: column;
    }

    /* One place. A row and not a word, so the whole line is the target: a name
       three letters long and one nine letters long are the same press. */
    .ds-site-menu__place {
      --ds-site-menu-place-face: var(--ds-font-heading-2-face);
      --ds-site-menu-place-size: var(--ds-font-heading-2-size);
      --ds-site-menu-place-weight: var(--ds-font-heading-2-weight);
      --ds-site-menu-place-leading: var(--ds-font-heading-2-leading);
      --ds-site-menu-place-tracking: var(--ds-font-heading-2-tracking);
      display: block;
      padding-block: var(--ds-rhythm-line);
      font-family: var(--ds-site-menu-place-face);
      font-size: var(--ds-site-menu-place-size);
      font-weight: var(--ds-site-menu-place-weight);
      line-height: var(--ds-site-menu-place-leading);
      letter-spacing: var(--ds-site-menu-place-tracking);
      --ds-site-menu-place-ink: var(--ds-text-secondary-neutral);
      color: var(--ds-site-menu-place-ink);
      text-decoration: none;
      transition: color var(--ds-duration) var(--ds-ease);
    }

    .ds-site-menu__place:hover {
      --ds-site-menu-place-ink: var(--ds-text-primary-neutral);
    }

    .ds-site-menu__place[aria-current] {
      --ds-site-menu-place-ink: var(--ds-text-primary-neutral);
    }

    .ds-site-menu__place:focus-visible {
      outline: 2px solid var(--ds-focus);
      outline-offset: 2px;
      border-radius: var(--ds-radius-2);
    }

    /* ── The foot ─────────────────────────────────────────────────────────── */

    .ds-site-menu__foot {
      display: flex;
      flex-direction: column;
      gap: var(--ds-rhythm-block);
    }

    /* The small places: legal, contact, a language. Wrapped rather than stacked,
       because there are usually three of them and three short words in a column
       is a column of air. */
    .ds-site-menu__more {
      display: flex;
      flex-wrap: wrap;
      gap: var(--ds-rhythm-element);
    }

    .ds-site-menu__link {
      --ds-site-menu-link-face: var(--ds-font-label-face);
      --ds-site-menu-link-size: var(--ds-font-label-size);
      --ds-site-menu-link-weight: var(--ds-font-label-weight);
      --ds-site-menu-link-leading: var(--ds-font-label-leading);
      --ds-site-menu-link-tracking: var(--ds-font-label-tracking);
      font-family: var(--ds-site-menu-link-face);
      font-size: var(--ds-site-menu-link-size);
      font-weight: var(--ds-site-menu-link-weight);
      line-height: var(--ds-site-menu-link-leading);
      letter-spacing: var(--ds-site-menu-link-tracking);
      --ds-site-menu-link-ink: var(--ds-text-secondary-neutral);
      color: var(--ds-site-menu-link-ink);
      text-decoration: none;
    }

    .ds-site-menu__link:hover {
      --ds-site-menu-link-ink: var(--ds-text-primary-neutral);
    }

    /* Somewhere else, so a mark and not a word: the row reads as one group of
       exits rather than as more of the list above it. */
    .ds-site-menu__social {
      display: flex;
      gap: var(--ds-rhythm-element);
    }

    .ds-site-menu__account {
      display: inline-flex;
      align-items: center;
      justify-content: center;
      /* The smallest target WCAG allows, which is what a row of marks with
         nothing around them needs. */
      inline-size: var(--ds-size-1);
      block-size: var(--ds-size-1);
      --ds-site-menu-account-ink: var(--ds-text-secondary-neutral);
      color: var(--ds-site-menu-account-ink);
    }

    .ds-site-menu__account:hover {
      --ds-site-menu-account-ink: var(--ds-text-primary-neutral);
    }

    .ds-site-menu__account > svg {
      inline-size: var(--ds-size-2);
      block-size: var(--ds-size-2);
    }

    /* A line of prose at the foot: an address, a line about the place, the year.
       Prose and not a link, and it is the one thing here that is allowed to be a
       sentence. */
    /* A control the menu carries: laid out, not styled, because what stands here
       is a component with a dress of its own. */
    .ds-site-menu__extra {
      display: flex;
      align-items: center;
      gap: var(--ds-rhythm-element);
    }

    .ds-site-menu__say {
      --ds-site-menu-say-face: var(--ds-font-label-face);
      --ds-site-menu-say-size: var(--ds-font-label-size);
      --ds-site-menu-say-weight: var(--ds-font-label-weight);
      --ds-site-menu-say-leading: var(--ds-font-label-leading);
      --ds-site-menu-say-tracking: var(--ds-font-label-tracking);
      margin: 0;
      font-family: var(--ds-site-menu-say-face);
      font-size: var(--ds-site-menu-say-size);
      font-weight: var(--ds-site-menu-say-weight);
      line-height: var(--ds-site-menu-say-leading);
      letter-spacing: var(--ds-site-menu-say-tracking);
      --ds-site-menu-say-ink: var(--ds-text-tertiary-neutral);
      color: var(--ds-site-menu-say-ink);
      text-wrap: pretty;
    }

    /* ==========================================================================
       Site shell

       The CSS behind <SiteShell> and <SiteSection>. Private, like every other
       component's.

       A website is three bands: a header, the page, a footer. That is the whole
       shape, and this component is not more than it.

       ── It scrolls as one ─────────────────────────────────────────────────────
       The difference between this and the application shell. There the bar and
       the column hold still and only the middle moves, because a table of four
       hundred rows must not take the navigation off the screen. Here a reader
       scrolling to the footer expects the top to leave: a site is a document,
       and documents scroll. So this declares no height and no scroller of its
       own. Nothing to fight the page with.

       ── Two questions, and they are not the same one ─────────────────────────
       HOW WIDE THE PAGE RUNS is `width`, on the shell, and it is the default
       every band takes. `full` runs to the glass with the page's gutter and
       nothing else. `boxed` holds every band to a container centred in the
       window, which is what most sites are.

       HOW TIGHT ONE BAND HOLDS ITS WORDS is `measure`, on the section, and it
       overrides the page's default for that band alone. `reading` is a column of
       prose. `full` is a picture or a map that wants the edges whatever the page
       is doing.

       Two settings because they are two decisions: a boxed page still wants the
       odd band to break out, and a full page still wants its paragraphs held.

       ── Nothing here uses margin ─────────────────────────────────────────────
       Spacing is padding, which belongs to the box that draws it, or gap, which
       belongs to the box that holds them: boxes inside boxes, and every distance
       owned by something. A margin belongs to neither and escapes both.

       Centring too, which is the one place margin usually survives: a grid whose
       single track is `min(100%, measure)` and whose content is centred puts a
       box in the middle of another with nothing to collapse and nothing to
       inherit.
       ========================================================================== */

    /* The page's own gutter, declared once rather than left as a fallback on
       each of the four rules that read it. Written `var(--ds-site-gutter,
       var(--ds-space-6))` in four places it was a default nobody could find and
       four chances to disagree. On the section too, because a section stands
       alone: LegalPage and ContactPage render one outside any shell, and an
       undeclared name in a bare var() is silent everywhere, so the band's
       padding-inline collapsed to 0 and the title sat 1px from the edge. */
    .ds-site,
    .ds-site__section {
      --ds-site-gutter: var(--ds-space-6);
    }

    .ds-site {
      /* So the sheet below has something to be absolute against. `relative` and
         not a container: a container would take the fixed descendants with it,
         which is the mistake this file made once already. */
      position: relative;
      /* NOT a container, and this is load-bearing: `container-type` makes an
         element the containing block for every `position: fixed` descendant, and
         a site holds one — the drawer the header's menu opens. Declared here it
         collapsed that drawer to one pixel by nothing, at x -1. The same trap the
         `backdrop-filter` on the docs bar set, and it is written in CLAUDE.md.
         The width is measured further down, on a box that holds no such thing. */
      display: flex;
      flex-direction: column;
      /* At least the window, so a short page still puts its footer at the bottom
         rather than halfway up the glass. */
      min-block-size: 100dvh;
      background-color: var(--ds-surface-0);
      --ds-site-ink: var(--ds-text-primary-neutral);
      color: var(--ds-site-ink);
    }

    /* Takes what the header and the footer leave, so a page of two paragraphs has
       a footer at the foot. */
    .ds-site__main {
      flex: 1 0 auto;
      min-inline-size: 0;
    }

    /* ── One width, and everything on the page reads it ───────────────────────
       The bar, the bands and the footer are all held to the SAME track, and that
       is the whole rule. Each of those three ships with a width of its own — a
       header used on a page that is not a site still has to hold its row to
       something — and inside a site the site answers for all three.

       Without this they answered separately and the page came out at three
       widths: measured, a bar of 1152 centred inside a site of 1278, stopping
       short of the content under it for no reason a reader could see. Two numbers
       deciding one thing is not a setting, it is a bug with an option.

       The hooks are the components' own, and each one says why it is there: the
       bar has to line up with the page under it, and only the page knows what it
       is. */
    .ds-site .ds-header,
    .ds-site .ds-footer {
      --ds-header-width: var(--ds-site-band);
      --ds-header-gutter: var(--ds-site-gutter);
      --ds-footer-width: var(--ds-site-band);
      --ds-footer-gutter: var(--ds-site-gutter);
    }

    /* ── The page's own width ─────────────────────────────────────────────────
       Not a box around the site: the header, the sections and the footer all run
       the full width either way, because a band of colour that stops short is a
       band with a margin. What changes is the track each band's middle is held
       to, and that is one variable the sections read. */
    .ds-site--full {
      --ds-site-band: 100%;
    }

    .ds-site--boxed {
      --ds-site-band: var(--ds-site-container, 75rem);
    }

    /* ── A section is a band, and a band owns two things ──────────────────────
       The section owns the ground and the vertical rhythm, and runs the full
       width of whatever holds it, because a band of colour that stops short is a
       band with a margin. It is also what CENTRES: a grid of one track, so the
       band inside it needs no margin either.

       The band owns the gutter, and the gutter is INSIDE the track. That is the
       part that was wrong: with the padding outside it, the container measured
       1200 of track and the bar measured 1200 of box, so the words in the bar
       started 24px right of the words under them. Both are boxes now, both count
       their padding, and both are the same box. */
    .ds-site__section {
      display: grid;
      justify-content: center;
      grid-template-columns: min(100%, var(--ds-site-band, 100%));
      background-color: var(--ds-surface-0);
      padding-block: var(--ds-rhythm-section);
    }

    .ds-site__section--raised {
      background-color: var(--ds-surface-1);
    }

    /* ── The inverse band ─────────────────────────────────────────────────────
       The strongest ground a page owns, for the one section that has to read as
       a statement: a closing invitation, a banner, the band a project paints in
       its brand. The pair is the system's own inverse, and the hooks follow the
       auth panel's arrangement word for word: a project repaints via
       `--ds-site-section-ground` and `--ds-site-section-ink`, both or neither,
       never half of a pair.

       The `color` line is the part a hand-built band gets wrong, and the first
       consumer paid for it before this variant existed: `.ds-site` resolves its
       `color` at the shell, so a band that re-points `--ds-text-primary-neutral`
       changes nothing the words INHERIT, only what is read again further down.
       The title inside a painted band came out the light theme's near-black on a
       dark ground, and in dark the defect was invisible because the page's ink
       is already light. So the section resolves `color` again, on itself.

       What the CONTENTS become is not decided here: the section wears
       `data-ground="inverse"`, on the painting element the way the toast wears
       it on its root, and `tokens/color.css` owns that scope, the same division
       of labour every mode has. */
    .ds-site__section--inverse {
      background-color: var(--ds-site-section-ground, var(--ds-inverse));
      color: var(--ds-site-section-ink, var(--ds-on-inverse));
    }

    /* A band with nothing above or below it: a hero that meets the header, a
       strip of pictures that meets the band under it. */
    .ds-site__section--flush {
      padding-block: 0;
    }

    .ds-site__band {
      box-sizing: border-box;
      min-inline-size: 0;
      padding-inline: var(--ds-site-gutter);
    }

    /* ── What one band overrides for itself ───────────────────────────────────
       The track is the section's, so a band that wants its own says so and the
       section takes it. */
    .ds-site__section:has(.ds-site__band--reading) {
      grid-template-columns: min(100%, var(--ds-site-measure, 44rem));
    }

    /* No track and no gutter: the content is the band. A picture, a map, a strip
       of photographs. Anything inside it that wants a measure asks for one. */
    .ds-site__section:has(.ds-site__band--full) {
      grid-template-columns: 100%;
    }

    .ds-site__band--full {
      padding-inline: 0;
    }

    /* Off screen until the keyboard reaches it, and the first thing it reaches. */
    .ds-site__skip {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-site-skip-face: var(--ds-font-label-face);
      --ds-site-skip-tracking: var(--ds-font-label-tracking);
      --ds-site-skip-size: var(--ds-font-label-size);
      --ds-site-skip-weight: var(--ds-font-label-weight);
      --ds-site-skip-leading: var(--ds-font-label-leading);
      position: absolute;
      inset-inline-start: var(--ds-space-3);
      inset-block-start: calc(var(--ds-space-3) * -1);
      z-index: 2;
      padding: var(--ds-space-2) var(--ds-space-3);
      border-radius: var(--ds-radius-2);
      background-color: var(--ds-surface-1);
      --ds-site-skip-ink: var(--ds-text-primary-neutral);
      color: var(--ds-site-skip-ink);
      font-family: var(--ds-site-skip-face);
      font-size: var(--ds-site-skip-size);
      letter-spacing: var(--ds-site-skip-tracking);
      font-weight: var(--ds-site-skip-weight);
      line-height: var(--ds-site-skip-leading);
      text-decoration: none;
      transform: translateY(-100%);
    }

    .ds-site__skip:focus-visible {
      inset-block-start: var(--ds-space-3);
      outline: 2px solid var(--ds-focus);
      outline-offset: 2px;
      transform: none;
    }

    /* ── The gutter on a narrow page ──────────────────────────────────────────
       24 down each side of a 1200 page is air; 24 down each side of a 375 one is
       an eighth of the screen spent on nothing, and it is the eighth the words
       needed. 16, which is the step below on the same ramp.

       Set on the bands and the two bars rather than on the site, because a
       container is read by what is INSIDE it and the site is the container. Same
       arrangement the app shell has had all along; this is the page that did not.
       ========================================================================== */
    /* The section measures, because a band is what the gutter is on and a section
       holds nothing fixed. */
    .ds-site__section {
      container-type: inline-size;
      container-name: ds-band;
    }

    @container ds-band (width <= 40rem) {
      .ds-site__band {
        --ds-site-gutter: var(--ds-space-4);
      }
    }

    /* ── The anchor under the bar ─────────────────────────────────────────────
       A sticky bar sits over the very line an anchor scrolls to: the address
       with the hash lands its target at the top of the window, and the top of
       the window is where the bar is. Every target inside a site that keeps its
       bar takes a margin of the bar's tier, so the landing line starts under it
       rather than behind it. The first consumer wrote this by hand as the 64px
       of `.gl-capo`; `--ds-site-anchor` is the hook for a project whose bar
       stands taller. */
    .ds-site:has(.ds-header--sticky) :target {
      scroll-margin-block-start: var(--ds-site-anchor, var(--ds-size-6));
    }

    /* ── The navigation sheet ─────────────────────────────────────────────────
       The same arrangement the application shell has, word for word, because it
       is the same thing: what opens on a narrow screen is the places the bar
       cannot show, and it lies over the page.

       Absolute against the SITE and not fixed against the window. A modal dialog
       goes into the top layer, and the top layer is the window's: a site shown
       inside a panel or a device canvas had its menu pinned to the far edge of
       the browser, outside the phone it belongs to. Measured: the sheet's right
       edge at 1280 of the window while the canvas ended at 1036.

       No `z-index` in the pair. Both are positioned and the header, the page and
       the footer are not, so both paint above the content whatever the order;
       between the two, document order decides, and the sheet is the site's last
       child while `::before` is its first.
       ========================================================================== */
    .ds-site > .ds-drawer--inline {
      /* FIXED, and to the window, which is what a menu covering the screen means:
         absolute against the site it was as tall as the PAGE, so the places sat at
         the top and the foot of the menu was two thousand pixels below the fold.
         A menu is as tall as what you can see.
         Fixed is safe here because this drawer is not modal: `mode="inline"` never
         calls `showModal`, so it stays in the document and a canvas that wants to
         hold it can, with a transform. The docs canvas does exactly that. A modal
         could not be held by anything, which is why it is not one. */
      position: fixed;
      /* THE WHOLE SCREEN, and this is what a site's menu is on a phone: you press
         the mark and the places are what there is, edge to edge and top to bottom.
         A 320 panel down one side is an application's idea — there the sheet is
         the column that stands beside the page on a wide screen, so it keeps the
         column's width and the page stays visible behind it. A site has no column
         and nothing to keep, and a strip of navigation with a slice of the page
         showing past it reads as a drawer somebody left open. */
      inset: 0;
      inline-size: 100%;
      max-inline-size: none;
      max-block-size: none;
      /* No width animation, for the reason the application shell gives: laid over
         the page there is nothing beside it to move, and a sheet whose geometry is
         in the hands of the motion is a sheet stuck at nought wide whenever a
         frame does not paint. */
      transition: none;
      /* Nothing of a panel left: no corners, no edges, and the ground is the
         page's own rather than a sheet floating on it. */
      border-radius: 0;
      border-width: 0;
      background-color: var(--ds-surface-0);
    }

    /* No scrim, and nothing to dim: the sheet IS the screen. The application
       shell keeps one because its sheet covers a part and the rest has to read as
       behind; here there is no rest. */

    /* ==========================================================================
       Hero

       The CSS behind <Hero>. Private, like every other component's.

       The first band of a page, and the one that has to say what this is before
       anybody scrolls.

       ── What a hero is now, and what it stopped being ────────────────────────
       It stopped being a photograph with words laid over it. That arrangement
       asks one image to be a background AND to carry a sentence: the text needs
       a scrim to be readable, the scrim ruins the picture, and the whole thing
       moves under the words at every window size. It reads as a website from
       twenty years ago because it is one.

       What it is instead is a column of type with the room to be read, and a
       picture BESIDE or UNDER it rather than behind it — a piece of the page
       with its own edges, which is a thing a reader can look at rather than
       squint through.

       ── The furniture, in the order it is read ───────────────────────────────
       An eyebrow, which is usually a pill saying what is new. A title. A line
       under it in the reading colour. What to do, as two actions and not five.
       A quiet line under those for the thing that removes the last objection —
       no card, free for a month, cancel whenever.

       Every one of them is optional and the arrangement holds without any of
       them, because a hero with a title and one button is a perfectly good hero
       and the commonest one there is.

       ── The text is narrower than the band ───────────────────────────────────
       Always, and this is the whole difference between a hero that reads and one
       that does not. A headline at 56px running the width of a desk is a
       headline nobody finishes: the eye loses the start of the next line on the
       way back. The measure here is its own, tighter than the page's, because a
       title is read at a glance and a paragraph is not.
       ========================================================================== */

    .ds-hero {
      /* It measures itself, and until now nothing did.
         The rule at the foot of this file asks `@container (width <= 40rem)` and
         no ancestor was ever a container, so the query could not match at any
         width: the stacked arrangement was dead the day it was written, and the
         hero stayed in two columns on a phone. Every other component in the
         package that asks a container question declares its own container; this
         one asked and nobody answered.
         The hero is the right thing to measure: what decides whether the words
         and the picture sit side by side is the room the HERO has, not the room
         the window has. */
      container-type: inline-size;
      display: grid;
      gap: var(--ds-rhythm-block);
      align-items: center;
    }

    /* Beside: the words take what they need and the picture takes the rest. Not
       halves — a column of type at half a wide window is a column with nothing in
       it, and the picture is the thing that can afford to be smaller. */
    .ds-hero--split {
      grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr);
    }

    /* Under, and centred: the arrangement a product shot wants, because a
       screenshot under a centred title is the shape everybody now reads as "here
       is the thing itself". */
    .ds-hero--stacked {
      justify-items: center;
      text-align: center;
    }

    /* Nothing but the words. */
    .ds-hero--bare .ds-hero__media {
      display: none;
    }

    .ds-hero__text {
      display: flex;
      flex-direction: column;
      gap: var(--ds-rhythm-element);
      /* Its own measure, tighter than the page's: a headline that runs the width
         of a desk is a headline nobody finishes. */
      max-inline-size: var(--ds-hero-measure, 40rem);
    }

    .ds-hero--stacked .ds-hero__text {
      align-items: center;
    }

    /* The pill above the title. It is a link as often as it is a label, so it
       takes whatever it is given and only owns the distance under it, which the
       stack above already draws. */
    .ds-hero__eyebrow {
      display: flex;
    }

    /* The title is an `h1` and takes what an `h1` is: the face, the size, the
       leading, the weight and the tracking are all declared once in the base and
       were declared a second time here, which is two places to change a headline
       and one of them wrong the first time somebody moves the other. Measured
       against a bare `h1` in the same place: identical on every one of them.

       What is left is the only thing this heading wants that the base cannot
       give every heading — a headline breaks where the sense is rather than
       where the box ends, and a paragraph does not. */
    /* The title, and it declares its type where before it only asked for balance.
       Rendered as a bare `<h1>`, it was taking heading 1 from the element selector
       in `roles/`, which is a decision made by the tag rather than by the hero: it
       read back as `--ds-font-heading-1-face` beside a `--ds-hero-note-face` on
       the line under it, and one component was answering in two vocabularies.
       The values are the ones the `<h1>` already had, so nothing moves. */
    .ds-hero__title {
      --ds-hero-title-face: var(--ds-font-heading-1-face);
      --ds-hero-title-size: var(--ds-font-heading-1-size);
      --ds-hero-title-weight: var(--ds-font-heading-1-weight);
      --ds-hero-title-leading: var(--ds-font-heading-1-leading);
      --ds-hero-title-tracking: var(--ds-font-heading-1-tracking);
      --ds-hero-title-ink: var(--ds-text-primary-neutral);
      font-family: var(--ds-hero-title-face);
      font-size: var(--ds-hero-title-size);
      font-weight: var(--ds-hero-title-weight);
      line-height: var(--ds-hero-title-leading);
      letter-spacing: var(--ds-hero-title-tracking);
      color: var(--ds-hero-title-ink);
      text-wrap: balance;
    }

    .ds-hero__description {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-hero-description-face: var(--ds-font-copy-face);
      --ds-hero-description-tracking: var(--ds-font-copy-tracking);
      --ds-hero-description-size: var(--ds-font-copy-size);
      --ds-hero-description-weight: var(--ds-font-copy-weight);
      --ds-hero-description-leading: var(--ds-font-copy-leading);
      font-family: var(--ds-hero-description-face);
      font-size: var(--ds-hero-description-size);
      letter-spacing: var(--ds-hero-description-tracking);
      font-weight: var(--ds-hero-description-weight);
      line-height: var(--ds-hero-description-leading);
      --ds-hero-description-ink: var(--ds-text-secondary-neutral);
      color: var(--ds-hero-description-ink);
      text-wrap: pretty;
    }

    /* What to do. Two of them: the thing to do, and the thing to do instead. */
    .ds-hero__actions {
      display: flex;
      flex-wrap: wrap;
      gap: var(--ds-space-3);
      padding-block-start: var(--ds-space-2);
    }

    .ds-hero--stacked .ds-hero__actions {
      justify-content: center;
    }

    /* The line that removes the last objection. Quiet, because it is not the
       offer: it is the reason to accept it. */
    .ds-hero__note {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-hero-note-face: var(--ds-font-label-face);
      --ds-hero-note-tracking: var(--ds-font-label-tracking);
      --ds-hero-note-size: var(--ds-font-label-size);
      --ds-hero-note-weight: var(--ds-font-label-weight);
      --ds-hero-note-leading: var(--ds-font-label-leading);
      font-family: var(--ds-hero-note-face);
      font-size: var(--ds-hero-note-size);
      letter-spacing: var(--ds-hero-note-tracking);
      font-weight: var(--ds-hero-note-weight);
      line-height: var(--ds-hero-note-leading);
      --ds-hero-note-ink: var(--ds-text-tertiary-neutral);
      color: var(--ds-hero-note-ink);
    }

    /* The picture. A piece of the page with its own edges, not a ground behind
       the words: it holds a shape so the band is the height of a decision rather
       than of whatever file was uploaded. */
    .ds-hero__media {
      display: block;
      inline-size: 100%;
      aspect-ratio: var(--ds-hero-ratio, 4 / 3);
      overflow: hidden;
      border-radius: var(--ds-radius-3);
      background-color: var(--ds-surface-2);
    }

    .ds-hero--stacked .ds-hero__media {
      --ds-hero-ratio: 16 / 9;
      max-inline-size: var(--ds-hero-shot, 60rem);
    }

    .ds-hero__media > img,
    .ds-hero__media > video {
      display: block;
      inline-size: 100%;
      block-size: 100%;
      object-fit: cover;
    }

    /* ── Narrow ───────────────────────────────────────────────────────────────
       Beside becomes under. There is no width at which two columns of anything
       are worth having on a phone, and the picture goes below the words rather
       than beside them because the words are what was being looked for. */
    @container (width <= 40rem) {
      /* The CHILDREN, not the hero. A container is queried by what is inside it,
         and the hero is the container: asking it to restyle itself was asking a
         box to answer a question about its own width, which no `@container` rule
         can do. Written as `.ds-hero--split { grid-template-columns: 100% }` it
         matched nothing at any width.
         Each child taking the whole track puts them on rows instead of columns,
         which is the same arrangement with the grid left alone. */
      .ds-hero--split > * {
        grid-column: 1 / -1;
      }
    }

    /* ==========================================================================
       Tab bar

       The CSS behind <TabBar>. Private, like every other component's.

       The row of places along the bottom of a phone.

       ── At the bottom, because that is where a thumb is ───────────────────────
       And below the safe area, which is the strip a phone keeps for its own home
       indicator. `env(safe-area-inset-bottom)` is that strip, and a bar that
       ignores it is a bar whose last row of pixels cannot be pressed.

       ── The height is the industry's, not ours ────────────────────────────────
       64, which is what Material 3 Expressive shrank its navigation bar to from
       80, and comfortably over the 49 of an iOS tab bar — both before the phone's
       own strip is added under them. The mark is 24, which is Material's exactly
       and one point off Apple's 25. The word is 12, between Apple's 10 and
       Material's 14.

       With the word taken away the row keeps 48, which is Material's floor under a
       target and four over Apple's.

       No pill behind the chosen mark, which is the one place this parts company
       with Material 3: iOS marks the place with ink alone, and a filled cell in a
       row of five reads as a button somebody left pressed.

       ── Every place is the same width ─────────────────────────────────────────
       Not the width of its word. A row where "Home" is narrow and "Notifications"
       is wide is a row whose targets move every time the labels are translated.

       ── The word stays ────────────────────────────────────────────────────────
       A row of marks with no words is a row of guesses for anybody who has not
       learnt them yet, and nobody has on their first visit. `--marks` exists for
       the product that has measured otherwise.
       ========================================================================== */

    .ds-tab-bar {
      /* And the one you are ON, which is the other thing this file was
         saying with the same number. */
      --ds-tab-bar-weight-current: var(--ds-current-weight);
      box-sizing: border-box;
      display: flex;
      align-items: stretch;
      inline-size: 100%;
      border-block-start: 1px solid var(--ds-border-subtle);
      background-color: var(--ds-surface-0);
      /* The bar's own height, plus whatever the phone keeps for itself. */
      padding-block-end: env(safe-area-inset-bottom, 0px);
    }

    /* Equal shares of the row, so the targets do not move when the words change
       length or language. */
    .ds-tab-bar__item {
      /* The floor below is the whole height, not the height of what is inside it.
         Measured content-box first: a floor of 64 plus 16 of padding came out 80,
         which is Material's standard bar rather than the one we said we were
         drawing. */
      box-sizing: border-box;
      display: flex;
      flex: 1 1 0;
      min-inline-size: 0;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      gap: var(--ds-space-1);
      padding-block: var(--ds-space-2);
      --ds-tab-bar-item-ink: var(--ds-text-secondary-neutral);
      color: var(--ds-tab-bar-item-ink);
      text-decoration: none;
      transition: color var(--ds-duration) var(--ds-ease);
    }

    /* The floor under a target, and the whole share of the row is the target: on a
       phone the thing being aimed at is the column, not the mark in it. */
    .ds-tab-bar__item {
      min-block-size: var(--ds-size-6);
    }

    .ds-tab-bar__mark {
      position: relative;
      display: inline-flex;
      align-items: center;
      justify-content: center;
      /* 24 here rather than `1lh`: the word under it is 12, and a mark tied to
         that line would be too small to aim at. This is the one slot in the
         system standing over its line rather than beside it. */
      inline-size: var(--ds-size-1);
      block-size: var(--ds-size-1);
    }

    .ds-tab-bar__mark > svg {
      inline-size: 100%;
      block-size: 100%;
    }

    /* On the corner of the mark, out of the flow, so a count does not push the
       word off centre. Half of it hangs off the drawing, which is where every
       phone puts one, and the ring it carries keeps the two apart. */
    .ds-tab-bar__badge {
      position: absolute;
      inset-block-start: 0;
      inset-inline-start: 50%;
      transform: translate(-25%, -50%);
    }

    .ds-tab-bar__label {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-tab-bar-label-face: var(--ds-font-small-face);
      --ds-tab-bar-label-tracking: var(--ds-font-small-tracking);
      --ds-tab-bar-label-size: var(--ds-font-small-size);
      --ds-tab-bar-label-leading: var(--ds-font-small-leading);
      max-inline-size: 100%;
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
      font-family: var(--ds-tab-bar-label-face);
      font-size: var(--ds-tab-bar-label-size);
      letter-spacing: var(--ds-tab-bar-label-tracking);
      line-height: var(--ds-tab-bar-label-leading);
    }

    .ds-tab-bar__item:hover {
      --ds-tab-bar-item-ink: var(--ds-text-primary-neutral);
      color: var(--ds-tab-bar-item-ink);
    }

    .ds-tab-bar__item:focus-visible {
      outline: 2px solid var(--ds-focus);
      outline-offset: -2px;
      border-radius: var(--ds-radius-1);
    }

    /* The place you are. Ink and weight, not a fill: a filled cell in a row of
       five reads as a button somebody left pressed. */
    .ds-tab-bar__item[aria-current] {
      --ds-tab-bar-item-ink: var(--ds-text-primary-neutral);
      color: var(--ds-tab-bar-item-ink);
    }

    .ds-tab-bar__item[aria-current] .ds-tab-bar__label {
      font-weight: var(--ds-tab-bar-weight-current);
    }

    /* Marks only. The row loses a line, so it keeps the floor under a target
       through its own height rather than through what it holds — and the floor is
       48, not 40. Apple asks for 44 and Material for 48; 40 was under both, which
       is a target nobody had measured. */
    .ds-tab-bar--marks .ds-tab-bar__item {
      min-block-size: var(--ds-size-4);
    }

    /* ==========================================================================
       App shell

       The CSS behind <AppShell>. Private, like every other component's.

       Three bands, which is what a web application is:

         head     holds still at the top
         body     takes the rest and scrolls
         foot     holds still at the bottom, when there is one

       ── The sidebar is not a fourth band ─────────────────────────────────────
       It was, and that is what made this complicated. A column down the side is
       a thing INSIDE the body on a wide screen and a drawer over it on a narrow
       one; treating it as structure meant the grid had to change shape at the
       break and every rule had to know about it.

       As a piece of the body it costs one rule: below the break it is not drawn
       there, and the same sidebar is drawn in the drawer instead.

       ── Two frames, and the difference is what scrolls ───────────────────────
       `app` is a window: the bands hold still and the body scrolls on its own. A
       dashboard, an inbox, a settings screen.

       `reading` is a document: the whole thing scrolls as one, the column sticks
       under the head, and the middle is held to a measure. A docs site, a
       handbook, a long article with a menu beside it.

       ── It is the window ──────────────────────────────────────────────────────
       At least the viewport tall by default, the way the auth shell is: an
       application is the whole window in every product, and a shell that
       waited for its caller to say so stood at content height with nothing
       under it. A panel showing one, the playground's frame, says
       `--ds-app-shell-size: 100%` and the shell takes the panel's height.

       ── The break is measured, not assumed ───────────────────────────────────
       `--ds-break-tablet`, 64rem, which is 1024: the widest tablet this package
       draws for, so the column is gone AT a tablet rather than under one. Asked
       with `@container` and not a media query, because this is shown inside
       panels narrower than the window and inside canvases wider than it, and a
       media query answers about the window in both. The query writes the number
       because a container query cannot read a custom property in its condition;
       the check asserts it is one a token names.
       ========================================================================== */

    .ds-app-shell {
      /* The page's gutter, and the one hook anybody reaches for first.
         One name for both frames: the app frame pads its body with it and the
         reading frame pads its own and hands it to the bar, so a project that
         wants 32 instead of 24 says it once instead of writing CSS over this
         component, which is the moment a consumer starts owning pieces of it. */
      --ds-app-shell-gutter: var(--ds-space-6);
      container-type: inline-size;
      /* Named, so the panels in the top layer can ask the shell how wide the
         screen is: a drawer and a menu become sheets from the bottom under the
         phone break, and a modal dialog has no width of its own to ask. */
      container-name: ds-app-shell;
      /* What the navigation sheet is positioned against. A modal dialog would be
         positioned against the window, which is right for a screen that IS the
         window and wrong for one shown inside anything. */
      position: relative;
      display: flex;
      flex-direction: column;
      /* The window, or the frame that shows one. An application is exactly the
         window: the bands hold still and the body scrolls inside it, which only
         happens when the shell's height is definite. At a minimum it grew with
         its rows and put the tab bar under the fold, measured 1133 in a window
         of 812 on the pilot, 5 September 2026. `reading` is a document and is
         at least the window, since a document scrolls as one. */
      block-size: var(--ds-app-shell-size, 100dvh);
      min-block-size: 0;
    }

    .ds-app-shell--reading {
      block-size: auto;
      min-block-size: var(--ds-app-shell-size, 100dvh);
    }

    .ds-app-shell__head {
      flex: none;
      display: flex;
      align-items: center;
      min-inline-size: 0;
    }

    .ds-app-shell__head > .ds-header {
      flex: 1;
      min-inline-size: 0;
    }

    .ds-app-shell__body {
      /* The gutter counts, or a body told to be 100% wide is 100% PLUS its two
         gutters and runs past the shell it sits in: measured 407 inside a 375
         shell, and 932 inside 900. The measure below has the same problem in
         reverse — 76rem of measure came out 1264 on screen. */
      box-sizing: border-box;
      flex: 1;
      display: flex;
      min-inline-size: 0;
      /* Un livello solo, e il corpo e' quello. Il foglio della navigazione conta
         sull'ordine del documento per stare sopra la pagina — e' l'ultimo figlio
         — ma l'ordine perde contro qualunque discendente posizionato che si dia
         un z-index: la testa fissa di una tabella ne ha uno, e su un telefono si
         vedeva passare sopra il foglio aperto (misurato il 25 agosto 2026). Con
         un contesto di impilamento qui, tutto quel che sta nel corpo resta nel
         corpo, e l'ordine torna a decidere come dice la regola in fondo a questo
         file. Chi deve davvero uscirne esce dal TOP LAYER — popover e dialog —
         che nessun contesto trattiene. */
      position: relative;
      z-index: 0;
      min-block-size: 0;
    }

    .ds-app-shell__aside {
      flex: none;
      display: flex;
      min-inline-size: 0;
      min-block-size: 0;
    }

    /* Off screen until the keyboard reaches it, and the first thing it reaches:
       the same link the site shell has, since 12 September 2026 (P4.3). An admin
       screen puts a bar and a whole column before its first word. */
    .ds-app-shell__skip {
      --ds-app-shell-skip-face: var(--ds-font-label-face);
      --ds-app-shell-skip-tracking: var(--ds-font-label-tracking);
      --ds-app-shell-skip-size: var(--ds-font-label-size);
      --ds-app-shell-skip-weight: var(--ds-font-label-weight);
      --ds-app-shell-skip-leading: var(--ds-font-label-leading);
      --ds-app-shell-skip-ink: var(--ds-text-primary-neutral);
      position: absolute;
      inset-inline-start: var(--ds-space-3);
      inset-block-start: calc(var(--ds-space-3) * -1);
      z-index: 2;
      padding: var(--ds-space-2) var(--ds-space-3);
      border-radius: var(--ds-radius-2);
      background-color: var(--ds-surface-1);
      color: var(--ds-app-shell-skip-ink);
      font-family: var(--ds-app-shell-skip-face);
      font-size: var(--ds-app-shell-skip-size);
      letter-spacing: var(--ds-app-shell-skip-tracking);
      font-weight: var(--ds-app-shell-skip-weight);
      line-height: var(--ds-app-shell-skip-leading);
      text-decoration: none;
      transform: translateY(-100%);
    }

    .ds-app-shell__skip:focus-visible {
      inset-block-start: var(--ds-space-3);
      outline: 2px solid var(--ds-focus);
      outline-offset: 2px;
      transform: none;
    }

    .ds-app-shell__main {
      /* The main is what scrolls, so it is also the containing block of every
         absolutely positioned thing inside it. Without this a hidden label or a
         clipped choice input, positioned absolute at its static place four
         thousand pixels down a long view, was measured against the shell, and
         the DOCUMENT grew to hold it: the page scrolled past the shell's edge
         into 3,484px of nothing. Measured on the first product's roadmap, 11
         September 2026, html 4662 against a shell of 1178. */
      position: relative;
      flex: 1;
      display: flex;
      flex-direction: column;
      min-inline-size: 0;
      min-block-size: 0;
    }

    .ds-app-shell__foot {
      flex: none;
      min-inline-size: 0;
    }

    /* How much of the window's foot this shell is already standing in, for
       anything that floats there: a selection bar reads it and lifts itself by
       it. The shell is the only thing that knows it has a row of places at the
       bottom — the bar is fixed to the window and cannot see it — so the shell
       says so, under its own name, and whatever reads it does so with a fallback
       of nothing.

       The number is the row's own: the floor of a tab bar's target, its line, and
       whatever the phone keeps for itself under that. */
    .ds-app-shell:has(> .ds-app-shell__foot .ds-tab-bar) {
      --ds-app-shell-foot-room: calc(var(--ds-size-6) + 1px + env(safe-area-inset-bottom, 0px));
    }

    /* ── The window ───────────────────────────────────────────────────────────
       The bands hold still and the body scrolls inside itself, so a table of
       four hundred rows never takes the navigation off the screen. */
    .ds-app-shell--app .ds-app-shell__aside {
      border-inline-end: 1px solid var(--ds-border-subtle);
    }

    .ds-app-shell--app .ds-app-shell__main {
      overflow-y: auto;
      padding: var(--ds-app-shell-gutter);
    }

    /* ── Una misura anche nel guscio-finestra ─────────────────────────────────
       `--ds-app-shell-measure` esisteva solo per il documento. Un prodotto le cui
       pagine sono poche informazioni per riga non guadagna niente a stirarle su
       una finestra da 2000px, e la decisione e' sua: qui il valore di partenza e'
       `none` e non cambia niente per nessuno.

       Sul CONTENUTO e non sul guscio, come nel documento e per la stessa ragione:
       la barra in alto e' una fascia, corre quanto la stanza in cui sta e disegna
       la propria linea fino ai due bordi. Capando il guscio la barra si fermava
       prima dei margini e si staccava dalla propria pagina. */
    .ds-app-shell--app .ds-app-shell__main > * {
      max-inline-size: var(--ds-app-shell-measure, none);
      margin-inline: auto;
      /* E la larghezza per intero. Un margine automatico sull'asse trasversale di
         un contenitore flex fa diventare il figlio grande quanto il suo contenuto:
         una griglia di card e una pagina di prosa si sono strette a seicento pixel
         mentre una pagina-record, che si allarga da sola, sembrava a posto
         (proprietario, 15 settembre 2026). E' la seconda volta in un giorno, ed e'
         la stessa riga che la regola di lettura qui sopra porta da sempre. */
      inline-size: 100%;
    }

    /* ── The document ─────────────────────────────────────────────────────────
       One scroll for everything, the column sticking where it was put, and the
       middle held to a measure: a line of prose that runs the width of a desk is
       a line nobody finishes. The height goes back to what the content asks for,
       because a document is as long as it is. */
    .ds-app-shell--reading {
      block-size: auto;
      overflow-y: auto;
    }

    /* The bar lines up with the page under it, and the page is this shell.
       `Header` exposes both of these as hooks and says why: the bar has to stand
       over the column beneath it and only the page knows what its own gutter is.
       Left unset, the bar kept its own measure of 72rem against the body's 76 and
       centred itself separately: measured, the bar starting 32 right of the body
       and the brand 72 right of the column it is supposed to sit over. */
    .ds-app-shell--reading .ds-header {
      --ds-header-width: var(--ds-app-shell-measure, 76rem);
      --ds-header-gutter: var(--ds-app-shell-gutter);
    }

    /* The measure holds the BODY, and the bands run the width of the screen.
       It held the whole shell, which capped the head at 76rem and centred it: on
       a wide window the bar stopped short of both edges and the line under it
       stopped with it, which reads as a bar that has come loose from its own
       page. A band spans what it sits on. A measure is for reading, and reading
       is what the body does.
       The file used to say a band meant to run the whole width goes ABOVE this
       component rather than inside it. That stopped being true when the shell
       took a `header` slot: the band is inside now, and the rule had to follow
       it rather than the other way round. */
    .ds-app-shell--reading .ds-app-shell__body {
      max-inline-size: var(--ds-app-shell-measure, 76rem);
      margin-inline: auto;
      inline-size: 100%;
    }

    /* The gutter is the BODY's, not the shell's.
       On the shell it pushed the head in with everything else, and a head is a
       band: it runs the width of what it sits on, it carries its own padding, and
       it draws a line under itself that has to reach both edges. Measured with
       the gutter on the shell: the header inset 24 on each side, its own bar
       padding adding 16 more, and the line under it stopping short at both ends
       while looking for all the world like a decision somebody took. */
    .ds-app-shell--reading .ds-app-shell__body {
      padding-inline: var(--ds-app-shell-gutter);
    }

    .ds-app-shell--reading .ds-app-shell__body {
      column-gap: var(--ds-rhythm-block);
    }

    .ds-app-shell--reading .ds-app-shell__aside {
      align-items: start;
    }

    .ds-app-shell--reading .ds-app-shell__main {
      padding-block: var(--ds-rhythm-block);
    }

    /* ── Narrow ───────────────────────────────────────────────────────────────
       The column is not drawn in the body. `display: none` and not a width of
       zero: a column nobody can see is still a column a screen reader reads and
       a keyboard walks through. The same sidebar is drawn in the drawer, which
       the shell opens with the mark it puts beside the bar.

       Written twice, once per setting of `columnFrom`, and it has to be: a
       container query cannot read a custom property in its condition, so the
       threshold is in the query and the class picks which query applies. Both
       numbers are ones `tokens/size.css` names, and the check says so.

       `tablet` is the default and is 64rem: a column and a page beside it fit
       from a tablet up. `desktop` is 80rem, for a column wider than the usual
       16rem, where 1024 would leave a page too narrow to be one.

       The two marks in the bar are governed here rather than in `header.css`,
       and that is the point of putting them here: they have to change hands at
       the same width the column does, and only this component knows that width.
       A bar outside a shell keeps the default the header sets for itself. */
    @container (width <= 64rem) {
      .ds-app-shell--tablet .ds-app-shell__aside {
        display: none;
      }

      .ds-app-shell--tablet .ds-header__mark--sidebar {
        display: none;
      }

      .ds-app-shell--tablet .ds-header__mark--menu {
        display: inline-flex;
      }

      /* The gutter comes in with the window. 24 around a page 1024 wide is air;
         24 around one 375 wide is a twelfth of the screen spent on nothing. */
      .ds-app-shell--tablet {
        --ds-app-shell-gutter: var(--ds-space-4);
      }
    }

    @container (width <= 80rem) {
      .ds-app-shell--desktop .ds-app-shell__aside {
        display: none;
      }

      .ds-app-shell--desktop .ds-header__mark--sidebar {
        display: none;
      }

      .ds-app-shell--desktop .ds-header__mark--menu {
        display: inline-flex;
      }

      .ds-app-shell--desktop {
        --ds-app-shell-gutter: var(--ds-space-4);
      }
    }

    /* Wide, and the bar along the bottom is not needed: the column is doing that
       job. It stays where a screen gives a footer and no column, which is a
       screen that meant it. A sibling rule rather than `:has()` on the shell,
       because the shell is the container and a container cannot be reached by
       its own query.

       And the sheet is not drawn at all, so a window widened while it is open
       does not leave one over the column it duplicates. */
    /* The same argument where the places are a row of links in the bar instead of
       a column beside the page. Above the bar's own break that row is on screen,
       so a row along the bottom is the second navigation and stands down; below
       it, where `HeaderNav` has gone, the bottom row IS the navigation.

       40rem because that is the width the bar itself uses to put its links away,
       and a screen where the two rules disagree is a screen with two navigations
       or none. A product with no row in its bar keeps the old behaviour: a foot
       with nothing else to be is a foot that meant it. */
    @container (width > 40rem) {
      .ds-app-shell__head:has(.ds-header__nav) ~ .ds-app-shell__foot {
        display: none;
      }
    }

    @container (width > 64rem) {
      .ds-app-shell--tablet .ds-app-shell__body:has(.ds-app-shell__aside) ~ .ds-app-shell__foot {
        display: none;
      }

      /* And the room it was keeping goes with it, or a bar floats above nothing. */
      .ds-app-shell--tablet:has(> .ds-app-shell__foot .ds-tab-bar):has(.ds-app-shell__aside) {
        --ds-app-shell-foot-room: 0px;
      }

      .ds-app-shell--tablet > .ds-drawer {
        display: none;
      }
    }

    @container (width > 80rem) {
      .ds-app-shell--desktop .ds-app-shell__body:has(.ds-app-shell__aside) ~ .ds-app-shell__foot {
        display: none;
      }

      /* And the room it was keeping goes with it, or a bar floats above nothing. */
      .ds-app-shell--desktop:has(> .ds-app-shell__foot .ds-tab-bar):has(.ds-app-shell__aside) {
        --ds-app-shell-foot-room: 0px;
      }

      .ds-app-shell--desktop > .ds-drawer {
        display: none;
      }
    }

    /* ── The navigation sheet ─────────────────────────────────────────────────
       The shell's own drawer, laid over its body rather than beside it. Inline
       mode is a panel that PUSHES, which is right for a detail beside a table and
       wrong for a navigation on a phone: there is nothing to push it into.

       Absolute against the shell and not fixed against the window, which is the
       whole reason it is inline at all. A modal dialog goes into the top layer
       and the top layer is the window's, so a shell shown inside a panel or a
       canvas had its navigation pinned to the far edge of the browser. */
    .ds-app-shell > .ds-drawer--inline {
      position: absolute;
      inset-block: 0;
      inset-inline-start: 0;
      max-block-size: none;
      /* And it does not animate its width. Inline mode animates `inline-size`
         because that is what MOVES the thing beside it: a flex row recalculates
         every frame and the table slides over rather than jumping. Laid over the
         body there is nothing beside it, so the animation buys nothing and costs
         the same thing it cost the overlay: the panel's geometry in the hands of
         the motion, and a sheet stuck at zero wide whenever a frame does not
         paint. Measured in that state: the sheet at the shell's own corner, full
         height, and nought wide. */
      transition: none;
      border-radius: 0;
      border-block-width: 0;
      border-inline-start-width: 0;
    }

    /* The page behind, dimmed. The scrim is the shell's because the sheet is the
       shell's: a drawer that is not modal has no backdrop of its own to lend.

       No `z-index` anywhere in this pair, and none is wanted. Both of these are
       positioned and the body and the head are not, so both paint above the
       content whatever their order. Between the two, document order decides:
       `::before` is the shell's first child and the sheet is its last, so the
       sheet lands on top of its own scrim. A layer number here would be a number
       no token holds, invented to buy what the order already gives. */
    .ds-app-shell:has(> .ds-drawer--inline[data-open="true"])::before {
      content: "";
      position: absolute;
      inset: 0;
      background-color: var(--ds-scrim);
    }


    /* ── The sheet IS the column ──────────────────────────────────────────────
       What opens on a narrow screen is the same navigation that stands in the
       body on a wide one, and it is not a panel holding a panel. Left alone it
       was three grounds inside each other — the sheet's, then the column's own,
       then the pill under the current row — which reads as three boxes rather
       than one list.

       So inside the sheet the column stops being a frame: it has no ground of its
       own, because the sheet is the ground; no width of its own, because the
       sheet is the width; and the sheet's body stops padding it, because the
       column already pads its own rows and two paddings make a margin nobody
       asked for. */
    .ds-app-shell > .ds-drawer--inline .ds-drawer__body {
      padding: 0;
    }

    /* No ground of its own: the sheet is the ground. */
    .ds-app-shell > .ds-drawer--inline .ds-sidebar {
      background-color: transparent;
    }

    /* No width of its own: the sheet is the width. Two rules and not one, because
       a box that is sized AND painted has to say how it lays out, and this one
       does neither of those things — it takes them BACK. Split, each says the one
       thing it means. */
    .ds-app-shell > .ds-drawer--inline .ds-sidebar {
      inline-size: 100%;
    }

    /* ==========================================================================
       Auth shell

       The CSS behind <AuthShell>. Private, like every other component's.

       The screen somebody signs in on: a panel that says whose product this is,
       and a card with one step of a form in it. Every screen in a flow is this
       with different children, which is what makes eleven of them feel like one
       place rather than eleven pages that share a logo.

       ── One breakpoint, and it reads the component, not the window ────────────
       Below 60rem the panel stops being a column beside the card and becomes a
       band above it. Not because of a device: at half of 60rem a form column is
       under 28rem, which is where a label and its control stop having room to
       stand side by side and a sentence stops holding a line.

       And it is a CONTAINER query, not a media query. Measured on the
       documentation page: with a media query the shell inside a 610px panel still
       read the 1280px window, laid out two columns, and put the panel and the card
       in the same one — 430px of content with 544px of nothing beside it. A
       component that can be shown inside anything has to answer for its own width.
       That is why the grid is on an inner element: a container cannot query
       itself, only its children can.

       ── The card never fills the screen ───────────────────────────────────────
       A sign-in form is six controls. Given a whole window it becomes six controls
       spread over a metre of glass, so the card keeps its own measure and sits in
       the middle of whatever it is given.

       ── It is the window ──────────────────────────────────────────────────────
       At least the viewport tall, by default, since a door is the whole window
       in every product: the pilot's stood at half a window with nothing under
       it, and the owner read that as the bug it was, 5 September 2026. It had
       no height of its own, on the argument that a component which decides it
       is the window cannot be shown inside anything; the answer is the hook
       below, not a prop every page has to remember. A panel showing one, the
       playground's frame, says `--ds-auth-size: 100%` and the shell takes the
       panel's height instead. A column flex box, so the grid fills whatever
       the shell measures: a percentage height against a minimum resolves to
       nothing, which is how the first fix left the panel at content height.
       ========================================================================== */

    .ds-auth {
      container-type: inline-size;
      box-sizing: border-box;
      /* Declared because it is painted: a box with a ground on it says how it
         lays out, or the next thing put inside it lands wherever the default
         leaves it. Block, and the grid is its one child. */
      display: flex;
      flex-direction: column;
      min-block-size: var(--ds-auth-size, 100dvh);
      background-color: var(--ds-surface-0);
      --ds-auth-ink: var(--ds-text-primary-neutral);
      color: var(--ds-auth-ink);
    }

    /* The grid, one element in, so the container above it has something to
       measure. Stacked is the starting point and the wide arrangement is the
       exception: a shell in a narrow place is the commoner one. */
    .ds-auth__grid {
      box-sizing: border-box;
      display: grid;
      grid-template-columns: 1fr;
      grid-template-rows: auto 1fr;
      grid-template-areas:
        "panel"
        "stage";
      flex: 1;
      min-block-size: 0;
    }

    .ds-auth__panel {
      grid-area: panel;
      position: relative;
      overflow: hidden;
      display: flex;
      flex-direction: column;
      /* The brand opens the panel and the aside closes it: space-between is what
         puts the sentence at the foot, which is where every current reference
         puts it, and a panel with only a brand keeps it at the top. */
      justify-content: space-between;
      gap: var(--ds-rhythm-element);
      padding: var(--ds-rhythm-block);
      /* The INVERSE ground by default, and a hook. This panel is the one part of
         the screen that is purelyely the product's; unpainted it used to be a
         faint raised grey, which on a large screen read as a huge empty box with
         a small line of text in its corner (owner's screenshot, 24 August 2026).
         The inverse pair is the strongest ground the system owns and it holds in
         both themes; a project with a brand colour repaints via `--ds-auth-panel`
         and `--ds-auth-panel-ink`, both or neither, never half of a pair. */
      background-color: var(--ds-auth-panel, var(--ds-inverse));
      color: var(--ds-auth-panel-ink, var(--ds-on-inverse));
    }

    .ds-auth__brand {
      /* This component's own, pointed at the role: the brand is the panel's one
         headline and it reads at heading weight, not at body size in a corner. */
      --ds-auth-brand-face: var(--ds-font-heading-5-face);
      --ds-auth-brand-size: var(--ds-font-heading-5-size);
      --ds-auth-brand-weight: var(--ds-font-heading-5-weight);
      --ds-auth-brand-leading: var(--ds-font-heading-5-leading);
      --ds-auth-brand-tracking: var(--ds-font-heading-5-tracking);
      display: flex;
      align-items: center;
      gap: var(--ds-space-3);
      font-family: var(--ds-auth-brand-face);
      font-size: var(--ds-auth-brand-size);
      font-weight: var(--ds-auth-brand-weight);
      line-height: var(--ds-auth-brand-leading);
      letter-spacing: var(--ds-auth-brand-tracking);
    }

    .ds-auth__stage {
      grid-area: stage;
      display: flex;
      /* Centred, both axes. The 22 August ruling hung the card from the top so
         the flow's titles stayed on one line; on a large screen that put six
         controls in the top corner of a metre of glass, and the owner overruled
         it on a screenshot, 24 August 2026. What keeps the flow steady now is
         the card's own measure: the doors differ by a control or two, so the
         title drifts far less than the corner arrangement cost. */
      align-items: center;
      justify-content: center;
      overflow-y: auto;
      padding: var(--ds-rhythm-block) var(--ds-space-6);
    }

    /* Its own measure rather than the room it is given: a sign-in form is six
       controls, and a whole window turns that into six controls spread over a
       metre of glass. */
    .ds-auth__card {
      inline-size: 100%;
      max-inline-size: var(--ds-auth-measure, 24rem);
    }

    /* ── Stacked, and split below its breakpoint ──────────────────────────── */
    /* A band rather than a column: enough to carry the mark, and no more. The
       panel's own padding would give a band the height of a page. */
    .ds-auth__panel {
      padding: var(--ds-rhythm-element) var(--ds-space-6);
    }

    /* ── Split: the panel beside the card, once there is room ─────────────── */
    @container (min-width: 60rem) {
      /* Two halves, balanced. The old split gave the panel everything and left
         the stage a narrow right-hand column, so on wide glass the form sat in
         a corner beside an enormous empty ground. Half and half is what every
         current door does, and the card keeps its own measure inside its half
         whatever the glass. */
      .ds-auth--split .ds-auth__grid {
        grid-template-columns: 1fr 1fr;
        grid-template-rows: 1fr;
        grid-template-areas: "panel stage";
      }

      /* The panel changes sides and the card follows it, in one declaration,
         because the areas are named. */
      .ds-auth--split.ds-auth--end .ds-auth__grid {
        grid-template-areas: "stage panel";
      }

      .ds-auth--split .ds-auth__panel {
        padding: var(--ds-rhythm-block);
      }
    }

    /* ── Bare: no panel at all ────────────────────────────────────────────── */
    .ds-auth--bare .ds-auth__grid {
      grid-template-rows: 1fr;
      grid-template-areas: "stage";
    }

    /* With no panel the card stands alone on the ground, so it draws the edge
       the panel used to give the page: its own sheet, the way a dialog stands
       over a page. The brand, which lived in the panel, stands over the card
       instead, centred, the mark before the door. The stage is a column for the
       two of them. */
    .ds-auth--bare .ds-auth__stage {
      flex-direction: column;
      /* A block, not an element: the mark is not a line of the card, it is the
         name over the door, and at the element step it read as stuck to the
         sheet. The owner's eye, 18 September 2026. */
      gap: var(--ds-rhythm-block);
    }

    .ds-auth--bare .ds-auth__card {
      box-sizing: border-box;
      padding: var(--ds-space-8);
      background-color: var(--ds-surface-1);
      border: 1px solid var(--ds-border);
      border-radius: var(--ds-radius-3);
    }

    .ds-auth--bare .ds-auth__brand {
      justify-content: center;
    }

    /* ── The card's own vocabulary ────────────────────────────────────────────
       A door names its parts and the shell decides what a part is: the title of
       every door in a product is ONE declaration here, and retitling the flow
       never means walking the doors one by one with a new role class in hand.
       Owner's rule, 22 August 2026, and it is the same move every component in
       the package makes: the markup says what a thing IS and the component's own
       token says what that currently looks like. */

    .ds-auth__title {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-auth-title-face: var(--ds-font-heading-4-face);
      --ds-auth-title-size: var(--ds-font-heading-4-size);
      --ds-auth-title-weight: var(--ds-font-heading-4-weight);
      --ds-auth-title-leading: var(--ds-font-heading-4-leading);
      --ds-auth-title-tracking: var(--ds-font-heading-4-tracking);
      margin: 0;
      font-family: var(--ds-auth-title-face);
      font-size: var(--ds-auth-title-size);
      font-weight: var(--ds-auth-title-weight);
      line-height: var(--ds-auth-title-leading);
      letter-spacing: var(--ds-auth-title-tracking);
      text-wrap: balance;
    }

    /* La frase sotto il titolo. La misura e' quella della lettura; l'interlinea e'
       quella dell'interfaccia.
       
       `body-prose` da' 24 su 16, che e' la riga di un paragrafo: giusto in una
       colonna di testo, sbagliato in un pannello di accesso largo poco piu' di
       trecento pixel, dove la frase va a due righe e le due righe si staccano
       l'una dall'altra invece di leggersi come una (proprietario, 15 settembre
       2026). `body` tiene la stessa misura, 16, su 20, e le due righe restano una
       frase sola. Il ruolo si prende intero: mescolare la misura di uno con
       l'interlinea di un altro e' quello che il check vieta, ed e' giusto che lo
       vieti, perche' e' come nascono le coppie che nessuno ha scelto. */
    .ds-auth__lede {
      --ds-auth-lede-face: var(--ds-font-body-face);
      --ds-auth-lede-size: var(--ds-font-body-size);
      --ds-auth-lede-weight: var(--ds-font-body-weight);
      --ds-auth-lede-leading: var(--ds-font-body-leading);
      --ds-auth-lede-tracking: var(--ds-font-body-tracking);
      margin: 0;
      font-family: var(--ds-auth-lede-face);
      font-size: var(--ds-auth-lede-size);
      font-weight: var(--ds-auth-lede-weight);
      line-height: var(--ds-auth-lede-leading);
      letter-spacing: var(--ds-auth-lede-tracking);
      --ds-auth-lede-ink: var(--ds-text-secondary-neutral);
      color: var(--ds-auth-lede-ink);
      text-wrap: pretty;
    }

    /* The line at the card's foot that names the other door. */
    .ds-auth__foot {
      --ds-auth-foot-face: var(--ds-font-small-face);
      --ds-auth-foot-size: var(--ds-font-small-size);
      --ds-auth-foot-weight: var(--ds-font-small-weight);
      --ds-auth-foot-leading: var(--ds-font-small-leading);
      --ds-auth-foot-tracking: var(--ds-font-small-tracking);
      margin: 0;
      font-family: var(--ds-auth-foot-face);
      font-size: var(--ds-auth-foot-size);
      font-weight: var(--ds-auth-foot-weight);
      line-height: var(--ds-auth-foot-leading);
      letter-spacing: var(--ds-auth-foot-tracking);
      --ds-auth-foot-ink: var(--ds-text-secondary-neutral);
      color: var(--ds-auth-foot-ink);
    }

    /* ==========================================================================
       Truncated text

       The CSS behind <TruncatedText>. Private, like every other component's.

       A run of text cut to a number of lines, with the rest of it one press away.
       The clamp itself was written twice before this component existed, on the
       card's description and on the capture note's preview, and both stay: a
       clamp with no way to read the rest is the right answer where the whole
       thing is a link to the thing itself. This is the other case, where the text
       IS the page and cutting it has to be undoable.

       ── The button is hidden until the text is actually cut ───────────────────
       A "Show more" over four lines that were only three is a control that does
       nothing, and neither renderer can know: a server counts characters and not
       lines, and the number of lines depends on the width, the face and the size
       the reader chose. So both renderers write the button `hidden` and whoever
       is running shows it once the text has been measured, which is the same
       arrangement `share` has for a device with no share sheet. With no script at
       all the text stays cut and the button never appears, which is the honest
       failure: the reader sees what the page chose to show and no control that
       lies.
       ========================================================================== */

    .ds-truncated-text {
      /* This component's own, pointed at the semantic floor. */
      --ds-truncated-text-gap: var(--ds-rhythm-line);
      display: flex;
      flex-direction: column;
      align-items: start;
      gap: var(--ds-truncated-text-gap);
    }

    /* The clamp. `-webkit-line-clamp` is not a prefix on a private thing: it is
       how every browser in use spells this, and the unprefixed `line-clamp` is
       written beside it so the day they all take it, this file already did. */
    .ds-truncated-text__body {
      --ds-truncated-text-lines: 3;
      display: -webkit-box;
      -webkit-box-orient: vertical;
      -webkit-line-clamp: var(--ds-truncated-text-lines);
      line-clamp: var(--ds-truncated-text-lines);
      overflow: hidden;
    }

    /* Open, the box goes back to being a box. `line-clamp: none` alone is not
       enough: `-webkit-box` keeps the element a flex-ish box whose children lay
       out in a column, and a paragraph left in one loses its own margins and any
       inline element inside it that wanted a line of its own. */
    .ds-truncated-text[data-open="true"] .ds-truncated-text__body {
      display: block;
      -webkit-line-clamp: none;
      line-clamp: none;
    }

    /* Which state turns the words over, which `swap.css` says is the component's
       own: the root already carries `data-open`, so nothing new is written to the
       markup to make this work. */
    .ds-truncated-text[data-open="true"] .ds-swap__off {
      display: none;
    }

    .ds-truncated-text[data-open="true"] .ds-swap__on {
      display: inline-flex;
    }

    /* ==========================================================================
       Star rating

       The CSS behind <StarRating>. Private, like every other component's.

       Two things wearing one drawing: a rating READ, which is a picture of a
       number, and a rating SET, which is a group of radios. The old system's
       stylesheet opened with "ARIA requirements (consumer responsibility)" and
       listed the roles, the states and the keyboard a consumer had to write; here
       the settable one is real `<input type="radio">` in a real fieldset, so the
       arrows, the space bar, the posting and the announcement come from the
       browser and none of it is anybody's responsibility.

       ── The fill is a length, not a count ─────────────────────────────────────
       Four and a half out of five is what a page usually holds, and a half star
       cannot be drawn by picking a mark. So the stars are drawn twice, the outline
       underneath and the filled one over it clipped to a width: the value becomes
       a percentage and any fraction falls out of it. One drawing, one clip, and
       nothing rounds.
       ========================================================================== */

    .ds-star-rating {
      /* This component's own, pointed at the semantic floor. The mark follows the
         text it stands in, which is what a rating beside a title wants; a page
         that wants it larger asks for a size rather than a pixel. */
      --ds-star-rating-mark: var(--ds-font-body-size);
      --ds-star-rating-gap: var(--ds-space-1);
      --ds-star-rating-off: var(--ds-border-strong);
      --ds-star-rating-on: var(--ds-warning);
      /* What the renderer writes: the value as a share of the stars. */
      --ds-star-rating-fill: 0%;
      position: relative;
      display: inline-flex;
      align-items: center;
      gap: var(--ds-star-rating-gap);
      border: 0;
      padding: 0;
      margin: 0;
      min-inline-size: 0;
      font-size: var(--ds-star-rating-mark);
      line-height: 1;
      color: var(--ds-star-rating-off);
    }

    .ds-star-rating--small {
      --ds-star-rating-mark: var(--ds-font-small-size);
    }

    .ds-star-rating--large {
      --ds-star-rating-mark: var(--ds-font-heading-4-size);
    }

    /* ── Read ──────────────────────────────────────────────────────────────── */

    /* The two rows, one over the other. The row underneath is the outlines and
       sets the width; the one over it is the same number of filled stars, clipped
       to the share the value is. `inset-inline-start: 0` and a width, so the clip
       runs from the start edge in both writing directions. */
    .ds-star-rating__row {
      display: flex;
      gap: var(--ds-star-rating-gap);
    }

    .ds-star-rating__row--on {
      position: absolute;
      inset-block-start: 0;
      inset-inline-start: 0;
      inline-size: var(--ds-star-rating-fill);
      overflow: hidden;
      color: var(--ds-star-rating-on);
    }

    .ds-star-rating__mark {
      display: inline-flex;
      flex: none;
      inline-size: 1em;
      block-size: 1em;
    }

    .ds-star-rating__mark > svg {
      inline-size: 100%;
      block-size: 100%;
    }

    /* ── Set ───────────────────────────────────────────────────────────────── */

    /* Each star is a label holding a real radio. The input is clipped rather than
       hidden: `display: none` takes it out of the tab order and out of the
       accessibility tree, and the whole point of it being real is that it is
       there. */
    /* The option owns the box and the marks inside it carry no `display` of
       their own, which is not a tidiness: `.ds-swap__on` is hidden by a rule of
       the same weight in another file, and any `display` written on the mark
       after it wins by position and shows both stars. The settable row came out
       ten stars long that way, and swap.css's own header warns about exactly
       this. */
    .ds-star-rating__option {
      position: relative;
      display: inline-flex;
      inline-size: 1em;
      block-size: 1em;
      cursor: pointer;
    }

    .ds-star-rating__option > .ds-swap__off > svg,
    .ds-star-rating__option > .ds-swap__on > svg {
      inline-size: 1em;
      block-size: 1em;
    }

    .ds-star-rating__input {
      position: absolute;
      inline-size: 1px;
      block-size: 1px;
      padding: 0;
      margin: -1px;
      overflow: hidden;
      clip-path: inset(50%);
      white-space: nowrap;
      border: 0;
    }

    /* A star up to and including the chosen one is on, and "up to" is the part
       `:has()` is needed for: written the other way round, with `~` from the
       checked input, only the stars AFTER the choice can be reached, which is
       every star except the ones that should be filled.

       Both marks are drawn on every star and this shows one, the arrangement
       `swap.css` sets up: no script turns a star over, because the radio already
       knows and the stylesheet can read it. */
    .ds-star-rating__option:has(~ .ds-star-rating__option .ds-star-rating__input:checked),
    .ds-star-rating__option:has(.ds-star-rating__input:checked) {
      color: var(--ds-star-rating-on);
    }

    .ds-star-rating__option:has(~ .ds-star-rating__option .ds-star-rating__input:checked) .ds-swap__off,
    .ds-star-rating__option:has(.ds-star-rating__input:checked) .ds-swap__off {
      display: none;
    }

    .ds-star-rating__option:has(~ .ds-star-rating__option .ds-star-rating__input:checked) .ds-swap__on,
    .ds-star-rating__option:has(.ds-star-rating__input:checked) .ds-swap__on {
      display: inline-flex;
    }

    /* The focus ring goes on the star, since the input it belongs to is a pixel
       nobody can see. */
    .ds-star-rating__option:has(.ds-star-rating__input:focus-visible) {
      outline: 2px solid var(--ds-focus);
      outline-offset: 2px;
      border-radius: var(--ds-radius-1);
    }

    /* A switched-off star takes the system's own disabled ink rather than a
       share of its live colour: an opacity is a second way to say what
       `--ds-text-disabled` already says, and the check caught the invented name
       before it reached the sheet. */
    .ds-star-rating:has(.ds-star-rating__input:disabled) {
      --ds-star-rating-off: var(--ds-text-disabled);
      --ds-star-rating-on: var(--ds-text-disabled);
    }

    .ds-star-rating__option:has(.ds-star-rating__input:disabled) {
      cursor: not-allowed;
    }

    /* The name of the whole thing, for the eye as well as the reader, when a page
       wants it shown. */
    .ds-star-rating__label {
      font: inherit;
    }

    /* ==========================================================================
       Admin page

       The CSS behind the admin screens' shared vocabulary. Private, like every
       other component's.

       An admin product has one shell and many pages, and every page opens the
       same way: a title, the sentence under it, and whatever the page is beside
       them. The pair lives here once, so retitling every admin page in a product
       is one declaration, the same ruling the auth card took on 22 August 2026:
       the page names the part and the vocabulary decides what a part is.

       ── The ramp is the hierarchy ─────────────────────────────────────────────
       The title pointed at heading-4 and the section at heading-5, which on the
       product surface is 18 and 16 against a 16px lede and a 14px label: four
       rungs inside four pixels, and the page read as one wall of text (owner's
       screenshot, 24 August 2026). Pointed a rung up each, the same page reads
       24 / 18 / 16 / 14 — the title is the loudest thing on it, which is what a
       title is for. The roles are the ramp; these names are how a product moves
       one part without moving the system.
       ========================================================================== */

    .ds-admin-head__title {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-admin-head-title-face: var(--ds-font-heading-2-face);
      --ds-admin-head-title-size: var(--ds-font-heading-2-size);
      --ds-admin-head-title-weight: var(--ds-font-heading-2-weight);
      --ds-admin-head-title-leading: var(--ds-font-heading-2-leading);
      --ds-admin-head-title-tracking: var(--ds-font-heading-2-tracking);
      margin: 0;
      font-family: var(--ds-admin-head-title-face);
      font-size: var(--ds-admin-head-title-size);
      font-weight: var(--ds-admin-head-title-weight);
      line-height: var(--ds-admin-head-title-leading);
      letter-spacing: var(--ds-admin-head-title-tracking);
      text-wrap: balance;
    }

    /* ── The head ────────────────────────────────────────────────────────────
       How every page opens: the crumbs an ELEMENT above the pair, the title and
       its lede a pair at LINE inside, whatever acts beside the whole pair. One
       part since 10 September 2026, where seven screens here and three pages of
       the first product each wrote the same column by hand and the first product
       opened its database page on a different ramp from its inbox. The head
       ends a SECTION before the page begins, and that step is the screen's. */
    .ds-admin-head {
      display: flex;
      flex-direction: column;
      gap: var(--ds-rhythm-element);
    }

    /* ── Il nome della pagina, con quel che lo precede ────────────────────────
       Una griglia di due colonne e due righe: la freccia nella prima colonna
       centrata sulla RIGA DEL TITOLO, il titolo accanto, la riga sotto nella
       seconda colonna sotto il titolo.

       In riga flex non tornava: il bottone e' alto 40 (il gradino `medium`) e la
       riga del titolo 32, quindi allineato in alto stava 4px piu' in basso del
       centro della parola, e allineato al centro scivolava a meta' della coppia
       titolo piu' riga sotto (proprietario, 15 settembre 2026). Qui si allinea a
       quello su cui deve stare, e la riga sotto comincia dove comincia il titolo. */
    .ds-admin-head__name {
      display: grid;
      grid-template-columns: auto minmax(0, 1fr);
      column-gap: var(--ds-rhythm-element);
      row-gap: var(--ds-rhythm-line);
      align-items: center;
    }

    .ds-admin-head__name > .ds-admin-head__title,
    .ds-admin-head__name > .ds-admin-head__lede {
      grid-column: 2;
    }

    .ds-admin-head__name > .ds-button,
    .ds-admin-head__name > .ds-admin-head__icon {
      grid-column: 1;
      grid-row: 1;
    }

    /* The mark of the thing the page is about, before its name. It takes the
       title's own size and leading, declared again here because a sibling cannot
       read the title's tokens, so that `1lh` is the title's line: the same
       reading the card's slot takes for its mark. */
    .ds-admin-head__icon {
      --ds-admin-head-icon-size: var(--ds-font-heading-2-size);
      --ds-admin-head-icon-leading: var(--ds-font-heading-2-leading);
      display: inline-flex;
      flex: none;
      font-size: var(--ds-admin-head-icon-size);
      line-height: var(--ds-admin-head-icon-leading);
      inline-size: 1lh;
      block-size: 1lh;
    }

    .ds-admin-head__icon > svg {
      inline-size: 100%;
      block-size: 100%;
    }

    /* ── La pagina-record prende la stanza che ha ─────────────────────────────
       `--ds-admin-form-measure` esiste come nome e non come regola: un prodotto
       che vuole una misura di lettura sui campi la scrive sulla sua schermata,
       qui il valore e' `none`. Per un giorno ha capato a 48rem perche' un campo
       largo quanto una finestra da 2000px sembrava sbagliato — ma quello e' un
       giudizio, e un giudizio del genere lo prende il prodotto, non il sistema,
       che si e' ritrovato una pagina ferma a meta' schermo senza che nessuno
       l'avesse chiesta (proprietario, 25 agosto 2026).

       Se un prodotto la mette, capa i CAMPI e non lo schermo: sulla radice
       capava anche la testata, e un record che si chiama come un titolo di
       giornale andava a due righe con mille pixel vuoti accanto. Un titolo si
       legge per traverso, un campo si compila. */
    .ds-admin-form {
      --ds-admin-form-measure: none;
      --ds-admin-form-rail: 20rem;
      /* Quando il prodotto la scrive, la misura tiene la PAGINA: testata, corpo e
         colonna del record insieme, centrate. La regola di prima la teneva solo
         sul corpo, e il risultato su una finestra da 1667px era una testata larga
         quanto la finestra sopra una pagina che finiva 450px prima, cioe' due
         pagine sovrapposte (proprietario, 15 settembre 2026). La riga del 25
         agosto diceva di capare i campi e non lo schermo, ed era una risposta a
         una misura di 48rem scelta dal SISTEMA: a quella larghezza un titolo
         lungo andava a capo con mille pixel vuoti accanto. Il numero adesso lo
         sceglie il prodotto, che e' cio' che quella stessa riga chiedeva, e a
         76rem nessun titolo va a capo. Senza misura, `none`, non cambia niente
         per nessuno. */
      max-inline-size: var(--ds-admin-form-measure);
      margin-inline: auto;
      /* E la larghezza per intero, come fa il guscio nella sua stessa regola di
         lettura: un margine automatico sull'asse trasversale di un contenitore
         flex fa diventare il figlio grande quanto il suo contenuto, e la pagina
         si e' strizzata a 110px (misurato 15 settembre 2026). */
      inline-size: 100%;
      /* Its own container, so the split below answers to the width of the SCREEN
         and not of the window: an admin page stands next to a sidebar that opens
         and shuts, and the room it has is the room it has. */
      container-type: inline-size;
    }

    .ds-admin-form__body {
      max-inline-size: var(--ds-admin-form-measure);
    }

    /* ── The record's own column ──────────────────────────────────────────────
       `aside` is the rail beside the fields: what the record IS, rather than
       what it says — its state, its cover, the dates it turns on. It stands
       inside the form, because a control outside the form is a control the
       server never hears about.

       The rail is a real second column and not a floated box: the fields take
       the room and the rail keeps its width, and the two-column shape only
       appears when there is room for both. Under that, the rail falls under the
       fields at full width, which is the same page read down instead of across. */


    .ds-admin-form__cols {
      display: grid;
      grid-template-columns: minmax(0, 1fr);
      gap: var(--ds-rhythm-block);
      align-items: start;
    }

    @container (min-width: 60rem) {
      .ds-admin-form__cols {
        grid-template-columns: minmax(0, 1fr) var(--ds-admin-form-rail);
      }

      /* Con due colonne la riga di chiusura torna sotto i campi che salva, dove
         finisce la lettura. Con una sola non si dice niente e l'ordine del
         sorgente la mette ultima, dopo la colonna del record: su un telefono si
         sceglie tutto e poi si salva. */
      .ds-admin-form__fields {
        grid-column: 1;
        grid-row: 1;
      }

      /* E resta in vista. Accanto a un corpo lungo — un articolo intero in un
         editor — la colonna del record e' alta cinquecento pixel di fianco a
         settemila, e per il resto della pagina c'e' una colonna vuota: si
         scorre fino in fondo per accorgersi che lo stato era in cima
         (proprietario, 25 agosto 2026). `align-self: start` non e' un dettaglio:
         una cella che si stira alta quanto la riga non ha dove appiccicarsi. */
      .ds-admin-form__aside {
        grid-column: 2;
        grid-row: 1;
        position: sticky;
        inset-block-start: var(--ds-rhythm-block);
        align-self: start;
      }

      .ds-admin-form__acts {
        grid-column: 1;
        grid-row: 2;
      }
    }

    .ds-admin-form__fields {
      min-inline-size: 0;
    }

    .ds-admin-form__aside {
      min-inline-size: 0;
    }

    /* ── A list that fills the shell ──────────────────────────────────────────
       `fill` says: this screen is as tall as the room it stands in, and the
       panel takes what the head leaves. The table then scrolls its own rows, so
       the toolbar, the filters and the pagination stay in view over two hundred
       records instead of scrolling away with them.

       The chain is explicit because it has to be: a flex child only fills when
       every link above it is a flex column that has given up its automatic
       minimum. The alternative shipped first — the consumer capping the table at
       `calc(100dvh - 16rem)` — and the number went stale the same day the page
       title changed rung, which is what a number standing in for a layout does.
       The selectors walk this component's OWN markup, so the depth is a fact
       here and not a guess. */
    .ds-admin-list--fill {
      display: flex;
      flex-direction: column;
      flex: 1 1 auto;
      min-block-size: 0;
    }

    /* Il pannello prende quel che resta, e il capo no. `:last-child` e non un
       `.ds-stack` qualunque: senza `action` il capo NON e' una riga, e' la
       colonna titolo+lede, quindi cadeva anche lui in questa regola e i due si
       spartivano l'altezza — un titolo alto 56px in un blocco da 228, con
       centosettanta pixel di niente fra la riga sotto il titolo e la tabella. */
    .ds-admin-list--fill > .ds-stack,
    .ds-admin-list--fill > .ds-stack > .ds-stack:last-child {
      flex: 1 1 auto;
      min-block-size: 0;
    }

    /* La catena passava per `.ds-card`, che questa schermata non disegna piu' dal
       18 settembre 2026: quel che `children` porta — la tabella, o una riga col
       pannello dei filtri accanto alla tabella — prende l'altezza da se'. La
       striscia degli strumenti non e' piu' qui dentro: sta dentro il foglio
       dell'elenco, che e' il `toolbar` di `table()`. */
    .ds-admin-list--fill > .ds-stack > .ds-stack > * {
      flex: 1 1 auto;
      min-block-size: 0;
    }

    /* Il pager e' una banda e si tiene la sua altezza. */
    .ds-admin-list--fill > .ds-stack > .ds-stack > .ds-pagination {
      flex: none;
    }

    /* And the table itself, however deep the consumer wrapped it: the shell is a
       column so its scrollport can take the free height. */
    .ds-admin-list--fill .ds-table {
      display: flex;
      flex-direction: column;
      flex: 1 1 auto;
      min-block-size: 0;
    }

    .ds-admin-list--fill .ds-table__scroll {
      flex: 1 1 auto;
      min-block-size: 0;
    }

    /* A section's name inside the page: Security's "Password", "Two-factor",
       "Sessions". One rung under the page title, because a page has one h1 and
       its halves have h2s, and the halves of every admin page should retitle on
       one declaration exactly as the titles do. */
    /* ── A half of the screen, and the command that acts on it ───────────────
       The name is an h2 and the root is the row it stands in, so a section can
       carry its one command at the end of its own line the way the page's head
       carries its acts beside the title. Before this the consumer hand-built the
       row: `Row justify="between"` around the h2 and a Button, twice on one
       screen of the hub, and `Files` drew the same row a third time for itself.
       One arrangement, drawn once. The row wraps, so on a phone the command
       takes the next line and the name keeps its own. */
    .ds-admin-section {
      display: flex;
      flex-wrap: wrap;
      justify-content: space-between;
      align-items: center;
      gap: var(--ds-rhythm-element);
    }

    .ds-admin-section__name {
      --ds-admin-section-face: var(--ds-font-heading-4-face);
      --ds-admin-section-size: var(--ds-font-heading-4-size);
      --ds-admin-section-weight: var(--ds-font-heading-4-weight);
      --ds-admin-section-leading: var(--ds-font-heading-4-leading);
      --ds-admin-section-tracking: var(--ds-font-heading-4-tracking);
      margin: 0;
      font-family: var(--ds-admin-section-face);
      font-size: var(--ds-admin-section-size);
      font-weight: var(--ds-admin-section-weight);
      line-height: var(--ds-admin-section-leading);
      letter-spacing: var(--ds-admin-section-tracking);
      text-wrap: balance;
    }

    /* The command keeps to its own line when the name wraps, and never grows. */
    .ds-admin-section__action {
      display: flex;
      flex: none;
      align-items: center;
      gap: var(--ds-rhythm-line);
    }

    /* The sentence under the title: what this page holds and what to do in it. */
    .ds-admin-head__lede {
      --ds-admin-head-lede-face: var(--ds-font-body-face);
      --ds-admin-head-lede-size: var(--ds-font-body-size);
      --ds-admin-head-lede-weight: var(--ds-font-body-weight);
      --ds-admin-head-lede-leading: var(--ds-font-body-leading);
      --ds-admin-head-lede-tracking: var(--ds-font-body-tracking);
      margin: 0;
      font-family: var(--ds-admin-head-lede-face);
      font-size: var(--ds-admin-head-lede-size);
      font-weight: var(--ds-admin-head-lede-weight);
      line-height: var(--ds-admin-head-lede-leading);
      letter-spacing: var(--ds-admin-head-lede-tracking);
      --ds-admin-head-lede-ink: var(--ds-text-secondary-neutral);
      color: var(--ds-admin-head-lede-ink);
      text-wrap: pretty;
    }

    /* ==========================================================================
       Dashboard

       The CSS behind dashboard(). Private, like every other component's.

       The screen is composed out of parts that already have their own
       stylesheets — cards, stats, a grid — so this file holds only what the
       composition itself decided: the mark on a number that leads somewhere.
       ========================================================================== */

    /* A headline number can be the way into the thing it counts. The card takes
       the stretched link, which is the Card's own business; this is the arrow
       that says so, quiet next to the number and lit when the card is under the
       pointer, because the card is what the mouse is asking about. */
    .ds-dashboard__go {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. */
      --ds-dashboard-go-ink: var(--ds-text-tertiary-neutral);
      display: inline-flex;
      flex: none;
      color: var(--ds-dashboard-go-ink);
      transition: color var(--ds-duration) var(--ds-ease);
    }

    .ds-card:has(.ds-card__link:hover) .ds-dashboard__go,
    .ds-card:has(.ds-card__link:focus-visible) .ds-dashboard__go {
      --ds-dashboard-go-ink: var(--ds-text-primary-neutral);
    }

    /* ==========================================================================
       Site page

       The CSS behind the public screens' shared vocabulary. Private, like every
       other component's.

       A site has one shell and many pages, and the pages that are not the
       landing open the same way: a title, sometimes a sentence, sometimes a
       date. The parts live here once, the ruling the admin pages and the auth
       card took: the page names the part and the vocabulary decides what a part
       is, so retitling every public page in a product is one declaration.
       ========================================================================== */

    .ds-site-page__title {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-site-page-title-face: var(--ds-font-heading-2-face);
      --ds-site-page-title-size: var(--ds-font-heading-2-size);
      --ds-site-page-title-weight: var(--ds-font-heading-2-weight);
      --ds-site-page-title-leading: var(--ds-font-heading-2-leading);
      --ds-site-page-title-tracking: var(--ds-font-heading-2-tracking);
      margin: 0;
      font-family: var(--ds-site-page-title-face);
      font-size: var(--ds-site-page-title-size);
      font-weight: var(--ds-site-page-title-weight);
      line-height: var(--ds-site-page-title-leading);
      letter-spacing: var(--ds-site-page-title-tracking);
      text-wrap: balance;
    }

    /* The sentence under the title: running text, on the reading line. */
    .ds-site-page__lede {
      --ds-site-page-lede-face: var(--ds-font-copy-prose-face);
      --ds-site-page-lede-size: var(--ds-font-copy-prose-size);
      --ds-site-page-lede-weight: var(--ds-font-copy-prose-weight);
      --ds-site-page-lede-leading: var(--ds-font-copy-prose-leading);
      --ds-site-page-lede-tracking: var(--ds-font-copy-prose-tracking);
      margin: 0;
      font-family: var(--ds-site-page-lede-face);
      font-size: var(--ds-site-page-lede-size);
      font-weight: var(--ds-site-page-lede-weight);
      line-height: var(--ds-site-page-lede-leading);
      letter-spacing: var(--ds-site-page-lede-tracking);
      --ds-site-page-lede-ink: var(--ds-text-secondary-neutral);
      color: var(--ds-site-page-lede-ink);
      text-wrap: pretty;
    }

    /* The article's cover, standing between the title band and the reading.
       Block, because an inline image carries the text baseline's gap under it,
       and the width is the band's: the picture reads as part of the page, not
       as content that escaped it. */
    .ds-site-page__cover {
      --ds-site-page-cover-radius: var(--ds-radius-3);
      display: block;
      inline-size: 100%;
      block-size: auto;
      border-radius: var(--ds-site-page-cover-radius);
    }

    /* The quiet fact beside the words: a date, a version, who wrote it. */
    .ds-site-page__meta {
      --ds-site-page-meta-face: var(--ds-font-label-face);
      --ds-site-page-meta-size: var(--ds-font-label-size);
      --ds-site-page-meta-weight: var(--ds-font-label-weight);
      --ds-site-page-meta-leading: var(--ds-font-label-leading);
      --ds-site-page-meta-tracking: var(--ds-font-label-tracking);
      margin: 0;
      font-family: var(--ds-site-page-meta-face);
      font-size: var(--ds-site-page-meta-size);
      font-weight: var(--ds-site-page-meta-weight);
      line-height: var(--ds-site-page-meta-leading);
      letter-spacing: var(--ds-site-page-meta-tracking);
      --ds-site-page-meta-ink: var(--ds-text-tertiary-neutral);
      color: var(--ds-site-page-meta-ink);
    }

    /* ==========================================================================
       Chart

       The plot is an SVG and the words are HTML, which is the decision the whole
       file hangs off. A chart drawn with its labels inside the SVG scales them
       with it: twice the container, twice the letters, and no token in the type
       scale means anything any more. So the SVG holds geometry alone, stretched
       to whatever box it is given with `preserveAspectRatio: none`, every stroke
       wearing `vector-effect: non-scaling-stroke` so the stretch cannot thicken
       a line, and every word on the page is an element the roles already dress:
       the axis labels on the label rung, the legend beside its dot, the tooltip
       the behaviour bundle raises.

       The series colour travels as `--ds-chart-series`, written by the renderer
       on each series' own paths and read here: the palette lives in
       `tokens/color.css` as `--ds-chart-1..5`, categorical on purpose.
       ========================================================================== */

    .ds-chart {
      /* This component's own. The ratio is the one decision a chart makes about
         its own box: wide, because a time series is a sentence read left to
         right, and 2/1 is where the grid rows stay far enough apart to read. A
         dashboard card that wants it squarer moves the token, not the rule. */
      --ds-chart-ratio: 2 / 1;
      /* How long the tooltip takes to catch the pointer, and on what curve.
         0.4s and the browser's own `ease`, because that pairing is the
         reference library's and the chase is the point: at the system's 0.2s
         the panel reads as glued, which the owner said twice. */
      --ds-chart-chase: 0.4s;
      --ds-chart-chase-ease: ease;
      display: flex;
      flex-direction: column;
      gap: var(--ds-space-2);
      min-inline-size: 0;
    }

    /* The row that holds the y labels beside the plot; without them it is the
       plot alone. */
    .ds-chart__frame {
      display: flex;
      gap: var(--ds-space-2);
      align-items: stretch;
      min-inline-size: 0;
    }

    .ds-chart__plot {
      position: relative;
      flex: 1;
      min-inline-size: 0;
      aspect-ratio: var(--ds-chart-ratio);
    }

    /* A round chart occupies its row as a pair: the wheel on one side and the
       legend standing as a column beside it, which is how the industry's cards
       spend the width a square drawing cannot. The table view keeps the full
       row under them. */
    .ds-chart--round {
      flex-direction: row;
      flex-wrap: wrap;
      align-items: center;
      justify-content: center;
      gap: var(--ds-space-6);
    }

    .ds-chart--round .ds-chart__legend {
      flex-direction: column;
      align-items: flex-start;
      gap: var(--ds-space-2);
    }

    .ds-chart--round .ds-chart__table {
      flex-basis: 100%;
    }

    /* The round kinds hold the plot square, because the SVG beneath keeps its
       aspect and a stretched circle is an ellipse. Centred rather than grown,
       so a pie in a wide card is a pie with room beside it. */
    .ds-chart__plot--round {
      aspect-ratio: 1;
      align-self: center;
      inline-size: min(100%, var(--ds-chart-wheel, 20rem));
      flex: none;
    }

    .ds-chart__svg {
      display: block;
      inline-size: 100%;
      block-size: 100%;
      overflow: visible;
    }

    /* The grid: quiet lines a reader counts against, on the same token every
       resting container's line reads. */
    .ds-chart__grid {
      stroke: var(--ds-chart-grid, var(--ds-border));
      stroke-dasharray: 3 3;
      vector-effect: non-scaling-stroke;
    }

    .ds-chart__line {
      fill: none;
      stroke: var(--ds-chart-series);
      stroke-width: 1.5px;
      stroke-linejoin: round;
      stroke-linecap: round;
      vector-effect: non-scaling-stroke;
    }

    /* The wash under the line. `soft` paints it flat at the same strength the
       tones use for their tint; `gradient` hands the same colour to the SVG
       stops below and lets it fall to nothing at the floor. */
    .ds-chart__area {
      fill: var(--ds-chart-series);
      fill-opacity: var(--ds-chart-wash, 0.4);
    }

    .ds-chart__area--gradient {
      fill-opacity: 1;
    }

    /* The reference's own gradient, 0.8 falling to 0.1: the first draft ran a
       fifth of the colour to nothing, and every area chart read as dull, which
       the owner said of all of them at once. */
    .ds-chart__stop-top {
      stop-color: var(--ds-chart-series);
      stop-opacity: 0.8;
    }

    .ds-chart__stop-bottom {
      stop-color: var(--ds-chart-series);
      stop-opacity: 0.1;
    }

    /* Zero, where a scale digs below it: a firmer line than the grid's,
       because zero is a fact and the grid is furniture. */
    .ds-chart__zero {
      stroke: var(--ds-chart-zero, var(--ds-border-strong));
      vector-effect: non-scaling-stroke;
    }

    /* A number written on the drawing: over its point, at a bar's end, inside a
       slice. HTML at plot percentages, on the small rung like every other word
       here, shifted off its anchor by its modifier. */
    .ds-chart__value {
      position: absolute;
      transform: translate(-50%, calc(-100% - var(--ds-space-1)));
      --ds-chart-value-face: var(--ds-font-small-face);
      --ds-chart-value-size: var(--ds-font-small-size);
      --ds-chart-value-weight: var(--ds-font-small-weight);
      --ds-chart-value-leading: var(--ds-font-small-leading);
      --ds-chart-value-tracking: var(--ds-font-small-tracking);
      font-family: var(--ds-chart-value-face);
      font-size: var(--ds-chart-value-size);
      font-weight: var(--ds-chart-value-weight);
      line-height: var(--ds-chart-value-leading);
      letter-spacing: var(--ds-chart-value-tracking);
      --ds-chart-value-ink: var(--ds-text-secondary-neutral);
      color: var(--ds-chart-value-ink);
      pointer-events: none;
      white-space: nowrap;
      font-variant-numeric: tabular-nums;
    }

    .ds-chart__value--under {
      transform: translate(-50%, var(--ds-space-1));
    }

    .ds-chart__value--beside {
      transform: translate(var(--ds-space-1), -50%);
    }

    .ds-chart__value--beside.ds-chart__value--under {
      transform: translate(calc(-100% - var(--ds-space-1)), -50%);
    }

    /* Inside a slice the number centres on its spot and takes the ink that
       holds on paint. */
    .ds-chart__value--in {
      transform: translate(-50%, -50%);
      --ds-chart-value-ink: var(--ds-static-white);
    }

    /* A bar is its colour, whole: the wash is for what sits under a line, and a
       bar has no line to sit under. */
    .ds-chart__bar {
      fill: var(--ds-chart-series);
    }

    /* A slice the same, with the page's own ground as the seam between two, so
       neighbouring slices read as parts rather than as one wheel. */
    .ds-chart__slice {
      /* Fill alone: the corners are rounded by the renderer's own geometry,
         the d3 construction, after two attempts at rounding them with a fat
         same-colour stroke both smeared the middle. The air between slices is
         the renderer's pad, and the page shows through it. */
      fill: var(--ds-chart-series);
    }

    /* Whatever the consumer stands in the donut's middle: a Stat, a number, a
       word. Centred over the plot and inert to the pointer, so the tooltip
       underneath keeps answering. */
    .ds-chart__middle {
      position: absolute;
      inset: 0;
      display: grid;
      place-items: center;
      text-align: center;
      pointer-events: none;
    }

    /* A dot on every point: zero-length strokes with round caps, sized here and
       held under the stretch exactly as the lines are. */
    .ds-chart__point {
      fill: none;
      stroke: var(--ds-chart-series);
      stroke-width: 6px;
      stroke-linecap: round;
      vector-effect: non-scaling-stroke;
    }

    /* The radial kinds draw as strokes in the plot's own units, so the band's
       thickness scales with the wheel, which is what a band wants. */
    .ds-chart__ring {
      fill: none;
      stroke: var(--ds-chart-series);
      stroke-linecap: round;
    }

    .ds-chart__track {
      fill: none;
      stroke: var(--ds-chart-track, var(--ds-surface-2));
    }

    /* The matrix kinds are HTML grids, no SVG under them: a heatmap's cell and
       a dot chart's pip are boxes, a number in a cell is a word, and words live
       in HTML here. The plot loses its fixed ratio and takes the height its
       rows add up to. */
    .ds-chart__plot--matrix {
      aspect-ratio: auto;
    }

    .ds-chart__matrix {
      display: grid;
      /* PACKED at the industry's own square: the cell caps at 1.25rem and the
         grid packs from the start, so the strip stays a strip at any window,
         because a cell that grows with the card was, in the owner's words,
         mega quadratoni. A consumer who wants full-bleed tiles sets
         `--ds-chart-cell: 1fr`; the written-out variant below raises the cap
         for its numbers. */
      grid-template-columns: repeat(var(--ds-chart-cols, 1), minmax(0, var(--ds-chart-cell, 1.25rem)));
      justify-content: start;
      gap: var(--ds-space-1);
    }

    /* The written-out calendar centres in whatever room it gets: packed left it
       read as a thing with a hole beside it, and tiles that grew with the card
       read as mega quadratoni. Both were the owner's words. */
    .ds-chart__matrix--values {
      --ds-chart-cell: 3.5rem;
      justify-content: center;
    }

    /* The matrix's words go ABOVE the grid and only where they change, the way
       the industry's year strips write a month once at the week it starts: a
       twin grid with the same columns, each label placed on its own column and
       read from there. */
    .ds-chart__matrix-axis {
      display: grid;
      grid-template-columns: repeat(var(--ds-chart-cols, 1), minmax(0, var(--ds-chart-cell, 1.25rem)));
      justify-content: start;
      gap: var(--ds-space-1);
      margin-block-end: var(--ds-space-1);
    }

    .ds-chart__matrix-axis:has(+ .ds-chart__matrix--values) {
      --ds-chart-cell: 3.5rem;
      justify-content: center;
    }

    .ds-chart__matrix-tick {
      grid-row: 1;
      text-align: start;
      white-space: nowrap;
      overflow: visible;
    }

    .ds-chart__cell {
      aspect-ratio: 1;
      border-radius: var(--ds-radius-1);
      /* The heat is the series colour at the cell's own strength, mixed over
         the raised surface and never over nothing: a cold cell is a QUIET TILE,
         the way every industry calendar draws its empty days, because a grid
         with holes in it stops being a grid. */
      background-color: color-mix(in oklab, var(--ds-chart-series) var(--ds-chart-heat, 100%), var(--ds-surface-2));
      display: grid;
      place-items: center;
      overflow: hidden;
      --ds-chart-cell-face: var(--ds-font-label-face);
      --ds-chart-cell-size: var(--ds-font-label-size);
      --ds-chart-cell-weight: var(--ds-font-label-weight);
      --ds-chart-cell-leading: var(--ds-font-label-leading);
      --ds-chart-cell-tracking: var(--ds-font-label-tracking);
      font-family: var(--ds-chart-cell-face);
      font-size: var(--ds-chart-cell-size);
      font-weight: var(--ds-chart-cell-weight);
      line-height: var(--ds-chart-cell-leading);
      letter-spacing: var(--ds-chart-cell-tracking);
      --ds-chart-cell-ink: var(--ds-text-primary-neutral);
      color: var(--ds-chart-cell-ink);
      font-variant-numeric: tabular-nums;
    }

    /* A day outside the month: the slot holds the grid and shows nothing,
       exactly as a printed calendar pads its first week. */
    .ds-chart__cell--void {
      background-color: transparent;
    }

    /* The rowed matrix leads each row with its own name: one max-content
       column ahead of the cells, on both the grid and its axis twin, whose
       words shift one column right to stay over their own. */
    .ds-chart__matrix--rowed {
      grid-template-columns: max-content repeat(var(--ds-chart-cols, 1), minmax(0, var(--ds-chart-cell, 1.25rem)));
    }

    .ds-chart__matrix-row {
      align-self: center;
      text-align: end;
      padding-inline-end: var(--ds-space-2);
      white-space: nowrap;
    }

    /* Nothing to draw: the words stand where the drawing would. */
    .ds-chart__empty {
      position: absolute;
      inset: 0;
      display: grid;
      place-items: center;
      --ds-chart-empty-face: var(--ds-font-small-face);
      --ds-chart-empty-size: var(--ds-font-small-size);
      --ds-chart-empty-weight: var(--ds-font-small-weight);
      --ds-chart-empty-leading: var(--ds-font-small-leading);
      --ds-chart-empty-tracking: var(--ds-font-small-tracking);
      font-family: var(--ds-chart-empty-face);
      font-size: var(--ds-chart-empty-size);
      font-weight: var(--ds-chart-empty-weight);
      line-height: var(--ds-chart-empty-leading);
      letter-spacing: var(--ds-chart-empty-tracking);
      --ds-chart-empty-ink: var(--ds-text-tertiary-neutral);
      color: var(--ds-chart-empty-ink);
    }

    /* A dot's cell holds no paint of its own: the pip is the value, and it
       says it twice, as size and as strength together, because that is what
       makes an activity strip readable at a glance: the quiet days recede dim
       and small, the loud ones stand up bright. Denser than the heatmap by its
       own cell cap, since a strip earns its keep by the dozen columns. */
    /* A dot strip FILLS its row, unlike the heatmap's packed squares: the
       reference bands run their dots edge to edge with the air distributed,
       and the pip stays a capped dot in the middle of however wide a cell the
       row hands it. */
    .ds-chart__matrix--dots {
      grid-template-columns: repeat(var(--ds-chart-cols, 1), minmax(0, 1fr));
      gap: var(--ds-space-0-5);
    }

    .ds-chart__cell--dot {
      display: grid;
      place-items: center;
    }

    .ds-chart__cell--dot {
      background-color: transparent;
    }

    .ds-chart__pip {
      display: block;
      /* Sized by its value and capped at the industry's small dot, whatever
         room the cell has: a strip's dot is a dot, not a coin. */
      inline-size: min(var(--ds-chart-dot, 100%), var(--ds-chart-pip, 0.625rem));
      aspect-ratio: 1;
      border-radius: var(--ds-radius-full);
      background-color: color-mix(in oklab, var(--ds-chart-series) var(--ds-chart-heat, 100%), transparent);
    }

    /* The radar's words, standing at the end of their own spokes. */
    .ds-chart__wheel-label {
      position: absolute;
      transform: translate(-50%, -50%);
      pointer-events: none;
    }

    /* The cursor the tooltip stands on: both halves move it with a transform
       and nothing else, because a position that goes through `inset` is a
       layout on every one of a pointer's 120 events a second, and that is what
       a cursor that sticks is made of. The transform stays on the compositor. */
    .ds-chart__cursor {
      position: absolute;
      inset-block: 0;
      inset-inline-start: 0;
      inline-size: 1px;
      background-color: var(--ds-chart-cursor, var(--ds-border-strong));
      pointer-events: none;
      will-change: transform;
    }

    /* On bars the cursor is the chosen band lit whole, on the same wash every
       hovered row in the system wears. */
    .ds-chart__cursor--band {
      background-color: var(--ds-chart-cursor-band, var(--ds-surface-hover));
    }

    /* The lying-down bar's band: full width, a band tall. */
    .ds-chart__cursor--hband {
      inset-inline: 0;
      inset-block-start: 0;
      inset-block-end: auto;
      inline-size: auto;
    }

    /* The x axis: one label under each point, the ends flush with the plot's
       edges, everything between centred over its own point. */
    .ds-chart__axis-x {
      display: flex;
      justify-content: space-between;
    }

    .ds-chart__axis-y {
      display: flex;
      flex-direction: column-reverse;
      justify-content: space-between;
      text-align: end;
    }

    /* The lying-down bar's categories: one word per band, centred on it,
       reading top to bottom the way the bands do. */
    .ds-chart__axis-y--bands {
      flex-direction: column;
      justify-content: space-around;
    }

    .ds-chart__tick {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. */
      --ds-chart-tick-face: var(--ds-font-small-face);
      --ds-chart-tick-size: var(--ds-font-small-size);
      --ds-chart-tick-weight: var(--ds-font-small-weight);
      --ds-chart-tick-leading: var(--ds-font-small-leading);
      --ds-chart-tick-tracking: var(--ds-font-small-tracking);
      font-family: var(--ds-chart-tick-face);
      font-size: var(--ds-chart-tick-size);
      font-weight: var(--ds-chart-tick-weight);
      line-height: var(--ds-chart-tick-leading);
      letter-spacing: var(--ds-chart-tick-tracking);
      --ds-chart-tick-ink: var(--ds-text-tertiary-neutral);
      color: var(--ds-chart-tick-ink);
      white-space: nowrap;
    }

    /* The legend: a dot and its word, centred under the plot. */
    .ds-chart__legend {
      display: flex;
      flex-wrap: wrap;
      justify-content: center;
      gap: var(--ds-space-4);
    }

    .ds-chart__key {
      display: inline-flex;
      align-items: center;
      gap: var(--ds-space-2);
      /* A key is a BUTTON now: it holds its series out of the drawing on a
         press and lifts it over the rest on hover, so the control resets what
         a button brings and keeps the words. */
      border: 0;
      padding: 0;
      background: none;
      cursor: pointer;
      font: inherit;
      --ds-chart-key-face: var(--ds-font-label-face);
      --ds-chart-key-size: var(--ds-font-label-size);
      --ds-chart-key-weight: var(--ds-font-label-weight);
      --ds-chart-key-leading: var(--ds-font-label-leading);
      --ds-chart-key-tracking: var(--ds-font-label-tracking);
      font-family: var(--ds-chart-key-face);
      font-size: var(--ds-chart-key-size);
      font-weight: var(--ds-chart-key-weight);
      line-height: var(--ds-chart-key-leading);
      letter-spacing: var(--ds-chart-key-tracking);
      --ds-chart-key-ink: var(--ds-text-secondary-neutral);
      color: var(--ds-chart-key-ink);
    }

    .ds-chart__dot {
      inline-size: var(--ds-space-2);
      block-size: var(--ds-space-2);
      border-radius: var(--ds-radius-1);
      background-color: var(--ds-chart-series);
      flex: none;
    }

    /* A key held down: the series is out of the drawing and the key says so
       the same way, dimmed with its dot hollowed. */
    .ds-chart__key--off {
      opacity: 0.45;
    }

    .ds-chart__key--off .ds-chart__dot {
      background-color: transparent;
      box-shadow: inset 0 0 0 1px var(--ds-chart-series);
    }

    /* Hovering one key lifts its series by dimming the rest: the classes are
       the renderer's, matched series to key, because a stylesheet cannot pair
       an nth key with an nth group on its own. */
    .ds-chart__dim {
      opacity: 0.3;
      transition: opacity var(--ds-duration) var(--ds-ease);
    }

    /* The tooltip: raised by React on its side and by the bundle on the PHP
       page, positioned on the cursor, dressed only here. Told apart from the
       page by a border and a surface, the same way every other raised thing in
       this system is: there is no elevation, and this is not the place to
       invent one. */
    .ds-chart__tip {
      position: absolute;
      /* Anchored at the plot's corner and MOVED by a transform the renderer
         writes, offset and side-swap included, for the same reason as the
         cursor above: the panel rides the compositor, and the pointer never
         waits for a layout. */
      inset-block-start: 0;
      inset-inline-start: 0;
      will-change: transform;
      /* The panel CHASES the pointer rather than riding glued to it: the
         renderer keeps writing the target and this is what makes the panel ease
         towards it, the way the reference library moves. The cursor line has no
         transition on purpose, because the snap is its whole meaning. */
      transition: transform var(--ds-chart-chase) var(--ds-chart-chase-ease);
      display: none;
      flex-direction: column;
      gap: var(--ds-space-1);
      padding: var(--ds-space-2) var(--ds-space-3);
      background-color: var(--ds-surface-1);
      border: 1px solid var(--ds-border);
      border-radius: var(--ds-radius-2);
      pointer-events: none;
      white-space: nowrap;
      z-index: 1;
    }

    .ds-chart__tip[data-open] {
      display: flex;
    }

    /* Bars stand in bands rather than on edges, so their labels centre under
       their own band instead of running flush to the plot's ends. */
    .ds-chart__axis-x--bands {
      justify-content: space-around;
    }

    .ds-chart__tip-title {
      --ds-chart-tip-title-face: var(--ds-font-label-face);
      --ds-chart-tip-title-size: var(--ds-font-label-size);
      --ds-chart-tip-title-weight: var(--ds-emphasis-weight);
      --ds-chart-tip-title-leading: var(--ds-font-label-leading);
      --ds-chart-tip-title-tracking: var(--ds-font-label-tracking);
      font-family: var(--ds-chart-tip-title-face);
      font-size: var(--ds-chart-tip-title-size);
      font-weight: var(--ds-chart-tip-title-weight);
      line-height: var(--ds-chart-tip-title-leading);
      letter-spacing: var(--ds-chart-tip-title-tracking);
      --ds-chart-tip-title-ink: var(--ds-text-primary-neutral);
      color: var(--ds-chart-tip-title-ink);
    }

    .ds-chart__tip-row {
      display: flex;
      align-items: center;
      justify-content: space-between;
      gap: var(--ds-space-4);
      --ds-chart-tip-row-face: var(--ds-font-label-face);
      --ds-chart-tip-row-size: var(--ds-font-label-size);
      --ds-chart-tip-row-weight: var(--ds-font-label-weight);
      --ds-chart-tip-row-leading: var(--ds-font-label-leading);
      --ds-chart-tip-row-tracking: var(--ds-font-label-tracking);
      font-family: var(--ds-chart-tip-row-face);
      font-size: var(--ds-chart-tip-row-size);
      font-weight: var(--ds-chart-tip-row-weight);
      line-height: var(--ds-chart-tip-row-leading);
      letter-spacing: var(--ds-chart-tip-row-tracking);
      --ds-chart-tip-row-ink: var(--ds-text-secondary-neutral);
      color: var(--ds-chart-tip-row-ink);
    }

    /* The numbers behind the drawing, behind the browser's own disclosure. The
       summary dresses as a quiet control on the label rung; the table inside is
       the Table, whole. */
    .ds-chart__table-summary {
      --ds-chart-table-summary-face: var(--ds-font-label-face);
      --ds-chart-table-summary-size: var(--ds-font-label-size);
      --ds-chart-table-summary-weight: var(--ds-font-label-weight);
      --ds-chart-table-summary-leading: var(--ds-font-label-leading);
      --ds-chart-table-summary-tracking: var(--ds-font-label-tracking);
      font-family: var(--ds-chart-table-summary-face);
      font-size: var(--ds-chart-table-summary-size);
      font-weight: var(--ds-chart-table-summary-weight);
      line-height: var(--ds-chart-table-summary-leading);
      letter-spacing: var(--ds-chart-table-summary-tracking);
      --ds-chart-table-summary-ink: var(--ds-text-secondary-neutral);
      color: var(--ds-chart-table-summary-ink);
      cursor: pointer;
      inline-size: fit-content;
    }

    .ds-chart__table[open] .ds-chart__table-summary {
      margin-block-end: var(--ds-space-2);
    }

    .ds-chart__tip-value {
      --ds-chart-tip-value-ink: var(--ds-text-primary-neutral);
      color: var(--ds-chart-tip-value-ink);
      font-variant-numeric: tabular-nums;
    }

    /* ==========================================================================
       Code field

       The CSS behind <CodeField>. Private, like every other component's.

       One box a digit. The row is the count, and that is the point of it: a reader
       copying six digits off another screen keeps their place by looking at how
       many boxes are left.

       ── The boxes are square, and stay square ─────────────────────────────────
       Each is as wide as it is tall, off the same tier ramp as every other
       control, so a row of six sits on the ramp rather than on a number somebody
       picked. Below the smallest useful window they shrink together rather than
       wrapping onto two lines: six boxes on two rows is not a code any more.

       ── The digit is centred and monospaced ───────────────────────────────────
       Proportional digits make a 1 sit in a different place from an 8, and a row
       where the digits do not line up reads as a row of mistakes.
       ========================================================================== */

    .ds-code-field {
      display: flex;
      flex-direction: column;
      /* The field's step, whose argument is in `field.css`: at 4px the label, the
         boxes and the message read as one block. */
      gap: var(--ds-space-2);
    }

    .ds-code-field__boxes {
      display: flex;
      gap: var(--ds-space-2);
      /* Every box gives way at the same rate, so the row narrows evenly. */
      min-inline-size: 0;
    }

    .ds-code-field__box {
      box-sizing: border-box;
      flex: 1 1 auto;
      min-inline-size: 0;
      /* Square: the tier is the height, and the width follows it. */
      block-size: var(--ds-code-field-tier);
      max-inline-size: var(--ds-code-field-tier);
      padding: 0;
      border: 1px solid var(--ds-border);
      border-radius: var(--ds-radius-2);
      background-color: var(--ds-surface-1);
      --ds-code-field-box-ink: var(--ds-text-primary-neutral);
      color: var(--ds-code-field-box-ink);
      --ds-code-field-box-face: var(--ds-font-mono-face);
      font-family: var(--ds-code-field-box-face);
      text-align: center;
      transition:
        border-color var(--ds-duration) var(--ds-ease),
        background-color var(--ds-duration) var(--ds-ease);
    }

    /* The ramp, and the type the tier map pairs with each rung. The size is set
       on the box rather than aliased into a variable of ours: a variable declared
       here would resolve once and keep the web surface's number on the product
       one, where the same rung carries different type. */
    .ds-code-field--xsmall { --ds-code-field-tier: var(--ds-size-1); }
    .ds-code-field--small { --ds-code-field-tier: var(--ds-size-2); }
    .ds-code-field--medium { --ds-code-field-tier: var(--ds-size-3); }
    .ds-code-field--large { --ds-code-field-tier: var(--ds-size-4); }
    .ds-code-field--xlarge { --ds-code-field-tier: var(--ds-size-5); }
    .ds-code-field--xxlarge { --ds-code-field-tier: var(--ds-size-6); }

    .ds-code-field--xsmall .ds-code-field__box {
      --ds-code-field-box-face: var(--ds-font-small-face);
      --ds-code-field-box-tracking: var(--ds-font-small-tracking);
      --ds-code-field-box-size: var(--ds-font-small-size);
      --ds-code-field-box-weight: var(--ds-font-small-weight);
      font-family: var(--ds-code-field-box-face);
     font-size: var(--ds-code-field-box-size); 
      letter-spacing: var(--ds-code-field-box-tracking);
      font-weight: var(--ds-code-field-box-weight);}
    .ds-code-field--small .ds-code-field__box {
      --ds-code-field-box-face: var(--ds-font-label-face);
      --ds-code-field-box-tracking: var(--ds-font-label-tracking);
      --ds-code-field-box-size: var(--ds-font-label-size);
      --ds-code-field-box-weight: var(--ds-font-label-weight);
      font-family: var(--ds-code-field-box-face);
     font-size: var(--ds-code-field-box-size); 
      letter-spacing: var(--ds-code-field-box-tracking);
      font-weight: var(--ds-code-field-box-weight);}
    .ds-code-field--medium .ds-code-field__box,
    .ds-code-field--large .ds-code-field__box,
    .ds-code-field--xlarge .ds-code-field__box,
    .ds-code-field--xxlarge .ds-code-field__box {
      --ds-code-field-box-face: var(--ds-font-body-face);
      --ds-code-field-box-tracking: var(--ds-font-body-tracking);
      --ds-code-field-box-size: var(--ds-font-body-size);
      --ds-code-field-box-weight: var(--ds-font-body-weight);
      font-family: var(--ds-code-field-box-face);
     font-size: var(--ds-code-field-box-size); 
      letter-spacing: var(--ds-code-field-box-tracking);
      font-weight: var(--ds-code-field-box-weight);}

    .ds-code-field__box:hover:not(:disabled) {
      background-image: linear-gradient(var(--ds-surface-hover), var(--ds-surface-hover));
    }

    .ds-code-field__box:focus {
      outline: none;
      border-color: var(--ds-border-active);
      box-shadow: 0 0 0 1px var(--ds-border-active);
    }

    .ds-code-field--invalid .ds-code-field__box {
      border-color: var(--ds-danger);
    }

    .ds-code-field--invalid .ds-code-field__box:focus {
      border-color: var(--ds-danger);
      box-shadow: 0 0 0 1px var(--ds-danger);
    }

    .ds-code-field__box:disabled {
      background-color: var(--ds-surface-disabled);
      --ds-code-field-box-ink: var(--ds-text-disabled);
      color: var(--ds-code-field-box-ink);
      cursor: not-allowed;
    }

    /* ==========================================================================
       Docs page

       The screen is composition: the sidebar is the shell's, the reading is
       Prose's, and the list of headings is Contents. What lives here is the two
       columns and the rail's behaviour, which is the one thing neither of the
       three can decide on its own.

       ── The rail is a column, not a floated box ──────────────────────────────
       The reading takes the room and the rail keeps its width, and the pair only
       appears where there is room for both. Under that the rail falls above the
       reading at full width, which is the same page read down instead of across:
       a list of headings under the article is a list nobody reaches, since the
       reader has already passed everything it names. Source order puts the body
       first, because that is the order a screen reader and a page with no CSS
       want; the rows are named so the one column reads rail, body, tail.

       ── It asks the SCREEN, not the window ───────────────────────────────────
       A docs page stands next to a sidebar that opens and shuts, and inside a
       panel or a device canvas it has less room than the window says it has. Its
       own container, so the split answers to the room it actually has.

       ── And it stays in view ─────────────────────────────────────────────────
       The rail is three hundred pixels beside seven thousand, and a reader who
       scrolls loses the one control that would take them somewhere else. Sticky
       with `align-self: start`, because a cell stretched as tall as its row has
       nothing to stick to.
       ========================================================================== */

    .ds-docs-page {
      /* The rail's width. A list of headings at 14px wants about forty
         characters before it starts wrapping every line. */
      --ds-docs-page-rail: 16rem;
      container-type: inline-size;
    }

    /* One column, and the rows say the order: the rail above the reading, the
       tail under it. Source order is body first, which is what a screen reader
       and an unstyled page want, and `grid-row` is what moves the rail up
       without moving it in the markup. */
    .ds-docs-page__cols {
      display: grid;
      grid-template-columns: minmax(0, 1fr);
      gap: var(--ds-rhythm-block);
      align-items: start;
    }

    .ds-docs-page__rail {
      grid-row: 1;
      min-inline-size: 0;
    }

    .ds-docs-page__body {
      grid-row: 2;
      min-inline-size: 0;
    }

    .ds-docs-page__tail {
      grid-row: 3;
    }

    /* The head of the page, and its own: `ds-site-page__*` was borrowed here at
       first and it was wrong twice over. A page title is one thing on a page, so
       three screens shown on one docs page wore the same name three times; and
       this screen stands in the application shell rather than in a site section,
       so the site page's decisions are not the ones it inherits. Its own names,
       pointed at the same roles.

       The title is an `h1` because in use this screen IS the page. */
    .ds-docs-page__title {
      --ds-docs-page-title-face: var(--ds-font-heading-2-face);
      --ds-docs-page-title-size: var(--ds-font-heading-2-size);
      --ds-docs-page-title-weight: var(--ds-font-heading-2-weight);
      --ds-docs-page-title-leading: var(--ds-font-heading-2-leading);
      --ds-docs-page-title-tracking: var(--ds-font-heading-2-tracking);
      display: block;
      margin: 0;
      font-family: var(--ds-docs-page-title-face);
      font-size: var(--ds-docs-page-title-size);
      font-weight: var(--ds-docs-page-title-weight);
      line-height: var(--ds-docs-page-title-leading);
      letter-spacing: var(--ds-docs-page-title-tracking);
    }

    .ds-docs-page__lede {
      --ds-docs-page-lede-face: var(--ds-font-copy-prose-face);
      --ds-docs-page-lede-size: var(--ds-font-copy-prose-size);
      --ds-docs-page-lede-weight: var(--ds-font-copy-prose-weight);
      --ds-docs-page-lede-leading: var(--ds-font-copy-prose-leading);
      --ds-docs-page-lede-tracking: var(--ds-font-copy-prose-tracking);
      --ds-docs-page-lede-ink: var(--ds-text-secondary-neutral);
      display: block;
      margin: 0;
      font-family: var(--ds-docs-page-lede-face);
      font-size: var(--ds-docs-page-lede-size);
      font-weight: var(--ds-docs-page-lede-weight);
      line-height: var(--ds-docs-page-lede-leading);
      letter-spacing: var(--ds-docs-page-lede-tracking);
      color: var(--ds-docs-page-lede-ink);
    }

    .ds-docs-page__meta {
      --ds-docs-page-meta-face: var(--ds-font-small-face);
      --ds-docs-page-meta-size: var(--ds-font-small-size);
      --ds-docs-page-meta-weight: var(--ds-font-small-weight);
      --ds-docs-page-meta-leading: var(--ds-font-small-leading);
      --ds-docs-page-meta-tracking: var(--ds-font-small-tracking);
      --ds-docs-page-meta-ink: var(--ds-text-tertiary-neutral);
      display: block;
      margin: 0;
      font-family: var(--ds-docs-page-meta-face);
      font-size: var(--ds-docs-page-meta-size);
      font-weight: var(--ds-docs-page-meta-weight);
      line-height: var(--ds-docs-page-meta-leading);
      letter-spacing: var(--ds-docs-page-meta-tracking);
      color: var(--ds-docs-page-meta-ink);
    }

    /* The line that closes the page: what came before and what comes next, the
       date it was changed, the way to say it was wrong. Separated from the
       reading, because it is about the page rather than in it. */
    .ds-docs-page__tail {
      display: flex;
      flex-direction: column;
      gap: var(--ds-rhythm-element);
      padding-block-start: var(--ds-rhythm-element);
      border-block-start: 1px solid var(--ds-border-subtle);
    }

    /* `--ds-break-wide`, the rung the system already calls "there is room for two
       columns", and the sum agrees with it: the rail's 16rem and the block step of
       48px leave 41rem of reading at 960. The tablet rung above it is 1024, and
       measured on 4048 at a 1440 window the docs canvas hands this screen 974px,
       so 64rem left the widest page anybody reads standing in one column. */
    @container (min-width: 60rem) {
      .ds-docs-page__cols {
        grid-template-columns: minmax(0, 1fr) var(--ds-docs-page-rail);
      }

      .ds-docs-page__body {
        grid-column: 1;
        grid-row: 1;
      }

      .ds-docs-page__rail {
        grid-column: 2;
        grid-row: 1;
        position: sticky;
        inset-block-start: var(--ds-rhythm-block);
        align-self: start;
      }

      /* Under the reading and not under both columns: the tail belongs to the
         article, and stretched across the rail as well it reads as the page's
         footer rather than as the end of what was being read. */
      .ds-docs-page__tail {
        grid-column: 1;
        grid-row: 2;
      }
    }

    /* ==========================================================================
       Divider

       The CSS behind <Divider>. Private, like every other component's.

       A line across, with an optional word sitting in it. The word is the reason
       this exists: a bare rule is one CSS declaration and needs no component, but
       a rule with "or" in the middle of it is a rule broken in two, measured
       against a word whose width nobody knows.

       The word gets no background to hide the line behind it. The line is drawn in
       two pieces, so the gap around the word is a gap and not a patch that goes
       wrong the moment the surface under it changes.
       ========================================================================== */

    .ds-divider {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-divider-face: var(--ds-font-small-face);
      --ds-divider-tracking: var(--ds-font-small-tracking);
      --ds-divider-size: var(--ds-font-small-size);
      --ds-divider-weight: var(--ds-font-small-weight);
      --ds-divider-leading: var(--ds-font-small-leading);
      display: flex;
      align-items: center;
      gap: var(--ds-space-3);
      border: 0;
      margin: 0;
      /* Each half takes what is left, so the word sits in the middle of the room
         rather than in the middle of the line. */
      --ds-divider-ink: var(--ds-text-tertiary-neutral);
      color: var(--ds-divider-ink);
      font-family: var(--ds-divider-face);
      font-size: var(--ds-divider-size);
      letter-spacing: var(--ds-divider-tracking);
      font-weight: var(--ds-divider-weight);
      line-height: var(--ds-divider-leading);
    }

    .ds-divider::before,
    .ds-divider::after {
      content: "";
      flex: 1;
      block-size: 1px;
      background-color: var(--ds-border-subtle);
    }

    /* Nothing in the middle: one line, and the two halves meet. */
    .ds-divider:empty {
      gap: 0;
    }

    .ds-divider__label {
      --ds-divider-label-tracking: var(--ds-font-overline-tracking);
      /* Small caps rather than a second size: the word is a joint in a rule, not
         a heading over what follows. */
      font-variant-caps: all-small-caps;
      letter-spacing: var(--ds-divider-label-tracking);
    }

    /* ==========================================================================
       Footer

       The CSS behind <Footer>. Private, like every other component's.

       The end of a page: who made it, everything that did not fit in the bar, and
       the small print. It is the one part of a site a reader arrives at on
       purpose, looking for the thing nobody put in the navigation.

       ── Three bands, and only one of them is a list ──────────────────────────
       The invitation to subscribe, the mark and its columns of links, and the line
       at the foot with the year and the legal pages. Each is optional and the band
       disappears with it, so a footer of one line is one line rather than an empty
       grid with a rule across it.

       The order is the old system's, kept on purpose: every consumer that grew a
       footer grew it in this order. What the old one had as a fourth band — the
       registered name and number — is in the brand column here, because at the
       very end it read as a second copyright under the first.

       ── It ends the page rather than sitting on it ───────────────────────────
       The page's own ground and a line along the top, the way the bar has a line
       along the bottom. A filled block at the end of a page is a second page.
       ========================================================================== */

    .ds-footer {
      background-color: var(--ds-surface-0);
      border-block-start: 1px solid var(--ds-border-subtle);
      --ds-footer-ink: var(--ds-text-secondary-neutral);
      color: var(--ds-footer-ink);
    }

    /* Held to a reading width and centred, like the bar's row: a mark at the far
       left of a wide screen and a legal link at the far right is a distance
       nobody's eye crosses. `full` gives it up, for an application whose columns
       start at the edge. */
    .ds-footer__inner {
      box-sizing: border-box;
      display: flex;
      flex-direction: column;
      gap: var(--ds-rhythm-block);
      inline-size: 100%;
      max-inline-size: var(--ds-footer-width, 72rem);
      margin-inline: auto;
      padding-block: var(--ds-rhythm-block);
      padding-inline: var(--ds-footer-gutter, var(--ds-space-6));
    }

    .ds-footer--full .ds-footer__inner {
      max-inline-size: none;
    }

    /* The invitation to subscribe, when there is one. Held to a measure of its
       own: an email box that runs the width of a desk asks for an essay.

       Three steps of air, not one. The band holds a head, a form and a line under
       it, and at one uniform 4 they read as four things of equal weight stacked by
       accident. 4 inside the head, because a title and its own sentence are one
       thing; 16 above the form, because that is where the reader is asked to act;
       8 down to the note, which belongs to the form and not to the band. */
    .ds-footer__newsletter {
      /* Two knobs, because this block is the one a site changes. It is the only
         thing in a footer that asks the reader for something, and how much room
         that ask deserves is the site's call, not the component's: an application
         wants a line, a magazine wants a band. Defaults are what it was — 32rem
         wide and no room of its own beyond the footer's own rhythm. */
      --ds-footer-newsletter-width: 32rem;
      --ds-footer-newsletter-space: 0px;
      display: flex;
      flex-direction: column;
      gap: var(--ds-space-2);
      max-inline-size: var(--ds-footer-newsletter-width);
      min-inline-size: 0;
      padding-block: var(--ds-footer-newsletter-space);
    }

    .ds-footer__newsletter-head {
      display: flex;
      flex-direction: column;
      gap: var(--ds-rhythm-line);
    }

    /* The loudest thing in a footer, and the only one that asks for something, so
       it stands a level above the 16 of running text and two above the 14 of the
       column headings. Body face: this is furniture, not an article. */
    .ds-footer__newsletter-title {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-footer-newsletter-title-face: var(--ds-font-heading-6-face);
      --ds-footer-newsletter-title-size: var(--ds-font-heading-6-size);
      --ds-footer-newsletter-title-leading: var(--ds-font-heading-6-leading);
      --ds-footer-newsletter-title-tracking: var(--ds-font-heading-6-tracking);
      margin: 0;
      font-family: var(--ds-footer-newsletter-title-face);
      font-size: var(--ds-footer-newsletter-title-size);
      line-height: var(--ds-footer-newsletter-title-leading);
      font-weight: var(--ds-font-heading-6-weight);
      letter-spacing: var(--ds-footer-newsletter-title-tracking);
      --ds-footer-newsletter-title-ink: var(--ds-text-primary-neutral);
      color: var(--ds-footer-newsletter-title-ink);
    }

    .ds-footer__newsletter-text {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-footer-newsletter-text-face: var(--ds-font-label-face);
      --ds-footer-newsletter-text-tracking: var(--ds-font-label-tracking);
      --ds-footer-newsletter-text-size: var(--ds-font-label-size);
      --ds-footer-newsletter-text-weight: var(--ds-font-label-weight);
      --ds-footer-newsletter-text-leading: var(--ds-font-label-leading);
      margin: 0;
      font-family: var(--ds-footer-newsletter-text-face);
      font-size: var(--ds-footer-newsletter-text-size);
      letter-spacing: var(--ds-footer-newsletter-text-tracking);
      font-weight: var(--ds-footer-newsletter-text-weight);
      line-height: var(--ds-footer-newsletter-text-leading);
      text-wrap: pretty;
    }

    /* The box takes the line and the button keeps its own width; on a narrow
       window the button drops under the box rather than being squeezed to its
       first letter. */
    .ds-footer__newsletter-form {
      display: flex;
      flex-wrap: wrap;
      align-items: end;
      gap: var(--ds-space-2);
      /* On top of the band's 8, so the form stands 16 clear of the head. */
      margin-block-start: var(--ds-space-2);
    }

    .ds-footer__newsletter-form > .ds-field {
      flex: 1 1 14rem;
      min-inline-size: 0;
    }

    .ds-footer__newsletter-note {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-footer-newsletter-note-face: var(--ds-font-small-face);
      --ds-footer-newsletter-note-tracking: var(--ds-font-small-tracking);
      --ds-footer-newsletter-note-size: var(--ds-font-small-size);
      --ds-footer-newsletter-note-weight: var(--ds-font-small-weight);
      --ds-footer-newsletter-note-leading: var(--ds-font-small-leading);
      margin: 0;
      font-family: var(--ds-footer-newsletter-note-face);
      font-size: var(--ds-footer-newsletter-note-size);
      letter-spacing: var(--ds-footer-newsletter-note-tracking);
      font-weight: var(--ds-footer-newsletter-note-weight);
      line-height: var(--ds-footer-newsletter-note-leading);
      --ds-footer-newsletter-note-ink: var(--ds-text-tertiary-neutral);
      color: var(--ds-footer-newsletter-note-ink);
    }

    /* The mark and the columns share a line while there is room. The mark keeps a
       measure of its own so its sentence does not run the width of the page, and
       the columns take what is left. */
    .ds-footer__top {
      display: flex;
      flex-wrap: wrap;
      gap: var(--ds-rhythm-block);
      align-items: start;
    }

    /* Three different things stacked — a wordmark, a sentence, a row of round
       targets — not three lines of one thing, so the line rhythm (4) had them
       touching. 12 is the step that separates them without opening the band. */
    .ds-footer__brand {
      display: flex;
      flex-direction: column;
      gap: var(--ds-space-3);
      flex: 1 1 16rem;
      max-inline-size: 28rem;
      min-inline-size: 0;
      /* This component's own, pointed at the role. The wordmark is the one line in
         this footer that had an ink and nothing else: the tagline, the details, the
         links, the headings, the social row and the bottom line all carry their
         five, and the name at the top of the column carried one. So a project that
         wanted its own mark at its own weight had nowhere to say it and reached
         into this rule instead, which is the thing the five names exist to stop.

         The type lands on the box because the mark is a bare child of it: the three
         rules under it declare their own and win, so this dresses the name and
         nothing else. */
      --ds-footer-brand-face: var(--ds-font-body-face);
      --ds-footer-brand-tracking: var(--ds-font-body-tracking);
      --ds-footer-brand-size: var(--ds-font-body-size);
      --ds-footer-brand-weight: var(--ds-font-body-weight);
      --ds-footer-brand-leading: var(--ds-font-body-leading);
      --ds-footer-brand-ink: var(--ds-text-primary-neutral);
      font-family: var(--ds-footer-brand-face);
      font-size: var(--ds-footer-brand-size);
      font-weight: var(--ds-footer-brand-weight);
      line-height: var(--ds-footer-brand-leading);
      letter-spacing: var(--ds-footer-brand-tracking);
      color: var(--ds-footer-brand-ink);
    }

    .ds-footer__tagline {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-footer-tagline-face: var(--ds-font-label-face);
      --ds-footer-tagline-tracking: var(--ds-font-label-tracking);
      --ds-footer-tagline-size: var(--ds-font-label-size);
      --ds-footer-tagline-weight: var(--ds-font-label-weight);
      --ds-footer-tagline-leading: var(--ds-font-label-leading);
      margin: 0;
      font-family: var(--ds-footer-tagline-face);
      font-size: var(--ds-footer-tagline-size);
      letter-spacing: var(--ds-footer-tagline-tracking);
      font-weight: var(--ds-footer-tagline-weight);
      line-height: var(--ds-footer-tagline-leading);
      --ds-footer-tagline-ink: var(--ds-text-secondary-neutral);
      color: var(--ds-footer-tagline-ink);
      text-wrap: pretty;
    }

    /* Who the company legally is: the registered name, the number, the address.
       It sat in a band of its own at the very end and read as a second copyright,
       two lines under the first one. It is not a closing line, it is part of what
       the brand column already says, so it says it there. */
    .ds-footer__details {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-footer-details-face: var(--ds-font-small-face);
      --ds-footer-details-tracking: var(--ds-font-small-tracking);
      --ds-footer-details-size: var(--ds-font-small-size);
      --ds-footer-details-weight: var(--ds-font-small-weight);
      --ds-footer-details-leading: var(--ds-font-small-leading);
      margin: 0;
      font-family: var(--ds-footer-details-face);
      font-size: var(--ds-footer-details-size);
      letter-spacing: var(--ds-footer-details-tracking);
      font-weight: var(--ds-footer-details-weight);
      line-height: var(--ds-footer-details-leading);
      --ds-footer-details-ink: var(--ds-text-tertiary-neutral);
      color: var(--ds-footer-details-ink);
      text-wrap: pretty;
    }

    .ds-footer__social {
      display: flex;
      flex-wrap: wrap;
      gap: var(--ds-space-2);
    }

    /* A round target on the second tier, 32, because these are the one place in a
       footer somebody aims at a mark rather than at a word. The type paired with
       that tier is label 14/20, and it is declared here for one reason: the mark
       below is `1lh`, and without it the slot took the page's own leading and came
       out 24 in a 32 circle — 4 of air on each side. At 20 it is 6. */
    .ds-footer__social-link {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-footer-social-link-face: var(--ds-font-label-face);
      --ds-footer-social-link-tracking: var(--ds-font-label-tracking);
      --ds-footer-social-link-size: var(--ds-font-label-size);
      --ds-footer-social-link-weight: var(--ds-font-label-weight);
      --ds-footer-social-link-leading: var(--ds-font-label-leading);
      font-family: var(--ds-footer-social-link-face);
      font-size: var(--ds-footer-social-link-size);
      letter-spacing: var(--ds-footer-social-link-tracking);
      font-weight: var(--ds-footer-social-link-weight);
      line-height: var(--ds-footer-social-link-leading);
      box-sizing: border-box;
      display: inline-flex;
      align-items: center;
      justify-content: center;
      inline-size: var(--ds-size-2);
      block-size: var(--ds-size-2);
      border: 1px solid var(--ds-border);
      border-radius: var(--ds-radius-full);
      --ds-footer-social-link-ink: var(--ds-text-secondary-neutral);
      color: var(--ds-footer-social-link-ink);
      transition:
        color var(--ds-duration) var(--ds-ease),
        border-color var(--ds-duration) var(--ds-ease),
        background-color var(--ds-duration) var(--ds-ease);
    }

    .ds-footer__social-link > svg {
      inline-size: 1lh;
      block-size: 1lh;
    }

    .ds-footer__social-link:hover {
      --ds-footer-social-link-ink: var(--ds-text-primary-neutral);
      color: var(--ds-footer-social-link-ink);
      border-color: var(--ds-text-primary-neutral);
      background-color: var(--ds-surface-hover);
    }

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

    /* The columns. `auto-fit` so three columns on a desk become two on a tablet
       and one on a phone without a breakpoint being named: the number of columns
       is a consequence of the room and the width a column needs to hold a link. */
    .ds-footer__groups {
      display: grid;
      grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
      /* One distance on both axes, and it is the block step.
         It was the element step down and the block step across: 16 and 48 on the
         web surface. Side by side that reads fine, because the row gap is never
         used — the groups are in one row. Stacked on a phone the 16 is the only
         thing between the last link of one group and the heading of the next, and
         a heading sitting 16 under a link belongs to the list above it as much as
         to the one below. The distance between two groups is the same distance
         whichever way they happen to be arranged. */
      gap: var(--ds-rhythm-block);
      flex: 2 1 24rem;
      min-inline-size: 0;
    }

    .ds-footer__group {
      display: flex;
      flex-direction: column;
      gap: var(--ds-space-2);
      min-inline-size: 0;
    }

    /* The name over a run of links. The overline treatment, because it labels a
       part of the furniture rather than heading a piece of the document. */
    .ds-footer__heading {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-footer-heading-face: var(--ds-font-overline-face);
      --ds-footer-heading-size: var(--ds-font-overline-size);
      --ds-footer-heading-leading: var(--ds-font-overline-leading);
      --ds-footer-heading-tracking: var(--ds-font-overline-tracking);
      margin: 0;
      font-family: var(--ds-footer-heading-face);
      font-size: var(--ds-footer-heading-size);
      line-height: var(--ds-footer-heading-leading);
      font-weight: var(--ds-font-overline-weight);
      letter-spacing: var(--ds-footer-heading-tracking);
      text-transform: uppercase;
      --ds-footer-heading-ink: var(--ds-text-tertiary-neutral);
      color: var(--ds-footer-heading-ink);
    }

    .ds-footer__link {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-footer-link-face: var(--ds-font-label-face);
      --ds-footer-link-tracking: var(--ds-font-label-tracking);
      --ds-footer-link-size: var(--ds-font-label-size);
      --ds-footer-link-weight: var(--ds-font-label-weight);
      --ds-footer-link-leading: var(--ds-font-label-leading);
      font-family: var(--ds-footer-link-face);
      font-size: var(--ds-footer-link-size);
      letter-spacing: var(--ds-footer-link-tracking);
      font-weight: var(--ds-footer-link-weight);
      line-height: var(--ds-footer-link-leading);
      --ds-footer-link-ink: var(--ds-text-secondary-neutral);
      color: var(--ds-footer-link-ink);
      text-decoration: none;
      /* The target is the line and its own air, so two links in a column are far
         enough apart to be told apart by a finger. */
      padding-block: var(--ds-space-1);
      min-inline-size: 0;
      transition: color var(--ds-duration) var(--ds-ease);
    }

    .ds-footer__link:hover {
      --ds-footer-link-ink: var(--ds-text-primary-neutral);
      color: var(--ds-footer-link-ink);

    }

    .ds-footer__link:focus-visible {
      outline: 2px solid var(--ds-focus);
      outline-offset: 2px;
      border-radius: var(--ds-radius-1);
    }

    /* The foot of the foot: the year on one side, the pages nobody reads until
       they need them on the other. It wraps onto two lines rather than shrinking,
       because a line of legal links is the last thing that should be clipped. */
    .ds-footer__bottom {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-footer-bottom-face: var(--ds-font-small-face);
      --ds-footer-bottom-tracking: var(--ds-font-small-tracking);
      --ds-footer-bottom-size: var(--ds-font-small-size);
      --ds-footer-bottom-weight: var(--ds-font-small-weight);
      --ds-footer-bottom-leading: var(--ds-font-small-leading);
      display: flex;
      flex-wrap: wrap;
      align-items: center;
      justify-content: space-between;
      gap: var(--ds-space-4);
      padding-block-start: var(--ds-rhythm-element);
      border-block-start: 1px solid var(--ds-border-subtle);
      font-family: var(--ds-footer-bottom-face);
      font-size: var(--ds-footer-bottom-size);
      letter-spacing: var(--ds-footer-bottom-tracking);
      font-weight: var(--ds-footer-bottom-weight);
      line-height: var(--ds-footer-bottom-leading);
      --ds-footer-bottom-ink: var(--ds-text-tertiary-neutral);
      color: var(--ds-footer-bottom-ink);
    }

    /* Measured: a footer of only the small print came out 142 tall, because it was
       paying for a rule and the 16 above it with nothing on the other side. When
       the last line is the only line, it is not the foot of anything. */
    .ds-footer__bottom:first-child {
      padding-block-start: 0;
      border-block-start: none;
    }

    .ds-footer__legal {
      display: flex;
      flex-wrap: wrap;
      align-items: center;
      gap: var(--ds-space-4);
    }

    .ds-footer__aside {
      display: flex;
      flex-wrap: wrap;
      align-items: center;
      gap: var(--ds-space-2);
    }



    /* ── Who built it ─────────────────────────────────────────────────────────
       Under the small print, an element down, and no rule of its own: the line
       above already closed the footer, and this is the signature under it. */
    .ds-footer__credits {
      padding-block-start: var(--ds-rhythm-element);
    }

    /* ==========================================================================
       Language switcher

       The CSS behind <LangSwitcher>. Private, like every other component's.

       ── It is navigation, not a form control ──────────────────────────────────
       Every language is a different page, so every choice here is a link. That is
       why this is not a Select wearing a globe: a select posts a value, and what a
       reader wants is the address of this page in their own language — openable in
       a new tab, followable by a crawler, carrying `hreflang`. The moment it
       becomes a value, all four of those go.

       ── Two shapes, one job ───────────────────────────────────────────────────
       `menu` is a button that opens the list, for a bar or a footer's last line
       where the row is already full. `row` lays the codes out flat, for a footer
       with room, where seeing that Spanish exists is the point.

       The panel is `ds-popover`, the one Menu and Select already open. A third
       panel written next to those two is a third set of paddings to keep in step.
       ========================================================================== */

    .ds-lang {
      /* And the one you are ON, which is the other thing this file was
         saying with the same number. */
      --ds-lang-weight-current: var(--ds-current-weight);
      display: inline-flex;
    }

    /* The code, not the name, and uppercase: it is a two-letter tag standing in
       for a word, and lowercase it reads as a word cut short. */
    .ds-lang__code {
      --ds-lang-code-tracking: var(--ds-font-overline-tracking);
      font-variant-caps: all-small-caps;
      letter-spacing: var(--ds-lang-code-tracking);
    }

    /* The flat shape. A row of codes, each its own target. */
    .ds-lang__row {
      display: flex;
      flex-wrap: wrap;
      align-items: center;
      gap: var(--ds-space-1);
    }

    .ds-lang__item {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-lang-item-face: var(--ds-font-small-face);
      --ds-lang-item-tracking: var(--ds-font-small-tracking);
      --ds-lang-item-size: var(--ds-font-small-size);
      --ds-lang-item-weight: var(--ds-font-small-weight);
      --ds-lang-item-leading: var(--ds-font-small-leading);
      box-sizing: border-box;
      display: inline-flex;
      align-items: center;
      gap: var(--ds-space-2);
      /* The first tier of the ramp, 24, which is the floor WCAG 2.5.8 puts under
         a target and the right height for a line of small type. */
      min-block-size: var(--ds-size-1);
      padding-inline: var(--ds-space-2);
      border-radius: var(--ds-radius-1);
      font-family: var(--ds-lang-item-face);
      font-size: var(--ds-lang-item-size);
      letter-spacing: var(--ds-lang-item-tracking);
      font-weight: var(--ds-lang-item-weight);
      line-height: var(--ds-lang-item-leading);
      /* Secondary, not tertiary: these are targets. The quietest ink in the
         system is for text nobody is meant to act on. */
      --ds-lang-item-ink: var(--ds-text-secondary-neutral);
      color: var(--ds-lang-item-ink);
      text-decoration: none;
      transition:
        color var(--ds-duration) var(--ds-ease),
        background-color var(--ds-duration) var(--ds-ease);
    }

    .ds-lang__item > svg {
      inline-size: 1lh;
      block-size: 1lh;
    }

    .ds-lang__item:hover {
      --ds-lang-item-ink: var(--ds-text-primary-neutral);
      color: var(--ds-lang-item-ink);
      background-color: var(--ds-surface-hover);
    }

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

    /* The page you are on. Ink rather than a fill: a filled chip in a row of four
       reads as a button somebody left pressed. */
    .ds-lang__item[aria-current] {
      --ds-lang-item-ink: var(--ds-text-primary-neutral);
      color: var(--ds-lang-item-ink);
      font-weight: var(--ds-lang-weight-current);
    }

    /* ==========================================================================
       Password field

       The CSS behind <PasswordField>. Private, like every other component's.

       The field is the shared one; what is here is the eye at the end of it. It is
       a button and not an icon, so unlike `ds-field__icon` it takes the focus,
       takes a ring, and reacts to the pointer.
       ========================================================================== */

    .ds-password-field__reveal {
      display: inline-flex;
      align-items: center;
      justify-content: center;
      flex: none;
      /* The slot is the line beside it, like every other mark in the system. */
      inline-size: 1lh;
      block-size: 1lh;
      padding: 0;
      border: 0;
      background: none;
      /* A button inherits neither the face nor the leading, so `1lh` below was
         resolving against `normal` and the eye came out 16 at every tier —
         measured 16 in a 64 field. With the frame's leading it tracks the line
         beside it, 16 / 20 / 24, exactly like `ds-field__icon`. */
      font: inherit;
      --ds-password-field-reveal-ink: var(--ds-text-tertiary-neutral);
      color: var(--ds-password-field-reveal-ink);
      cursor: pointer;
      transition: color var(--ds-duration) var(--ds-ease);
    }

    .ds-password-field__reveal > svg {
      inline-size: 100%;
      block-size: 100%;
    }

    .ds-password-field__reveal:hover {
      --ds-password-field-reveal-ink: var(--ds-text-primary-neutral);
      color: var(--ds-password-field-reveal-ink);
    }

    /* Inside the frame, so the ring wraps the eye and not the whole field: the
       field already has its own, and two rings on one control read as two
       controls. */
    .ds-password-field__reveal:focus-visible {
      outline: 2px solid var(--ds-focus);
      outline-offset: 2px;
      border-radius: var(--ds-radius-1);
    }

    /* Shown means the secret is on screen, and that is worth saying in the mark
       as well as in the label. */
    .ds-password-field__reveal[aria-pressed="true"] {
      --ds-password-field-reveal-ink: var(--ds-text-primary-neutral);
      color: var(--ds-password-field-reveal-ink);
    }

    .ds-field__frame:has(:disabled) .ds-password-field__reveal {
      --ds-password-field-reveal-ink: var(--ds-text-disabled);
      color: var(--ds-password-field-reveal-ink);
      cursor: not-allowed;
    }

    /* Which state turns the mark over. `swap.css` says there are two and that the
       crossed eye is the hidden one; this says when. `aria-pressed` is the
       attribute the button already carried. */
    .ds-password-field__reveal[aria-pressed="true"] .ds-swap__off {
      display: none;
    }

    .ds-password-field__reveal[aria-pressed="true"] .ds-swap__on {
      display: inline-flex;
    }

    /* ==========================================================================
       Password rules

       The CSS behind <PasswordRules>. Private, like every other component's.

       The panel under a password box that says what the password has to be, and
       which of those things it already is. It is not an error message: it is on
       screen before anything has gone wrong, and every line is one of three
       states — not yet checked, met, missing.

       ── Three states, and the third only after a try ──────────────────────────
       Grey is "not yet": nothing has been typed, and nothing has failed. Green is
       met. Red is missing, and it is only shown once the reader has typed
       something, because a form that turns red before it has been touched is a
       form telling somebody off for arriving.

       ── The mark carries the state, and so does the ink ───────────────────────
       Colour alone is not a state anybody can read: a ring, a tick and a cross are
       three different shapes, and that is what a reader who cannot tell green from
       red is left with.
       ========================================================================== */

    .ds-password-rules {
      display: flex;
      flex-direction: column;
      /* 8, not 4: five rules at the line rhythm read as one block of text rather
         than five things being answered one at a time. */
      gap: var(--ds-space-2);
      margin: 0;
      padding: var(--ds-space-3);
      border-radius: var(--ds-radius-2);
      background-color: var(--ds-surface-2);
      list-style: none;
    }

    .ds-password-rules__rule {
      /* This component's own, pointed at the role. The role is what the system
         means; this is what this component decided, and it is the name the rule
         reads. A project moves the badge and the badge moves; it moves the role
         and everything that has not been moved on purpose moves with it. */
      --ds-password-rules-rule-face: var(--ds-font-label-face);
      --ds-password-rules-rule-tracking: var(--ds-font-label-tracking);
      --ds-password-rules-rule-size: var(--ds-font-label-size);
      --ds-password-rules-rule-weight: var(--ds-font-label-weight);
      --ds-password-rules-rule-leading: var(--ds-font-label-leading);
      display: flex;
      align-items: center;
      gap: var(--ds-space-2);
      font-family: var(--ds-password-rules-rule-face);
      font-size: var(--ds-password-rules-rule-size);
      letter-spacing: var(--ds-password-rules-rule-tracking);
      font-weight: var(--ds-password-rules-rule-weight);
      line-height: var(--ds-password-rules-rule-leading);
      --ds-password-rules-rule-ink: var(--ds-text-tertiary-neutral);
      color: var(--ds-password-rules-rule-ink);
      transition: color var(--ds-duration) var(--ds-ease);
    }

    .ds-password-rules__mark {
      display: inline-flex;
      align-items: center;
      justify-content: center;
      flex: none;
      inline-size: 1lh;
      block-size: 1lh;
    }

    .ds-password-rules__mark > svg {
      inline-size: 100%;
      block-size: 100%;
    }

    /* Waiting. A ring drawn in the border, rather than a mark from the set: the
       set has no "nothing has happened yet", and an empty circle is exactly what
       an unticked line looks like everywhere else in this system. */
    .ds-password-rules__mark--waiting::before {
      content: "";
      inline-size: 0.75lh;
      block-size: 0.75lh;
      border: 2px solid currentColor;
      border-radius: var(--ds-radius-full);
    }

    .ds-password-rules__rule--met {
      color: var(--ds-success-ink);
    }

    .ds-password-rules__rule--missing {
      color: var(--ds-danger-ink);
    }

    /* The state, for a reader who cannot see the mark. Off screen and still in
       the accessibility tree: `display: none` would take it out of both. */
    .ds-password-rules__said {
      position: absolute;
      inline-size: 1px;
      block-size: 1px;
      overflow: hidden;
      clip-path: inset(50%);
      white-space: nowrap;
    }

    /* ==========================================================================
       Figure

       A picture and what is said about it, as one thing. The stylesheet has
       dressed `figure` and `figcaption` since the first article, but only INSIDE
       `.ds-prose`, where the fence styles bare elements on purpose: a picture
       standing on a page of its own, or in a band built out of blocks, had
       nothing.

       The caption is a real `figcaption` and the picture is whatever the page
       hands over — an `<img>`, a `<picture>`, a video, a chart. This component
       owns the pairing and nothing else: the crop, the ratio and the rounding of
       a picture are `CardMedia`'s job where a card wants them, and a picture that
       wants the edges asks its band for `measure="full"`.

       The caption reads at the small face in the tertiary ink, which is the
       quietest the system has that still passes on the page ground: a caption
       louder than that competes with the words it explains.
       ========================================================================== */

    .ds-figure {
      --ds-figure-gap: var(--ds-space-3);
      --ds-figure-radius: var(--ds-radius-3);
      display: flex;
      flex-direction: column;
      gap: var(--ds-figure-gap);
      /* A `figure` carries the user agent's own margin, which is 40px on each
         side and turns a band's rhythm into two different rhythms. */
      margin: 0;
    }

    /* Whatever was handed over, held to the column it stands in. Written on the
       child and not asked of the caller, because a page that has to remember a
       class on an image is a page that will forget it once. */
    .ds-figure > :not(.ds-figure__caption) {
      display: block;
      max-inline-size: 100%;
      block-size: auto;
      border-radius: var(--ds-figure-radius);
    }

    .ds-figure__caption {
      --ds-figure-caption-face: var(--ds-font-small-face);
      --ds-figure-caption-size: var(--ds-font-small-size);
      --ds-figure-caption-weight: var(--ds-font-small-weight);
      --ds-figure-caption-leading: var(--ds-font-small-leading);
      --ds-figure-caption-tracking: var(--ds-font-small-tracking);
      --ds-figure-caption-ink: var(--ds-text-tertiary-neutral);
      margin: 0;
      color: var(--ds-figure-caption-ink);
      font-family: var(--ds-figure-caption-face);
      font-size: var(--ds-figure-caption-size);
      font-weight: var(--ds-figure-caption-weight);
      line-height: var(--ds-figure-caption-leading);
      letter-spacing: var(--ds-figure-caption-tracking);
    }

    /* ==========================================================================
       Marquee

       A row of words that never ends, for the band a page gives to the names it
       works with: clients, places, the words a studio wants read as a list
       without a list's punctuation.

       The track is written twice, identical, and the second copy is hidden from
       a screen reader: by the time the first has left the stage the second is
       exactly where it was, so the cycle never jumps, and the reader hears the
       words once instead of twice.

       Three speeds and no fourth: a duration a page picks by hand is a page
       deciding how fast reading is. They are durations and not distances, so a
       longer row moves faster to cover the same ground in the same time, which
       is what reading a longer row wants.

       It stops for anybody who asked motion to stop. Not slower — stopped, and
       the copy taken away with it: a row that crawls is still a row that moves
       under the eye of somebody who said no.
       ========================================================================== */

    .ds-marquee {
      --ds-marquee-gap: var(--ds-space-8);
      --ds-marquee-duration: 40s;
      display: flex;
      gap: var(--ds-marquee-gap);
      overflow: hidden;
      /* The row is the width of the band and no wider: the words run past the
         edges and nothing about the page moves with them. */
      inline-size: 100%;
    }

    .ds-marquee--slow {
      --ds-marquee-duration: 60s;
    }

    .ds-marquee--fast {
      --ds-marquee-duration: 24s;
    }

    .ds-marquee__track {
      display: flex;
      flex: 0 0 auto;
      gap: var(--ds-marquee-gap);
      align-items: center;
      /* At least the band's width, so a row of three words still fills the stage
         and the copy behind it starts where it should. */
      min-inline-size: 100%;
      animation: ds-marquee var(--ds-marquee-duration) linear infinite;
    }

    .ds-marquee__word {
      --ds-marquee-word-face: var(--ds-font-heading-3-face);
      --ds-marquee-word-size: var(--ds-font-heading-3-size);
      --ds-marquee-word-weight: var(--ds-font-heading-3-weight);
      --ds-marquee-word-leading: var(--ds-font-heading-3-leading);
      --ds-marquee-word-tracking: var(--ds-font-heading-3-tracking);
      --ds-marquee-word-ink: var(--ds-text-primary-neutral);
      color: var(--ds-marquee-word-ink);
      font-family: var(--ds-marquee-word-face);
      font-size: var(--ds-marquee-word-size);
      font-weight: var(--ds-marquee-word-weight);
      line-height: var(--ds-marquee-word-leading);
      letter-spacing: var(--ds-marquee-word-tracking);
      white-space: nowrap;
    }

    /* The mark between two words, which is a mark and not a word: it is not read,
       and it takes the quietest ink the system has. */
    .ds-marquee__mark {
      --ds-marquee-mark-ink: var(--ds-text-tertiary-neutral);
      color: var(--ds-marquee-mark-ink);
      font-size: var(--ds-marquee-word-size);
      line-height: var(--ds-marquee-word-leading);
    }

    @keyframes ds-marquee {
      from {
        translate: 0;
      }

      to {
        /* The track plus the gap that follows it, so the copy arrives exactly
           where the first one was and not a gap early. */
        translate: calc(-100% - var(--ds-marquee-gap));
      }
    }

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

      /* The copy exists to make a loop seamless. With no loop it is the same row
         printed twice. */
      .ds-marquee__track + .ds-marquee__track {
        display: none;
      }
    }


}
@layer utilities {
  /* ==========================================================================
     Utilities: empty, and this is the decision rather than a gap.

     D2, taken 20 August 2026: OS ships no utility classes, ever. The layer is
     declared so the order in `src/index.css` stays readable and so a session
     coming here to add one finds the ruling instead of an empty file.

     What a page may write is a ROLE, and a role names what the thing IS rather
     than which property to set: the twelve `ds-font-*` and the seven
     `ds-text-colour--*` in `src/roles/`. Everything else is a component or a
     prop on one, in both renderers. The one exception the ruling reserves is
     accessibility, which no component can own.

     The measurement that settles it, taken over the 23 projects on the previous
     system, where both a utility and a component were on the shelf: 874 elements
     build a flexbox by hand, 431 of them columns, against 176 uses of the two
     things that exist for exactly that, 62 elements wearing `.ds-stack` and 114
     of `<Stack>`. Ship both and the utility wins, because it is already in the
     file being edited and the import is not. OSROADMAP.md D2 carries the rest of
     the count.

     `npm run check` holds this: the layer compiles to the one rule below and
     nothing else.

     ── The exception, written 12 September 2026 (P4.3) ──────────────────────
     Accessibility is the one thing D2 reserves, because no component can own
     it: a word said to a screen reader and shown to nobody belongs to whatever
     element needs it, a live region, a table's caption, the label of a mark, and
     a page reaches for it without a component in between. One class, and it is
     the whole of the exception: `.ds-sr-only`. The skip link is not here, since
     a shell owns it (`.ds-site__skip`, `.ds-app-shell__skip`), and reduced
     motion is not a class at all: `tokens/motion.css` zeroes the durations under
     the media query. The old estate wore `ds-sr-only` nine times, and the count
     is not what earns it its place.
     ========================================================================== */

  /* Off screen and out of the box model, still in the accessibility tree. The
     clip and the 1px box are what every visually-hidden recipe has converged on:
     `display: none` and `visibility: hidden` take the words away from the reader
     too, and a negative text-indent loses right-to-left. The build wraps this
     file in its layer, so the rule stands bare here. */
  .ds-sr-only {
    position: absolute;
    inline-size: 1px;
    block-size: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
  }

}
}
