/* Fustat — the BASE Cuesoft brand typeface. Files shipped in assets/fonts/ (SIL OFL, see OFL.txt).
   The type system is multi-font by ruling (31 Aug 2026): Fustat is the base voice, and the
   sanctioned companions are Caveat Bold (script accents above headlines), Playfair Display
   Black + Instrument Serif (serif editorial headlines on lime and cream fields), Inter and
   JetBrains Mono (product surfaces, under Fustat ExtraBold 800 for product display) and
   Bricolage Grotesque (ID card, owner ruling). Manrope is retired (owner ruling, 6 Sep 2026):
   neither product site loads it, so it is declared nowhere and shipped nowhere.
   Every Figma-approved open-source family ships in assets/fonts/ with its OFL file. */
@font-face{font-family:'Fustat';src:url('/assets/design-system/fonts/Fustat-ExtraLight.ttf') format('truetype');font-weight:200;font-style:normal;font-display:swap}
@font-face{font-family:'Fustat';src:url('/assets/design-system/fonts/Fustat-Light.ttf') format('truetype');font-weight:300;font-style:normal;font-display:swap}
@font-face{font-family:'Fustat';src:url('/assets/design-system/fonts/Fustat-Regular.ttf') format('truetype');font-weight:400;font-style:normal;font-display:swap}
@font-face{font-family:'Fustat';src:url('/assets/design-system/fonts/Fustat-Medium.ttf') format('truetype');font-weight:500;font-style:normal;font-display:swap}
@font-face{font-family:'Fustat';src:url('/assets/design-system/fonts/Fustat-SemiBold.ttf') format('truetype');font-weight:600;font-style:normal;font-display:swap}
@font-face{font-family:'Fustat';src:url('/assets/design-system/fonts/Fustat-Bold.ttf') format('truetype');font-weight:700;font-style:normal;font-display:swap}
@font-face{font-family:'Fustat';src:url('/assets/design-system/fonts/Fustat-ExtraBold.ttf') format('truetype');font-weight:800;font-style:normal;font-display:swap}
@font-face{font-family:'Fustat Variable';src:url('/assets/design-system/fonts/Fustat-VariableFont_wght.ttf') format('truetype-variations');font-weight:200 800;font-style:normal;font-display:swap}
@font-face{font-family:'Caveat';src:url('/assets/design-system/fonts/Caveat-VariableFont_wght.ttf') format('truetype-variations');font-weight:400 700;font-style:normal;font-display:swap}
@font-face{font-family:'Playfair Display';src:url('/assets/design-system/fonts/PlayfairDisplay-VariableFont_wght.ttf') format('truetype-variations');font-weight:400 900;font-style:normal;font-display:swap}
@font-face{font-family:'Instrument Serif';src:url('/assets/design-system/fonts/InstrumentSerif-Regular.ttf') format('truetype');font-weight:400;font-style:normal;font-display:swap}
@font-face{font-family:'Instrument Serif';src:url('/assets/design-system/fonts/InstrumentSerif-Italic.ttf') format('truetype');font-weight:400;font-style:italic;font-display:swap}
@font-face{font-family:'Bricolage Grotesque';src:url('/assets/design-system/fonts/BricolageGrotesque-VariableFont_opsz_wdth_wght.ttf') format('truetype-variations');font-weight:200 800;font-stretch:75% 100%;font-style:normal;font-display:swap}
@font-face{font-family:'Inter';src:url('/assets/design-system/fonts/Inter-VariableFont_opsz_wght.ttf') format('truetype-variations');font-weight:100 900;font-style:normal;font-display:swap}
@font-face{font-family:'JetBrains Mono';src:url('/assets/design-system/fonts/JetBrainsMono-VariableFont_wght.ttf') format('truetype-variations');font-weight:100 800;font-style:normal;font-display:swap}
:root{--font-script:'Caveat',cursive; /* @kind font */ --font-serif-editorial:'Playfair Display','Instrument Serif',Georgia,serif; /* @kind font */}
:root{--font-brand:'Fustat',-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica Neue',Arial,sans-serif; /* @kind font */--font-brand-variable:'Fustat Variable','Fustat',sans-serif;--font-id:'Bricolage Grotesque',sans-serif;--font-product-ui:'Inter',sans-serif;--font-product-display:'Fustat',-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;--font-product-mono:'JetBrains Mono',monospace}

/* GENERATED by scripts/figma-web/generate.mjs from figma-scenes/variables.json — the Figma
   variable collections as CSS custom properties. Colours are hex; FLOAT tokens carry px.
   A variable whose Dark mode differs from Light also emits a `-dark` property; the theme re-points
   its role at that one when the lights go off, so the primitives themselves never switch. */
:root {
  /* Brand Colors */
  --primitive-black: #000000;
  --primitive-white: #ffffff;
  --primitive-blue: #325ea6;
  --primitive-lime: #bfff72;
  --color-surface-dark: var(--primitive-black);
  --color-text-on-dark: var(--primitive-white);
  --color-accent: var(--primitive-lime);
  --color-brand-blue: var(--primitive-blue);
  --color-text-on-light: var(--primitive-black);
  --surface-black: var(--primitive-black);
  --surface-lime: var(--primitive-lime);
  --surface-blue: var(--primitive-blue);
  --surface-white: var(--primitive-white);
  --text-on-lime: var(--primitive-black);
  --text-on-blue: var(--primitive-white);
  --text-muted-on-dark: rgba(255,255,255,0.85);
  --text-body-on-blue: rgba(255,255,255,0.92);
  --rule-on-dark: rgba(255,255,255,0.2);
  --pill-border-on-blue: rgba(255,255,255,0.4);
  --quote-glyph-on-lime: rgba(0,0,0,0.12);
  --motif-ink-on-lime: rgba(0,0,0,0.14);
  --link-default: var(--primitive-blue);
  --link-hover: #264880;
  /* 1 · Colour */
  --brand-black: #000000;
  --brand-white: #ffffff;
  --brand-lime: #bfff72;
  --brand-blue: #325ea6;
  --ink-caption: #8a8a8a;
  --ink-dim: #9a9a9a;
  --field-card: #171717;
  --cueprise-brand: #5300b8;
  --cueprise-lift: #a78bfa;
  --cueprise-deep: #1a1026;
  --cueprise-raised: #241733;
  --cueprise-lilac: #f6f3fb;
  --storefront-lift: #8fb3e8;
  --storefront-deep: #101b2e;
  --storefront-raised: #17243b;
  --storefront-mist: #f1f5fc;
  --storefront-sand: #f7f2ec;
  --neutral-ink-heading: #101828;
  --neutral-ink-heading-dark: #ffffff;
  --neutral-ink-body: #344054;
  --neutral-ink-body-dark: rgba(255,255,255,0.76);
  --neutral-ink-muted: #656f84;
  --neutral-ink-muted-dark: rgba(255,255,255,0.62);
  --neutral-hairline: #e4e7ec;
  --neutral-hairline-dark: rgba(255,255,255,0.1);
  --neutral-sunken: #f9fafb;
  --dataviz-series-3: #8fb3e8;
  --dataviz-other: #14264a;
  --dark-base: #121212; /* Dark-mode field shade. Nothing darker than #0E0E0E anywhere. */
  --dark-raised: #181818; /* Dark-mode field shade. Nothing darker than #0E0E0E anywhere. */
  --dark-panel: #1f1f1f; /* Dark-mode field shade. Nothing darker than #0E0E0E anywhere. */
  --dark-hero: #0e0e0e; /* Dark-mode field shade. Nothing darker than #0E0E0E anywhere. */
  --field-paper: #f5f5f5;
  --field-raised: #ededed;
  --field-navy: #0b1424;
  --field-canvas: #12233f;
  --field-cream: #f6f3ec;
  --state-error: #d11c1c;
  --state-focus: var(--brand-blue); /* Focus ring on a light field: focus/ring wide, focus/offset out from the control. */
  --state-focus-on-dark: var(--brand-lime); /* Focus ring on a dark, blue or black field. */
  --state-focus-product: var(--cueprise-brand); /* Product set focus ring; a Storefront page re-points it to brand/blue. */
  --state-focus-product-on-dark: var(--cueprise-lift); /* Product set focus ring in the dark; a Storefront page re-points it to storefront/lift. */
  --state-lime-hover: #a8e064; /* Lime control hovered: lime at 88% over black. */
  --state-lime-pressed: #99cc5b; /* Lime control pressed: lime at 80% over black. */
  --state-black-hover: #333333; /* Black control hovered (light theme): black at 80% over white. */
  --state-black-pressed: #4d4d4d; /* Black control pressed (light theme): black at 70% over white. */
  --state-white-hover: #d0d0d0; /* White control hovered (dark theme): white at 80% over dark/base. */
  --state-white-pressed: #b8b8b8; /* White control pressed (dark theme): white at 70% over dark/base. */
  --state-wash-on-light: rgba(0,0,0,0.08); /* Outline or quiet control hovered on a light field. */
  --state-wash-pressed-on-light: rgba(0,0,0,0.14); /* Outline or quiet control pressed on a light field. */
  --state-wash-on-dark: rgba(255,255,255,0.08); /* Outline or quiet control hovered on a dark field. */
  --state-wash-pressed-on-dark: rgba(255,255,255,0.14); /* Outline or quiet control pressed on a dark field. */
  --state-cueprise-hover: #430094; /* Cueprise brand control hovered (the live site’s --brand-hover). */
  --state-cueprise-pressed: #3a0081; /* Cueprise brand control pressed. */
  --state-storefront-hover: #264880; /* Storefront brand control hovered (the live site’s --brand-hover). */
  --state-storefront-pressed: #234274; /* Storefront brand control pressed. */
  --cutout-card: #ffffff;
  --cutout-card-dark: #1b1229;
  --cutout-sunken: #f9fafb;
  --cutout-sunken-dark: #221733;
  --cutout-border: #e4e7ec;
  --cutout-border-dark: rgba(255,255,255,0.1);
  --cutout-hover: #f9f5ff;
  --cutout-hover-dark: #291b3d;
  --cutout-card-storefront: #ffffff;
  --cutout-card-storefront-dark: #121b26;
  --cutout-sunken-storefront: #f9fafb;
  --cutout-sunken-storefront-dark: #17222f;
  --cutout-hover-storefront: #eff4fd;
  --cutout-hover-storefront-dark: #1c2837;
  --state-ok: #027a48;
  --state-ok-dark: #6ce9a6;
  --state-ok-field: #ecfdf3;
  --state-ok-field-dark: rgba(108,233,166,0.12);
  --state-warn: #b54708;
  --state-warn-dark: #fdb022;
  --state-warn-field: #fffaeb;
  --state-warn-field-dark: rgba(253,176,34,0.12);
  --state-bad: #b42318;
  --state-bad-dark: #fda29b;
  --state-bad-field: #fef3f2;
  --state-bad-field-dark: rgba(253,162,155,0.12);
  --chart-1: #5300b8;
  --chart-2: #7c3aed;
  --chart-3: #a78bfa;
  --chart-4: #ddd0fb;
  --product-accent-ink: var(--cueprise-brand);
  --product-accent-ink-dark: var(--cueprise-lift);
  --product-accent-ink-storefront: var(--brand-blue);
  --product-accent-ink-storefront-dark: var(--storefront-lift);
  --neutral-ink-strong: #1d2939;
  --neutral-ink-strong-dark: rgba(255,255,255,0.92);
  --neutral-hairline-strong: #d0d5dd;
  --neutral-hairline-strong-dark: rgba(255,255,255,0.18);
  --pointer-ring-fill: rgba(83,0,184,0.15); /* Pointer target click ring — brand at 15% alpha (paint-level opacity does not survive on variable-bound paints). */
  --pointer-ring-stroke: rgba(83,0,184,0.4); /* Pointer target click ring — brand at 40% alpha (paint-level opacity does not survive on variable-bound paints). */
  --pointer-ring-fill-storefront: rgba(50,94,166,0.15); /* Pointer target click ring — brand at 15% alpha (paint-level opacity does not survive on variable-bound paints). */
  --pointer-ring-stroke-storefront: rgba(50,94,166,0.4); /* Pointer target click ring — brand at 40% alpha (paint-level opacity does not survive on variable-bound paints). */
  --surface-hover: rgba(0,0,0,0.03); /* Card hover wash: 3% ink laid over the card's own fill (never alone). Corporate cards (Feature card grid, Case study card). Product cards take cutout/hover (Cueprise) or cutout/hover · storefront. Code: opaque via color-mix on the base surface. */
  --surface-hover-dark: rgba(255,255,255,0.03);
  --cuelearn-brand: var(--brand-blue); /* Cuelearn palette. Aliased to the house blue (brand/blue) until the owner rules the Cuelearn accent. */
  --cuelearn-lift: var(--storefront-lift); /* Cuelearn palette. Aliased to the house blue (storefront/lift) until the owner rules the Cuelearn accent. */
  --cuelearn-deep: var(--storefront-deep); /* Cuelearn palette. Aliased to the house blue (storefront/deep) until the owner rules the Cuelearn accent. */
  --cuelearn-raised: var(--storefront-raised); /* Cuelearn palette. Aliased to the house blue (storefront/raised) until the owner rules the Cuelearn accent. */
  --cuelearn-mist: var(--storefront-mist); /* Cuelearn palette. Aliased to the house blue (storefront/mist) until the owner rules the Cuelearn accent. */
  --state-cuelearn-hover: var(--state-storefront-hover); /* Cuelearn palette. Aliased to the house blue (state/storefront-hover) until the owner rules the Cuelearn accent. */
  --state-cuelearn-pressed: var(--state-storefront-pressed); /* Cuelearn palette. Aliased to the house blue (state/storefront-pressed) until the owner rules the Cuelearn accent. */
  --cuelearn-mark: var(--brand-lime); /* Lesson timeline marks and playhead: the one lime element on a Cuelearn surface. */
  /* 2 · Layout */
  --space-xs: 16px;
  --space-sm: 24px;
  --space-md: 32px;
  --space-lg: 40px;
  --space-xl: 72px;
  --margin-feed: 40px;
  --margin-display: 48px;
  --margin-story: 56px;
  --radius-chip: 3px;
  --radius-pill: 0px;
  --radius-product-sm: 2px;
  --radius-product-md: 6px;
  --radius-product-lg: 6px;
  --space-2xl: 120px;
  --space-heading-gap: 72px;
  --space-heading-gap-product: 56px; /* Product set: gap between the section heading holder and its content (corporate uses space/heading-gap = 72) */
  --space-section-mobile: 64px;
  --space-heading-gap-mobile: 48px;
  --focus-ring: 3px; /* Focus ring stroke width in px. */
  --focus-offset: 2px; /* Gap between a control and its focus ring in px. */
  --radius-product-thumb: 4px;
}

/*
 * Role tokens: what a surface IS, not what colour it happens to be.
 *
 * The primitives underneath are the Figma variables (tokens/web.css) and never change. What changes
 * between light and dark is which primitive each role points at — so a component names the role, the
 * theme names the colour, and one DOM serves both. Nothing in this library writes a hex.
 *
 * Dark is the ladder agreed for the estate: four rungs, #0E0E0E to #1F1F1F, no coloured fields, lime as accent
 * only. The brand-blue bands follow that rule too: in the dark a blue band is a rung of the ladder,
 * the hero at the floor (#0E0E0E) and every other blue band on the panel (#1F1F1F), which is where
 * the canvas puts them. The ink on those bands is white in both themes, since both fields are deep.
 *
 * The ladder is the whole ESTATE's, not this set's: theme-product.css points Cueprise's and
 * Storefront's site fields at the same four rungs in the dark, because that is what every page-15
 * Theme=Dark master draws. What stays theirs in the dark is the accent, the lift, the ink, the app
 * kit's own card colours and the chrome that floats over the page. `scripts/dark-fields.test.mjs`
 * holds both sets to this paragraph and `scripts/check-dark-ink.mjs` measures every composed
 * page, so the comment cannot drift from what ships.
 */

/* Zero specificity on both sides, so the dark block below wins by source order and a site's own
   rule wins over either. `:root` at (0,1,0) would have outranked `:where(.dark)` and the theme
   would never have switched. */
:where(:root, .ds-root) {
  /* Native controls draw from this: a date picker, a scrollbar, a checkbox a site leaves unstyled.
     The base copy is what a light block inside a dark band restores it to. */
  color-scheme: light;
  --ds-color-scheme-base: light;

  /* The base voice. An element with no type role of its own inherits this rather than the
     browser's serif. Zero specificity, so a site's own base font still wins. */
  font-family: 'Fustat Variable', Fustat, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;

  /* Fields */
  --ds-surface: var(--brand-white);
  /* The field a CONTROL paints for itself — a choice card, an alert. White on paper, and one rung UP
     from the page in the dark rather than level with it: every `27 · Form` dark master puts a radio
     card and the alert on dark/raised over the dark/base page, and painting them --ds-surface put
     them at #121212 on #121212, where the card's only edge was its hairline. */
  --ds-surface-control: var(--brand-white);
  --ds-surface-paper: var(--field-paper);
  --ds-surface-raised: var(--field-raised);
  /* The well a control sits in. The corporate masters fill every field with the surface itself, so
     here the two are one name; the product set fills its fields a rung off the page (neutral/sunken
     in the light, the raised rung in the dark) and re-points this alone. Reading through --ds-surface
     rather than repeating a value keeps it right inside a band that paints its own field. */
  --ds-surface-sunken: var(--ds-surface);
  /* The row a pointer is on inside a panel: one rung off the panel it sits in. Corporate masters fill
     it field/paper under a white panel; the product set fills it with the well above. */
  --ds-surface-row-hover: var(--ds-surface-paper);
  --ds-surface-invert: var(--brand-black);
  /* An inverted PANEL — the footer, the Ace console — is a thing that sits ON the page, where an
     inverted BAND runs across it. In the light both are black on white; in the dark they part
     company, which is why the panel has a role of its own. */
  --ds-surface-invert-panel: var(--brand-black);
  /* The inverted panel on a paper band. Black on paper in the light, the same panel as on the page. */
  --ds-surface-invert-panel-on-paper: var(--brand-black);
  --ds-surface-brand: var(--brand-blue);
  /* The field a LINE-SIZED element takes on an inverted panel — the reader's own words in the Ace
     console. A band can name a ladder rung and be seen, because it is separated by a page of air;
     an element the size of a line cannot, since no two rungs of the dark ladder are further apart
     than 1.21:1. So this role is the brand blue on the light console's black, and a lift off the
     panel's own white in the dark, where a blue field is not allowed. */
  --ds-surface-on-invert: var(--brand-blue);
  /* The hero's field. The same blue as a brand band in the light; the two part in the dark, where
     the hero sits at the floor of the ladder and a band on its panel. */
  --ds-surface-hero: var(--brand-blue);
  /* Chrome that FLOATS over the page rather than sitting in it — the consent bar, the back-to-top
     disc. The corporate set has no separate answer for it and takes the raised field in both themes,
     which is what its Consent banner masters draw (field/raised in the light, dark/panel in the
     dark). The product set parts company here: its site fields join the corporate ladder in the dark
     while this one stays the brand's own raised tone, because the masters draw it that way — chrome
     that floats is not a field of the page. Stated as a primitive in every block rather than aliased
     to --ds-surface-raised: a custom property is substituted where it is DECLARED, so an alias
     written here computes to the CORPORATE raised field once and inherits that value into a product
     page, which put #EDEDED under a Cueprise consent bar and dropped its quiet button to 4.31:1. */
  --ds-surface-chrome: var(--field-raised);
  /* A panel that stands dark in both themes — the media card, what the company publishes as its own
     surface. Dark-raised on the light page, as the master draws it. */
  --ds-surface-standing: var(--dark-raised);
  /* The field a band can take. */
  --ds-surface-accent: var(--brand-lime);
  /* The accent itself — the primary button, a chip, a rule. Lime in both themes; it is the one
     place the accent is spent, and it never becomes a field in the dark. */
  --ds-accent: var(--brand-lime);
  /* The accent as INK is a different question from the accent as a FIELD. Lime on white measures
     1.18:1, so on a light page the accent is never the ink. What the canvas uses instead is the
     brand blue: every accented eyebrow, step number, ACE letter and card link on a light corporate
     master is #325EA6, which measures 6.4:1. On a dark field the ink is the accent itself, which is
     what the blocks below and the field rules say. */
  --ds-accent-ink: var(--brand-blue);
  --ds-ink-on-accent-control: var(--brand-black);
  /* The card a reader has chosen. `Element=Radio card selected` inverts on paper — black field,
     white ink, under a 2px black rule — and in the dark stays on the control's own field with the
     rule turned white, because the dark ladder never flips a field to white. The 2px rule is the
     only thing that changes size between the two states, so it is drawn as an inset ring and the
     selected and unselected cards measure the same height. */
  --ds-selected-surface: var(--brand-black);
  --ds-selected-ink: var(--brand-white);
  --ds-selected-rule: var(--brand-black);

  /* Ink, by the field it sits on */
  --ds-ink: var(--brand-black);
  /* The label on a control filled with the ink (Button / corporate Style=Black): white on the black
     fill in light, black on the white fill in dark and on every dark field. */
  --ds-ink-inverse: var(--brand-white);
  --ds-ink-body: rgb(0 0 0 / 0.72);
  /* The muted rung is the canvas's 60%: what the three nav masters paint for the bar's links and for
     the section label behind its hairline, in both themes, and what the dark ladder below already
     carried. It was 56% here on light alone — the WCAG AA floor read as if it were the value — which
     left the light ladder four points lighter than its own dark twin and than the masters. 60% black
     on white measures 5.7:1. */
  --ds-ink-muted: rgb(0 0 0 / 0.6);
  /* What a form paints for text that is not a value yet: the placeholder, the unchosen select, the
     empty date. Form / corporate (1067:2230) draws every light placeholder at 56% black and every dark
     twin at 60% white, while the counter, the helper and the notice beside them stay on the muted
     rung. 56% black on white measures 4.9:1. Only the three placeholder rules read it. */
  --ds-ink-placeholder: rgb(0 0 0 / 0.56);
  --ds-ink-on-brand: var(--brand-white);
  --ds-ink-body-on-brand: rgb(255 255 255 / 0.92);
  /* The light accent field is lime. White on lime measures about 1.3:1 — the ink here is black,
     and the dark block below flips it because there the accent field is a panel, not the accent. */
  --ds-ink-on-accent: var(--brand-black);
  --ds-ink-on-invert: var(--brand-white);

  /* The theme's own ink ladder, kept aside. A band that paints a dark field re-points --ds-ink and
     everything under it for its subtree; a light block inside that band — a callout on a blue band,
     a white card on black — has to put the ladder back, and it cannot write a hex to do it because
     "light" is a different colour in each theme. These are what it restores from. */
  --ds-ink-base: var(--ds-ink);
  --ds-ink-inverse-base: var(--ds-ink-inverse);
  --ds-ink-body-base: var(--ds-ink-body);
  --ds-ink-muted-base: var(--ds-ink-muted);
  --ds-ink-placeholder-base: var(--ds-ink-placeholder);
  --ds-rule-base: var(--ds-rule);
  --ds-rule-strong-base: var(--ds-rule-strong);
  --ds-rule-outline-base: var(--ds-rule-outline);
  --ds-accent-ink-base: var(--ds-accent-ink);

  /* Lines and states */
  --ds-rule: rgb(0 0 0 / 0.12);
  --ds-rule-strong: rgb(0 0 0 / 0.4);
  /* The outlined callout's hairline: heavier than a divider because the box has no fill to hold it,
     lighter on paper than on a dark ground because black at 20% on white reads as a border. */
  --ds-rule-outline: rgb(0 0 0 / 0.15);
  /* The hairline an inverted panel draws on its own top edge: every Footer master, corporate and
     product, carries a 1px inside top stroke of white at 12%, in the light on black as in the dark
     on the ladder. The Ask Ace console carries no stroke on the canvas and draws none. */
  --ds-rule-on-invert: rgb(255 255 255 / 0.12);
  /* What a block paints where its content has not arrived: the file's own placeholder ink, 6% of the
     black on light and 8% of the white on dark (Contributor grid 1135:6691 State=Loading, and the
     Image placeholder master before it — white at 6% on the dark ladder does not read). It is an
     alpha rather than a colour so a block mixes it into whatever ink its band re-pointed. */
  --ds-placeholder-alpha: 6%;
  --ds-error: var(--state-error);
  /* The focus ring the estate ships: `focus/ring` (3px) solid, `focus/offset` (2px) outside the
     control, on every control. Its colour follows the FIELD the ring lands on, not the control —
     `state/focus` (blue) on a light ground, `state/focus-on-dark` (lime) wherever the ground is blue
     or dark, blue again on lime — so the two ladders in components.css re-point --ds-focus alongside
     the ink, and --ds-focus-base is what a light block inside a dark band restores it from. The width
     and the offset never vary: an inset ring on one control beside an outset ring on the next is two
     systems, and the estate has one. */
  --ds-focus: var(--state-focus);
  --ds-focus-base: var(--ds-focus);
  --ds-focus-width: var(--focus-ring);
  --ds-focus-offset: var(--focus-offset);
  /* A REGION that takes the focus (the submitted panel, a tab panel) runs its text to its own edge,
     so the one ring 2px out of it hugs the words. It keeps the one ring and pads itself instead:
     the text clears the ring by --ds-focus-clearance, the 8px the Input and Select rings clear
     theirs by, and the matching negative margin leaves the text where it was. */
  --ds-focus-clearance: 8px;
  --ds-focus-inset: calc(var(--ds-focus-clearance) - var(--ds-focus-offset));

  /* The field an element takes under the pointer, and pressed: the field it already sits on with a
     whisper of the ink that reads on it mixed IN. The canvas variable is `surface/hover` — 3% ink
     over the card's own fill, black in Light and white in Dark (`--surface-hover` in tokens/web.css)
     — and its own description asks for it "opaque via color-mix on the base surface", which is what
     happens here. Opaque matters: a hover that swaps an opaque field for a translucent wash lets the
     hairline bed behind a card grid bleed through it, which is how a 3% tint shipped as #D9D9D9 on
     live cuelabs. The tint is the ink that reads on the field, so a band that paints another field
     re-points it and the pair with it (components.css). Pressed is one step further, which the
     canvas has no variable for. */
  --ds-surface-tint: var(--ds-ink);
  --ds-surface-mix: 3%;
  --ds-surface-mix-strong: 5%;
  --ds-surface-hover: color-mix(in srgb, var(--ds-surface-tint) var(--ds-surface-mix), var(--ds-surface));
  --ds-surface-active: color-mix(in srgb, var(--ds-surface-tint) var(--ds-surface-mix-strong), var(--ds-surface));
  --ds-surface-hover-base: var(--ds-surface-hover);
  --ds-surface-active-base: var(--ds-surface-active);

  /* Depth. The corporate set casts no shadow, so both tokens are a layer that draws nothing rather
     than `none`: they sit inside shadow LISTS (`inset 0 0 0 1px …, var(--ds-shadow-card)`), and
     `none` inside a list invalidates the whole declaration and takes the hairline ring with it.
     theme-product.css re-points them at the two recipes the product sites cast. */
  --ds-shadow-card: 0 0 0 0 transparent;
  --ds-shadow-raised: 0 0 0 0 transparent;

  /* Rhythm. The canvas's own spacing, named for what it spaces. */
  --ds-page-max: 1440px;
  --ds-gutter: 24px;
  --ds-section-y: var(--space-section-mobile);
  --ds-heading-gap: var(--space-heading-gap-mobile);
  --ds-card-gap: var(--space-sm);
  --ds-radius-chip: var(--radius-chip);
  /* The site bar's height, which the hero and every anchored section measure from. */
  --ds-nav-height: 64px;

  /* The foot of the window. A floating control sits --ds-dock-inset in from the corner; chrome docked
     along the bottom (the consent bar) publishes its height as --ds-dock on the root so the control
     floats above it and the page's last block reserves that room below its last line. */
  --ds-dock-inset: var(--space-sm);
  --ds-dock-control: 48px;

  /* Corners. The corporate set is square: control, card and thumbnail are all 0 and the 3px chip is
     its one exception. The product set re-points the same roles (theme-product.css). */
  --ds-radius-control: 0px;
  --ds-radius-card: 0px;
  --ds-radius-thumb: 0px;

  /* Icon sizes. Lucide's 24 grid at stroke 2, so the stroke scales with the box: 1.67px at 20. */
  --ds-icon-14: 14px;
  --ds-icon-16: 16px;
  --ds-icon-18: 18px;
  --ds-icon-20: 20px;
  --ds-icon-24: 24px;
}

@media (min-width: 768px) {
  :where(:root, .ds-root) {
    --ds-gutter: 130px;
    --ds-section-y: var(--space-2xl);
    --ds-heading-gap: var(--space-heading-gap);
  }
}

/* 72 is a ruling (rule 24: nav 72 + hero 828 ≤ 900); the live division bars are 64. Changing it
   needs the owner, then the canvas, then here. */
@media (min-width: 900px) {
  :where(:root, .ds-root) {
    --ds-nav-height: 72px;
  }
}

/*
 * Dark. Both selectors, because a site puts the class on <html> and a section may ask for a dark
 * field on its own. `:where` keeps the specificity at zero so a site's own override still wins.
 */
:where(.dark, [data-theme='dark']) {
  color-scheme: dark;
  --ds-color-scheme-base: dark;
  --ds-surface: var(--dark-base);
  --ds-surface-control: var(--dark-raised);
  --ds-surface-paper: var(--dark-raised);
  --ds-surface-raised: var(--dark-panel);
  /* An inverted band goes DEEPER in dark mode, never white. Flipping it to white is the mistake
     that makes a dark page strobe: the ladder only ever runs #0E0E0E to #1F1F1F. */
  --ds-surface-invert: var(--dark-hero);
  /* An inverted PANEL goes the other way, one rung UP: the Footer and Ask Ace dark masters put it on
     dark/raised over the dark/base page. The floor would set it #0E0E0E on #121212, four levels
     apart, and the panel is lost — which is what shipped before this role existed. */
  --ds-surface-invert-panel: var(--dark-raised);
  /* Paper is dark/raised here, the rung the panel takes on the page, so a panel on a paper band would
     paint the band's own colour and have no edge. It climbs one more rung, to dark/panel. */
  --ds-surface-invert-panel-on-paper: var(--dark-panel);
  /* No coloured fields in the dark. A blue band becomes the panel and the hero the floor — the two
     rungs the canvas gives them — while the accent BAND becomes a panel too and the accent control
     below stays lime. The blue survives only as the light theme's accent ink. */
  --ds-surface-brand: var(--dark-panel);
  /* The rung the brand band takes here is dark/panel over a dark/raised console: 1.11:1, a field
     nobody can see. A line-sized element gets a lift off the panel instead — white at 22% over
     dark/raised reads 2.03:1, which is a shape, and still nothing the ladder would call a colour. */
  --ds-surface-on-invert: rgb(255 255 255 / 0.22);
  --ds-surface-hero: var(--dark-hero);
  /* Chrome that floats takes the raised field in the dark too, as Consent banner / corporate draws. */
  --ds-surface-chrome: var(--dark-panel);
  /* The standing panel steps down to the floor: the paper it sits on is dark-raised here already. */
  --ds-surface-standing: var(--dark-hero);
  --ds-surface-accent: var(--dark-panel);

  --ds-ink: var(--brand-white);
  --ds-ink-inverse: var(--brand-black);
  --ds-ink-body: rgb(255 255 255 / 0.78);
  --ds-ink-muted: rgb(255 255 255 / 0.6);
  --ds-ink-placeholder: var(--ds-ink-muted);
  --ds-ink-on-brand: var(--brand-white);
  --ds-ink-body-on-brand: rgb(255 255 255 / 0.92);
  --ds-ink-on-accent: var(--brand-white);
  --ds-ink-on-invert: var(--brand-white);

  --ds-accent-ink: var(--ds-accent);
  /* The hairline is 12% in both themes, which is what the canvas draws: page 14 carries black at
     12% on every light master and white at 12% on every dark one, the Accordion's own rows
     included. The 16% this token shipped with had no master behind it and made the dark hairline
     a third heavier than its light twin. */
  --ds-rule: rgb(255 255 255 / 0.12);
  --ds-rule-strong: rgb(255 255 255 / 0.45);
  --ds-rule-outline: rgb(255 255 255 / 0.2);
  --ds-placeholder-alpha: 8%;

  /* The dark theme's own ladder, for the same restoring job. */
  --ds-ink-base: var(--ds-ink);
  --ds-ink-inverse-base: var(--ds-ink-inverse);
  --ds-ink-body-base: var(--ds-ink-body);
  --ds-ink-muted-base: var(--ds-ink-muted);
  --ds-ink-placeholder-base: var(--ds-ink-placeholder);
  --ds-rule-base: var(--ds-rule);
  --ds-rule-strong-base: var(--ds-rule-strong);
  --ds-rule-outline-base: var(--ds-rule-outline);
  --ds-accent-ink-base: var(--ds-accent-ink);
  /* The brand's red is for ink on paper; on the dark ladder it measures under 3:1, so the dark
     theme lightens it. Still red, still only ever a state. */
  --ds-error: #ff8a8a;
  /* Every ground in the dark is dark, so the ring is lime everywhere — the same answer the live
     sites give with `.dark { --ring: lime }`. */
  --ds-focus: var(--state-focus-on-dark);
  --ds-focus-base: var(--ds-focus);
  /* No inversion in the dark: the chosen card keeps the control's field and takes the white rule,
     as `Element=Radio card selected, Theme=Dark` draws it. */
  --ds-selected-surface: var(--ds-surface-control);
  --ds-selected-ink: var(--ds-ink);
  --ds-selected-rule: var(--brand-white);
  /* The pair is RESTATED here, not inherited. A custom property's value is substituted where it is
     declared, so `:root`'s color-mix computes once against the LIGHT field and every dark scope then
     inherits that already-resolved near-white — a `<div class="dark">` inside a light document drew
     white text and a lime link on #F7F7F7. Restating it makes the mix resolve against this block's
     own tint and field. The steps are 6% and 10%, not the light theme's 3% and 5%: 3% white over
     #121212 measures 1.07:1 against the rest field, which no reader sees, while 6% measures 1.15:1
     — the perceptible step live cueprise's dark #291B3D takes over its own card. */
  --ds-surface-tint: var(--ds-ink);
  --ds-surface-mix: 6%;
  --ds-surface-mix-strong: 10%;
  --ds-surface-hover: color-mix(in srgb, var(--ds-surface-tint) var(--ds-surface-mix), var(--ds-surface));
  --ds-surface-active: color-mix(in srgb, var(--ds-surface-tint) var(--ds-surface-mix-strong), var(--ds-surface));
  --ds-surface-hover-base: var(--ds-surface-hover);
  --ds-surface-active-base: var(--ds-surface-active);
}

/* The document ground every site gets from this sheet and writes none of its own: no UA body margin,
   the page field and its ink under either theme. On body alone, so the browser carries body's field to
   the whole canvas and a set class on body (.ds-product) grounds the overscroll and short pages too.
   Zero specificity, so a site's own html or body rule still wins. scripts/document-ground.test.mjs and
   check-site's stylesheet rule hold it. */
:where(body) {
  margin: 0;
  background: var(--ds-surface);
  color: var(--ds-ink);
}

/* The page column: body is at least the viewport tall and main takes the slack, so a page shorter than
   the viewport ends on its footer rather than on a strip of ground below it. One wrapper between body
   and main that also holds the footer (ProductTheme, an app's #root) passes the column on; a host
   nested deeper sets its own. scripts/page-column.test.mjs holds it. */
:where(body) {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  min-height: 100dvh;
}
:where(body > :has(> main):has(> footer)) {
  display: flex;
  flex-direction: column;
  flex: 1 0 auto;
}
:where(body > main, body > * > main) {
  flex: 1 0 auto;
}

/* GENERATED by scripts/build-library-type.mjs from figma-scenes/text-styles.json — do not edit.
   The type scale is the canvas's; a site restyles it the way it restyles any stylesheet. */

/* The faces, by name. Every role below states its own stack; these let a stylesheet that is not part
   of the type scale name a face without copying it. */
:root {
  --ds-face-fustat: 'Fustat Variable', Fustat, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  --ds-face-inter: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  --ds-face-jetbrains-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
  --ds-face-caveat: Caveat, cursive;
  --ds-face-playfair-display: 'Playfair Display', Georgia, serif;
  --ds-face-instrument-serif: 'Instrument Serif', Georgia, serif;
}

.ds-t-display { font-family: 'Fustat Variable', Fustat, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-weight: 600; font-size: 38px; line-height: normal; letter-spacing: -0.04em; }
.ds-t-h1-sm { font-family: 'Fustat Variable', Fustat, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-weight: 600; font-size: 30px; line-height: normal; letter-spacing: -0.045em; }
.ds-t-h2 { font-family: 'Fustat Variable', Fustat, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-weight: 600; font-size: 30px; line-height: normal; letter-spacing: -0.045em; }
.ds-t-h3 { font-family: 'Fustat Variable', Fustat, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-weight: 600; font-size: 22px; line-height: normal; letter-spacing: -0.03em; }
.ds-t-h3-lead { font-family: 'Fustat Variable', Fustat, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-weight: 600; font-size: 30px; line-height: normal; letter-spacing: -0.045em; }
.ds-t-h4 { font-family: 'Fustat Variable', Fustat, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-weight: 600; font-size: 19px; line-height: 23px; letter-spacing: -0.02em; }
.ds-t-lede { font-family: 'Fustat Variable', Fustat, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-weight: 500; font-size: 17px; line-height: 26px; }
.ds-t-body { font-family: 'Fustat Variable', Fustat, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-weight: 500; font-size: 14px; line-height: 22px; }
.ds-t-body-bold { font-family: 'Fustat Variable', Fustat, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-weight: 600; font-size: 14px; line-height: 22px; }
.ds-t-nav { font-family: 'Fustat Variable', Fustat, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-weight: 500; font-size: 14px; line-height: 16px; }
.ds-t-cta { font-family: 'Fustat Variable', Fustat, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-weight: 700; font-size: 12px; line-height: 13px; letter-spacing: 0.045em; text-transform: uppercase; }
.ds-t-eyebrow { font-family: 'Fustat Variable', Fustat, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-weight: 500; font-size: 11px; line-height: 13px; letter-spacing: 0.08em; text-transform: uppercase; }
.ds-t-chip { font-family: 'Fustat Variable', Fustat, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-weight: 500; font-size: 10px; line-height: 12px; letter-spacing: 0.05em; text-transform: uppercase; }
.ds-t-caption { font-family: 'Fustat Variable', Fustat, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-weight: 500; font-size: 12px; line-height: normal; }
.ds-t-footer { font-family: 'Fustat Variable', Fustat, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-weight: 500; font-size: 14px; line-height: 21px; }
.ds-t-figure { font-family: 'Fustat Variable', Fustat, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-weight: 600; font-size: 19px; line-height: 23px; letter-spacing: -0.02em; font-variant-numeric: tabular-nums; }
.ds-t-meta { font-family: 'Fustat Variable', Fustat, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-weight: 500; font-size: 12px; line-height: normal; }
.ds-t-card-title { font-family: 'Fustat Variable', Fustat, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-weight: 600; font-size: 22px; line-height: normal; letter-spacing: -0.03em; }
.ds-t-script { font-family: Caveat, cursive; font-weight: 700; font-size: 40px; line-height: 44px; }
.ds-t-serif { font-family: 'Instrument Serif', Georgia, serif; font-weight: 400; font-size: 65px; line-height: 62px; letter-spacing: -0.01em; font-style: italic; }
.ds-t-product-display { font-family: 'Fustat Variable', Fustat, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-weight: 700; font-size: 38px; line-height: 42px; letter-spacing: -0.02em; }
.ds-t-product-h2 { font-family: 'Fustat Variable', Fustat, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-weight: 600; font-size: 30px; line-height: normal; letter-spacing: -0.045em; }
.ds-t-product-body { font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-weight: 400; font-size: 17px; line-height: 26px; }
.ds-t-product-body-sm { font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-weight: 400; font-size: 14px; line-height: 22px; }
.ds-t-product-nav { font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-weight: 500; font-size: 14px; line-height: 16px; }
.ds-t-product-nav-link { font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-weight: 400; font-size: 14px; line-height: 16px; }
.ds-t-product-nav-sheet { font-family: 'Fustat Variable', Fustat, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-weight: 700; font-size: 22px; line-height: 34px; }
.ds-t-product-field-label { font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-weight: 500; font-size: 14px; line-height: 16px; }
.ds-t-product-cta { font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-weight: 500; font-size: 12px; line-height: 13px; letter-spacing: 0.045em; text-transform: uppercase; }
.ds-t-product-eyebrow { font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace; font-weight: 500; font-size: 12px; line-height: normal; letter-spacing: 0.08em; text-transform: uppercase; }
.ds-t-product-chip { font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-weight: 500; font-size: 12px; line-height: normal; letter-spacing: 0.05em; text-transform: uppercase; }
.ds-t-product-ui { font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-weight: 600; font-size: 20px; line-height: normal; letter-spacing: -0.02em; }
.ds-t-product-ui-sm { font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-weight: 600; font-size: 18px; line-height: normal; letter-spacing: -0.02em; }
.ds-t-product-caption { font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-weight: 400; font-size: 13px; line-height: normal; }
.ds-t-mono { font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace; font-weight: 500; font-size: 12px; line-height: 13px; letter-spacing: 0.04em; }
.ds-t-kit-screen-21 { font-family: 'Fustat Variable', Fustat, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-weight: 800; font-size: 21px; line-height: 1.06; letter-spacing: -0.022em; }
.ds-t-kit-ui-26 { font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-weight: 600; font-size: 26px; line-height: 31px; }
.ds-t-kit-ui-16 { font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-weight: 600; font-size: 16px; line-height: 19px; }
.ds-t-kit-ui-15 { font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-weight: 600; font-size: 15px; line-height: 18px; }
.ds-t-kit-ui-14 { font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-weight: 600; font-size: 14px; line-height: 17px; }
.ds-t-kit-ui-13 { font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-weight: 600; font-size: 13px; line-height: 16px; }
.ds-t-kit-ui-12 { font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-weight: 600; font-size: 12px; line-height: 15px; }
.ds-t-kit-row-13 { font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-weight: 500; font-size: 13px; line-height: 16px; }
.ds-t-kit-meta-13 { font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-weight: 400; font-size: 13px; line-height: 16px; }
.ds-t-kit-meta-12 { font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-weight: 400; font-size: 12px; line-height: 15px; }
.ds-t-kit-meta-11 { font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-weight: 400; font-size: 11px; line-height: 13px; }
.ds-t-kit-meta-10 { font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-weight: 400; font-size: 10px; line-height: 12px; }
.ds-t-kit-link-12 { font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-weight: 500; font-size: 12px; line-height: 15px; }
.ds-t-kit-chip-11 { font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-weight: 500; font-size: 11px; line-height: 13px; }
.ds-t-kit-label-10 { font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-weight: 600; font-size: 10px; line-height: 12px; letter-spacing: 0.08em; text-transform: uppercase; }
.ds-t-kit-micro-9 { font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-weight: 400; font-size: 9px; line-height: 11px; }
.ds-t-kit-mono-eyebrow-10 { font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace; font-weight: 500; font-size: 10px; line-height: 13px; letter-spacing: 0.09em; text-transform: uppercase; }
.ds-t-kit-mono-eyebrow-9 { font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace; font-weight: 500; font-size: 9px; line-height: 12px; letter-spacing: 0.09em; text-transform: uppercase; }
.ds-t-kit-figure-28 { font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace; font-weight: 700; font-size: 28px; line-height: 1; letter-spacing: -0.01em; }
.ds-t-kit-figure-20 { font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace; font-weight: 700; font-size: 20px; line-height: 26px; letter-spacing: -0.01em; }
.ds-t-kit-figure-19 { font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace; font-weight: 700; font-size: 19px; line-height: 25px; letter-spacing: -0.025em; }
.ds-t-kit-figure-18 { font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace; font-weight: 700; font-size: 18px; line-height: 24px; letter-spacing: -0.01em; }
.ds-t-kit-figure-17 { font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace; font-weight: 700; font-size: 17px; line-height: 22px; letter-spacing: -0.01em; }
.ds-t-kit-figure-16 { font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace; font-weight: 700; font-size: 16px; line-height: 21px; letter-spacing: -0.01em; }
.ds-t-kit-figure-15 { font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace; font-weight: 700; font-size: 15px; line-height: 20px; letter-spacing: -0.01em; }
.ds-t-kit-figure-13 { font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace; font-weight: 500; font-size: 13px; line-height: 17px; letter-spacing: -0.01em; }
.ds-t-kit-figure-12 { font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace; font-weight: 500; font-size: 12px; line-height: 16px; letter-spacing: -0.01em; }
.ds-t-kit-figure-11 { font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace; font-weight: 500; font-size: 11px; line-height: 15px; letter-spacing: -0.01em; }
.ds-t-kit-figure-10 { font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace; font-weight: 500; font-size: 10px; line-height: 13px; letter-spacing: -0.01em; }
.ds-t-field-label { font-family: 'Fustat Variable', Fustat, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-weight: 500; font-size: 14px; line-height: 16px; }

/* Headings wrap balanced: the heading elements, in any headline face. Running text set in a headline
   face (a quote, a statement, a label) keeps the default, as the masters fill it. */
:is(h1, h2, h3, h4, h5, h6):where(.ds-t-display, .ds-t-h1-sm, .ds-t-h2, .ds-t-h3, .ds-t-h3-lead, .ds-t-h4, .ds-t-card-title, .ds-t-serif, .ds-t-product-display, .ds-t-product-h2, .ds-t-product-ui, .ds-t-product-ui-sm) { text-wrap: pretty; text-wrap: balance; }

/* The wide end of the scale. Mobile is the base so a narrow viewport never pays for a rule it
   immediately overrides, and so a new size defaults to the safe one. The measure a heading face
   carries (the canvas's widest box, in ch of the face) lives here, where the column can exceed it. */
@media (min-width: 768px) {
  .ds-t-display { font-family: 'Fustat Variable', Fustat, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-weight: 600; font-size: 78px; line-height: 74px; letter-spacing: -0.04em; max-width: 15ch; }
  .ds-t-h1-sm { font-family: 'Fustat Variable', Fustat, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-weight: 600; font-size: 54px; line-height: 52px; letter-spacing: -0.04em; }
  .ds-t-h2 { font-family: 'Fustat Variable', Fustat, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-weight: 600; font-size: 63px; line-height: 60px; letter-spacing: -0.045em; max-width: 22ch; }
  .ds-t-h3 { font-family: 'Fustat Variable', Fustat, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-weight: 600; font-size: 41px; line-height: 42px; letter-spacing: -0.03em; }
  .ds-t-h3-lead { font-family: 'Fustat Variable', Fustat, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-weight: 600; font-size: 41px; line-height: 42px; letter-spacing: -0.03em; }
  .ds-t-figure { font-family: 'Fustat Variable', Fustat, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-weight: 700; font-size: 41px; line-height: 42px; letter-spacing: -0.01em; }
  .ds-t-meta { font-family: 'Fustat Variable', Fustat, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-weight: 500; font-size: 14px; line-height: 22px; }
  .ds-t-card-title { font-family: 'Fustat Variable', Fustat, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-weight: 600; font-size: 19px; line-height: 23px; letter-spacing: -0.02em; }
  .ds-t-product-display { font-family: 'Fustat Variable', Fustat, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-weight: 700; font-size: 62px; line-height: 64px; letter-spacing: -0.02em; max-width: 22ch; }
  .ds-t-product-h2 { font-family: 'Fustat Variable', Fustat, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-weight: 700; font-size: 38px; line-height: 42px; letter-spacing: -0.02em; max-width: 33ch; }
}

/* The product set's faces, for the primitives both sets share. */
:where(.ds-product) .ds-t-display { font-family: 'Fustat Variable', Fustat, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-weight: 700; font-size: 38px; line-height: 42px; letter-spacing: -0.02em; }
:where(.ds-product) .ds-t-h1-sm { font-family: 'Fustat Variable', Fustat, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-weight: 600; font-size: 30px; line-height: normal; letter-spacing: -0.045em; }
:where(.ds-product) .ds-t-h2 { font-family: 'Fustat Variable', Fustat, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-weight: 600; font-size: 30px; line-height: normal; letter-spacing: -0.045em; }
:where(.ds-product) .ds-t-h3 { font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-weight: 600; font-size: 20px; line-height: normal; letter-spacing: -0.02em; }
:where(.ds-product) .ds-t-h4 { font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-weight: 600; font-size: 20px; line-height: normal; letter-spacing: -0.02em; }
:where(.ds-product) .ds-t-card-title { font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-weight: 600; font-size: 20px; line-height: normal; letter-spacing: -0.02em; }
:where(.ds-product) .ds-t-lede { font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-weight: 400; font-size: 17px; line-height: 26px; }
:where(.ds-product) .ds-t-body { font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-weight: 400; font-size: 14px; line-height: 22px; }
:where(.ds-product) .ds-t-body-bold { font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-weight: 600; font-size: 20px; line-height: normal; letter-spacing: -0.02em; }
:where(.ds-product) .ds-t-nav { font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-weight: 500; font-size: 14px; line-height: 16px; }
:where(.ds-product) .ds-t-cta { font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-weight: 500; font-size: 12px; line-height: 13px; letter-spacing: 0.045em; text-transform: uppercase; }
:where(.ds-product) .ds-t-eyebrow { font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace; font-weight: 500; font-size: 12px; line-height: normal; letter-spacing: 0.08em; text-transform: uppercase; }
:where(.ds-product) .ds-t-chip { font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-weight: 500; font-size: 12px; line-height: normal; letter-spacing: 0.05em; text-transform: uppercase; }
:where(.ds-product) .ds-t-caption { font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-weight: 400; font-size: 13px; line-height: normal; }
:where(.ds-product) .ds-t-footer { font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-weight: 400; font-size: 14px; line-height: 22px; }
:where(.ds-product) .ds-t-field-label { font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-weight: 500; font-size: 14px; line-height: 16px; }

@media (min-width: 768px) {
  :where(.ds-product) .ds-t-display { font-family: 'Fustat Variable', Fustat, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-weight: 700; font-size: 62px; line-height: 64px; letter-spacing: -0.02em; max-width: 22ch; }
  :where(.ds-product) .ds-t-h1-sm { font-family: 'Fustat Variable', Fustat, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-weight: 700; font-size: 38px; line-height: 42px; letter-spacing: -0.02em; max-width: 33ch; }
  :where(.ds-product) .ds-t-h2 { font-family: 'Fustat Variable', Fustat, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-weight: 700; font-size: 38px; line-height: 42px; letter-spacing: -0.02em; max-width: 33ch; }
}

/*
 * The components' own CSS. Hand-written, because how a button behaves on hover, how a grid re-flows
 * at 900px and how a field looks when it is invalid are web decisions the canvas does not make.
 *
 * Every colour is a role token, every size is either a token or a decision recorded here. Square
 * corners throughout on the corporate set; the 3px chip is its one exception. The product set
 * rounds as the live product sites do, through the radius/product-* tokens — chips at
 * radius/product-sm. Nowhere is there a pill. A drop shadow is only ever
 * `var(--ds-shadow-card)` or `var(--ds-shadow-raised)`: the corporate theme draws nothing for
 * either, the product theme its two recipes, and scripts/check-library-css.mjs refuses a blur
 * written here by hand.
 *
 * Defaults come from the theme; a site overrides them by redefining a token, by passing `className`
 * or `style`, or by rendering a different element with `as`. Every selector here is written at zero
 * or near-zero specificity so a site's own rule wins without `!important`.
 */

/* The library owns the box model of its own elements and nothing else on the page. */
/* Text for a screen reader only. Not display:none, which removes it from the reader too, and not
   width/height 0, which some readers skip: the clip-path technique keeps it in the accessibility
   tree while taking no space. */
/* A picture placed directly in one of the library's flex columns keeps its own proportions. A flex
   child stretches to the column by default, which sets an image's width without touching its height
   and quietly distorts every lockup. */
:where([class^='ds-'], [class*=' ds-']) > img:not([width]) { align-self: start; }

.ds-visually-hidden:not(:focus):not(:active) {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0;
}

:where([class^='ds-'], [class*=' ds-']),
:where([class^='ds-'], [class*=' ds-'])::before,
:where([class^='ds-'], [class*=' ds-'])::after {
  box-sizing: border-box;
}

/* ── Section ───────────────────────────────────────────────────────────────────────────────── */

.ds-section {
  position: relative;
  padding-block: var(--ds-section-y);
  /* An anchored section lands under the pinned bar, not behind it. */
  scroll-margin-block-start: var(--ds-nav-pinned, 0px);
  background: var(--ds-surface);
  color: var(--ds-ink);
}
/* Each band names the field it paints in --ds-surface-field as well as painting it, because the
   state pair further down is mixed over that field rather than over the page's. */
.ds-section--paper { background: var(--ds-surface-paper); --ds-surface-field: var(--ds-surface-paper); --ds-surface-invert-panel: var(--ds-surface-invert-panel-on-paper); color: var(--ds-ink); }
.ds-section--raised { background: var(--ds-surface-raised); --ds-surface-field: var(--ds-surface-raised); color: var(--ds-ink); }
.ds-section--invert { background: var(--ds-surface-invert); --ds-surface-field: var(--ds-surface-invert); color: var(--ds-ink-on-invert); }
/* People card row's Field=Black Theme=Dark master paints dark/raised, the inverted panel's rung. */
.ds-section--invert.ds-people { background: var(--ds-surface-invert-panel); --ds-surface-field: var(--ds-surface-invert-panel); color: var(--ds-ink-on-invert); }
.ds-section--brand { background: var(--ds-surface-brand); --ds-surface-field: var(--ds-surface-brand); color: var(--ds-ink-on-brand); }
/* The hero carries both classes, so this only moves the field: blue in the light like any brand
   band, the floor of the ladder in the dark where a band takes the panel. */
.ds-section--hero { background: var(--ds-surface-hero); --ds-surface-field: var(--ds-surface-hero); color: var(--ds-ink-on-brand); }
.ds-section--accent { background: var(--ds-surface-accent); --ds-surface-field: var(--ds-surface-accent); color: var(--ds-ink-on-accent); }
/* A band with nothing in it is not a band. A block that renders nothing in its empty state — the
   contributor grid when the fetch comes back with no one — takes the section's field and rhythm with
   it, rather than leaving a coloured gap where a heading used to be. */
.ds-section:empty, .ds-section:has(> .ds-container:empty) { display: none; }
/*
 * And a block that paints a light ground inside one of those puts the ladder back — a callout box on
 * the programme band, a white card on a black section. It restores from the theme's saved copy
 * rather than writing black, because "light" is #FFFFFF in one theme and #1A1A1A in the other.
 */
.ds-section,
.ds-section--paper,
.ds-section--raised,
.ds-section--accent,
.ds-psection--paper,
.ds-psection--lilac,
.ds-psection--mist,
.ds-psection--sand,
.ds-notfound--product,
.ds-callout,
.ds-card,
.ds-case,
.ds-pathway__item--here,
.ds-media,
.ds-nav,
.ds-nav--product.ds-nav--open,
.ds-nav--product .ds-nav__drawer,
.ds-input,
.ds-choice__card,
.ds-form__alert,
.ds-select__box,
.ds-values__cell,
.ds-facts__item,
.ds-case__status,
.ds-check__box,
.ds-article__card,
.ds-article__cards,
.ds-article__capability,
.ds-contributors__item,
.ds-phero__cutout,
.ds-dash,
.ds-shop,
.ds-trade-card,
.ds-int-card,
.ds-addon-card,
.ds-addon-card__media,
.ds-eco__card,
.ds-tour-card__media,
.ds-getstarted__form,
.ds-callout--accent,
.ds-callout--section,
.ds-card--paper,
.ds-contributors__avatar,
.ds-cutout__frame,
.ds-pform,
.ds-select__option,
.ds-settings-nav__item,
.ds-showcase__item,
.ds-showcase__shot,
.ds-signin__panel,
.ds-doc,
.ds-doc__body .ds-prose th,
.ds-doc-header,
.ds-doc-legal,
.ds-switch__thumb,
.ds-tabs__tab,
.ds-consent,
.ds-btt,
.ds-chip--accent-soft,
.ds-walk__worth-item,
.ds-app-card {
  --ds-ink: var(--ds-ink-base);
  --ds-ink-inverse: var(--ds-ink-inverse-base);
  --ds-ink-body: var(--ds-ink-body-base);
  --ds-ink-muted: var(--ds-ink-muted-base);
  --ds-ink-placeholder: var(--ds-ink-placeholder-base);
  --ds-rule: var(--ds-rule-base);
  --ds-rule-strong: var(--ds-rule-strong-base);
  --ds-rule-outline: var(--ds-rule-outline-base);
  --ds-accent-ink: var(--ds-accent-ink-base);
  --ds-focus: var(--ds-focus-base);
  /* The card link a light ground holds keeps the ground's accent. The blue band whitens the card
     link (further down), and without this the white reached into a white card on the band: 1:1. */
  --ds-card-link-ink: var(--ds-accent-ink-base);
  /* And the state pair with them: a block on its own light field hovers on that field, not on the
     band's, and the tint it mixes is the ink that has just been restored. */
  --ds-surface-tint: var(--ds-ink);
  --ds-surface-hover: var(--ds-surface-hover-base);
  --ds-surface-active: var(--ds-surface-active-base);
  color-scheme: var(--ds-color-scheme-base);
}
/*
 * The kit's roots, the members of the list above that paint the app's own surface, take the app's
 * ink ladder rather than the page's: in the dark the kit masters bind neutral/ink-body and ink-muted
 * (white at 0.76 and 0.62) where a product section draws 0.8 and 0.6. After the list, so it wins.
 */
.ds-phero__cutout,
.ds-dash,
.ds-shop,
.ds-cutout__frame,
.ds-app-card {
  --ds-ink-body: var(--ds-app-ink-body, var(--ds-ink-body-base));
  --ds-ink-muted: var(--ds-app-ink-muted, var(--ds-ink-muted-base));
  --ds-ink-placeholder: var(--ds-app-ink-muted, var(--ds-ink-placeholder-base));
}
/*
 * A field that is dark whatever the theme re-points the whole ink ladder for its subtree, not just
 * its own `color`. Setting `color` alone left every token underneath it — the body ink, the muted
 * ink, the hairline, the accent ink — pointing at the light page's values, so an intro inside a blue
 * band was black on blue and a card link on a black card was brand blue at 3.3:1. Anything that
 * paints its own dark ground belongs in this list.
 */
.ds-section--brand,
.ds-section--hero,
.ds-section--invert,
.ds-media-card,
.ds-footer,
.ds-pfooter,
.ds-psection--deep,
.ds-nav--clear,
.ds-nav--veil,
.ds-shop__band,
.ds-app-deep,
.ds-ace__console {
  color-scheme: dark;
  --ds-ink: var(--brand-white);
  --ds-ink-inverse: var(--brand-black);
  --ds-ink-body: rgb(255 255 255 / 0.9);
  /* 0.78, not 0.7: the tightest ground this ladder meets is the blue field, where 70% white
     measures 4.05:1 against a 15px line and 78% clears it. */
  --ds-ink-muted: rgb(255 255 255 / 0.78);
  --ds-ink-placeholder: var(--ds-ink-muted);
  --ds-rule: rgb(255 255 255 / 0.22);
  --ds-rule-strong: rgb(255 255 255 / 0.45);
  --ds-rule-outline: rgb(255 255 255 / 0.2);
  /* The accent follows the ground for the same reason the ring does: a product's brand measures
     2.7:1 on its own deep field, so on a deep band the accented ink is the lift — which is what the
     dark theme already does for every product surface. The corporate set has no lift and falls
     through to the accent, which is the lime its dark bands are lettered in. */
  --ds-accent-ink: var(--ds-lift, var(--ds-accent));
  /* The ring follows the ground: lime on blue and on black, as the live sites have it
     (`.bg-brand-blue :focus-visible { outline-color: lime }`); a product's deep band rings in its
     lift, the corporate set has no lift and falls through to the accent. */
  --ds-focus: var(--ds-lift, var(--ds-accent));
  /* A deep ground names no card-link ink, so a link on it takes the blue band's white. */
  --ds-card-link-ink: initial;
  /* The tint the state pair mixes is the ink that reads here: white, so a tile hovering on the blue
     band lightens rather than dirtying, which is what the canvas's Field=Blue State=Hover paints. */
  --ds-surface-tint: var(--ds-ink);
}
/*
 * The product deep grounds keep the list's ladder but for body and muted ink, which follow their own
 * masters rather than the corporate blue field's: a product deep band or footer draws body at 80%
 * white and its quieter lines at 70%; a kit deep panel binds the kit variables in their Dark mode.
 * After the list, so they win it.
 */
.ds-pfooter,
.ds-psection--deep {
  --ds-ink-body: rgb(255 255 255 / 0.8);
  --ds-ink-muted: rgb(255 255 255 / 0.7);
  --ds-ink-placeholder: rgb(255 255 255 / 0.7);
}
.ds-shop__band,
.ds-app-deep {
  --ds-ink-body: var(--neutral-ink-body-dark);
  --ds-ink-muted: var(--neutral-ink-muted-dark);
  --ds-ink-placeholder: var(--neutral-ink-muted-dark);
}
/*
 * A CONTROL that paints its own light field is not a ground. Its ring sits outside it, on whatever
 * the control was placed on, so it keeps the ring it inherits instead of restoring the light one:
 * an input on the blue band rings in lime, the white card beside it rings in blue and a button
 * inside that card rings in blue too. Every control in the restoring list above is named here.
 */
.ds-input,
.ds-choice__card,
.ds-check__box,
.ds-switch__track,
.ds-tabs__tab,
.ds-select__option,
.ds-settings-nav__item { --ds-focus: inherit; }
/*
 * The state pair for a band, and for anything that paints a field of its own: the field it names in
 * `--ds-surface-field` with `--ds-surface-mix` of `--ds-surface-tint` mixed in, opaque. It comes
 * AFTER both ladder lists on purpose — a band is in one of them, and the theme's pair it restores
 * there is the pair for the PAGE's field, not the band's. On a product page the pair is the canvas's
 * own `cutout/hover` (theme-product.css) and a band does not mix its own; the list below is the
 * corporate one, and the product bands take the brand's hover field whichever band they are.
 */
.ds-section--paper,
.ds-section--raised,
.ds-section--invert,
.ds-section--brand,
.ds-section--hero,
.ds-section--accent,
.ds-card--paper {
  --ds-surface-hover: color-mix(in srgb, var(--ds-surface-tint) var(--ds-surface-mix), var(--ds-surface-field));
  --ds-surface-active: color-mix(in srgb, var(--ds-surface-tint) var(--ds-surface-mix-strong), var(--ds-surface-field));
}
/* Inside a product page there is no mix to make: the canvas gives the hover field as a hex per brand
   and per theme (`cutout/hover`), so a band there takes it back whatever field the band paints. */
.ds-product :is(.ds-section--paper, .ds-section--raised, .ds-section--invert, .ds-section--brand, .ds-section--hero, .ds-section--accent, .ds-card--paper) {
  --ds-surface-hover: var(--ds-surface-hover-base);
  --ds-surface-active: var(--ds-surface-active-base);
}
/*
 * The one ring on every focusable, not only on the controls with a rule of their own. The sites ring
 * globally (`:focus-visible { outline: 3px solid var(--ring) }`), so a focusable the library had not
 * named — a media card's play control, the tabs panel Radix gives tabindex="0" — fell to the
 * browser's 1px ring. Zero specificity, so a site's own rule still wins.
 */
:where(:root, .ds-root) :focus-visible { outline: var(--ds-focus-width) solid var(--ds-focus); outline-offset: var(--ds-focus-offset); }
/*
 * A cutout is the app, not the page. Inside one, the surface roles point at the cutout palette the
 * product theme carries — the canvas's cutout/card, cutout/sunken, cutout/hover and cutout/border,
 * with their Storefront and Dark twins — so a fragment of interface built from the shared primitives
 * paints itself in the app's own colours whatever band the cutout was set down on. The ink is the
 * theme's, restored by the list above.
 */
.ds-phero__cutout,
.ds-cutout__frame,
.ds-showcase__shot {
  --ds-surface: var(--ds-cutout-surface, var(--cutout-card));
  --ds-surface-raised: var(--ds-cutout-surface, var(--cutout-card));
  --ds-surface-paper: var(--ds-cutout-sunken, var(--cutout-sunken));
  --ds-surface-hover: var(--ds-cutout-hover, var(--cutout-hover));
  --ds-rule: var(--ds-cutout-rule, var(--cutout-border));
  --ds-rule-strong: var(--ds-cutout-rule, var(--cutout-border));
}
/* A band with no field of its own sits on whatever it is placed on. */
.ds-section--plain { background: transparent; }
.ds-section--tight { --ds-band-top: calc(var(--ds-section-y) / 2); padding-block: var(--ds-band-top); }

.ds-container {
  width: 100%;
  max-width: var(--ds-page-max);
  margin-inline: auto;
  padding-inline: var(--ds-gutter);
}
/* The gutter is generous on a wide screen and modest on a phone; between the two it is the space
   that gives, not the content. */
@media (min-width: 768px) and (max-width: 1199.98px) {
  .ds-container { padding-inline: clamp(32px, 8vw, 130px); }
}

/* ── Stack and grid ────────────────────────────────────────────────────────────────────────── */

.ds-stack { display: flex; flex-direction: column; gap: var(--ds-gap, var(--space-xs)); }
.ds-row { display: flex; flex-wrap: wrap; align-items: center; gap: var(--ds-gap, var(--space-xs)); }

/* Twice as many tracks as columns and every card spanning two, so a last row that does not fill can
   start half a column in and sit centred: five cards on three columns read as 3 + 2, not 3 + 2 and a
   hole. The grid sets its track counts and that start line from the number of cards it holds; the
   selectors below only find the card that begins an incomplete last row. */
.ds-grid {
  display: grid;
  gap: var(--ds-card-gap);
  grid-template-columns: repeat(var(--ds-grid-tracks-sm, 2), minmax(0, 1fr));
}
.ds-grid > * { grid-column: auto / span 2; }
@media (min-width: 600px) {
  .ds-grid { grid-template-columns: repeat(var(--ds-grid-tracks-md, 4), minmax(0, 1fr)); }
}
/* Two across: an odd count leaves one card, and it sits in the middle. */
@media (min-width: 600px) and (max-width: 999.98px) {
  .ds-grid:not([data-cols='1']) > :where(:nth-child(2n + 1):last-child) { grid-column-start: 2; }
}
@media (min-width: 1000px) {
  .ds-grid { grid-template-columns: repeat(var(--ds-grid-tracks, 6), minmax(0, 1fr)); }
  /* The first card of a row, and within the last (columns − 1) cards: the one that opens a last row
     with room left in it. It starts on the line the grid's own arithmetic set. */
  .ds-grid[data-cols='2'] > :where(:nth-child(2n + 1):nth-last-child(-n + 1)),
  .ds-grid[data-cols='3'] > :where(:nth-child(3n + 1):nth-last-child(-n + 2)),
  .ds-grid[data-cols='4'] > :where(:nth-child(4n + 1):nth-last-child(-n + 3)),
  .ds-grid[data-cols='5'] > :where(:nth-child(5n + 1):nth-last-child(-n + 4)),
  .ds-grid[data-cols='6'] > :where(:nth-child(6n + 1):nth-last-child(-n + 5)) { grid-column-start: var(--ds-grid-start, auto); }
}
/* A ruled grid (`fill`) never frames an empty cell: its hairlines are the ground showing through the
   gaps, so a short last row is closed by its last card spanning the tracks the row leaves. The cards
   keep reading order rather than centring. Below 600px every card is a row of its own. */
.ds-grid[data-fill] > * { grid-column-start: auto; }
@media (min-width: 600px) and (max-width: 999.98px) {
  .ds-grid[data-fill]:not([data-cols='1']) > :nth-child(odd):last-child { grid-column-end: span 4; --ds-fill-span: 2; }
}
@media (min-width: 1000px) {
  .ds-grid[data-fill][data-cols='2'] > :nth-child(2n + 1):last-child,
  .ds-grid[data-fill][data-cols='3'] > :nth-child(3n + 2):last-child,
  .ds-grid[data-fill][data-cols='4'] > :nth-child(4n + 3):last-child,
  .ds-grid[data-fill][data-cols='5'] > :nth-child(5n + 4):last-child,
  .ds-grid[data-fill][data-cols='6'] > :nth-child(6n + 5):last-child { grid-column-end: span 4; --ds-fill-span: 2; }
  .ds-grid[data-fill][data-cols='3'] > :nth-child(3n + 1):last-child,
  .ds-grid[data-fill][data-cols='4'] > :nth-child(4n + 2):last-child,
  .ds-grid[data-fill][data-cols='5'] > :nth-child(5n + 3):last-child,
  .ds-grid[data-fill][data-cols='6'] > :nth-child(6n + 4):last-child { grid-column-end: span 6; --ds-fill-span: 3; }
  .ds-grid[data-fill][data-cols='4'] > :nth-child(4n + 1):last-child,
  .ds-grid[data-fill][data-cols='5'] > :nth-child(5n + 2):last-child,
  .ds-grid[data-fill][data-cols='6'] > :nth-child(6n + 3):last-child { grid-column-end: span 8; --ds-fill-span: 4; }
  .ds-grid[data-fill][data-cols='5'] > :nth-child(5n + 1):last-child,
  .ds-grid[data-fill][data-cols='6'] > :nth-child(6n + 2):last-child { grid-column-end: span 10; --ds-fill-span: 5; }
  .ds-grid[data-fill][data-cols='6'] > :nth-child(6n + 1):last-child { grid-column-end: span 12; --ds-fill-span: 6; }
}

/* ── Section heading ───────────────────────────────────────────────────────────────────────── */

/* No trailing margin. The block that holds a heading is a column with the heading gap on it, and a
   margin here as well made every one of those bands open with 144px of nothing — the gap twice. */
.ds-section-heading {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
}
/* The two blocks whose heading and grid are siblings inside the section's own measure rather than
   inside a block of their own. */
.ds-card-grid,
.ds-people,
.ds-card-grid > .ds-container,
.ds-people > .ds-container { display: flex; flex-direction: column; gap: var(--ds-heading-gap); }
/* Cards in a grid share their rows, so a subtitle that wraps in one card moves every card's body with
   it and the row stays level — the same treatment as the tour and product cards. The card keeps its
   own 12 between slots while the grid keeps the card gap between rows, and it spans as many rows as
   the fullest card in the grid has slots: a fixed six left every three-slot card with three empty
   tracks, and their gutters were 36px of dead space under the body. */
.ds-card-grid .ds-card { display: grid; grid-template-rows: subgrid; grid-row: span var(--ds-card-rows, 6); gap: 12px; align-content: start; }
/* A reveal standing in a grid for the card it wraps hands the cell straight through: a one-cell grid,
   so the card stretches to the row as it did when it was the grid's own child, and in the card grid a
   subgrid of the shared rows, so the card's slots still line up across the row. */
.ds-grid > .ds-reveal, .ds-media-cards__grid > .ds-reveal { display: grid; }
.ds-card-grid .ds-grid > .ds-reveal { grid-template-rows: subgrid; grid-row: span var(--ds-card-rows, 6); }
.ds-card-grid .ds-card__stat,
.ds-card-grid .ds-card__link { margin-block-start: 0; align-self: end; }
/* Every line in this block is a paragraph or a heading, so the browser's own 1em above and below each
   one is the browser's, not the block's gap. The title and the intro each spent it on their own; the
   eyebrow never did, and at 11px that is 22px the block stood taller than its master wherever it had
   one — the whole of the MediaCards overshoot at 390. Stated once for every slot, so the next slot
   added here cannot be missed the way the eyebrow was. */
.ds-section-heading__eyebrow, .ds-section-heading__title, .ds-section-heading__intro { margin: 0; }
.ds-section-heading__title { max-width: 20ch; }
/* Full ink, both of them. A card's eyebrow is set at 60% and its body at 70-80%, but the block that
   opens a section is the section's own voice on every master that has one, and muting it was the
   difference between a heading that leads and one that apologises. */
.ds-section-heading__intro { max-width: 62ch; }
.ds-section-heading__eyebrow { color: inherit; }
/* Theme=Dark letters the eyebrow in the ground's accent: on an inverted field, and on every field of a dark page. */
.ds-section--brand .ds-section-heading__eyebrow, .ds-section--invert .ds-section-heading__eyebrow, :where(.dark, [data-theme='dark']) .ds-section-heading__eyebrow { color: var(--ds-accent-ink); }

/* Wide enough for two columns, the heading and its intro sit side by side the way the canvas has
   them; narrower, they stack. Every heading block, whichever layout it names: the master's
   Layout=Standalone is the Layout=Inline block inside a band of its own, the same 640 / 36 / fill
   row, and only its Size=Mobile stacks. */
@media (min-width: 1000px) {
  /* The master's own measure: the title column is 640 wide, the gap 36, and the intro takes the rest
     (Section heading · inner, Desktop: 640 + 36 + 504 = 1180). Two equal columns gave the
     title 590 and broke it a word early. On this column the greedy break is the master's, line for
     line; what the reader sees is the estate's `text-wrap: balance` (type.css) evening those lines,
     which is a rule of its own with its own gate. */
  .ds-section-heading {
    display: grid;
    grid-template-columns: minmax(0, 640px) minmax(0, 1fr);
    align-items: end;
    gap: 36px;
  }
  /* The title takes the whole 640 track. The 20ch cap is the stacked block's measure and it broke
     the master's line a word early inside a column the canvas has already sized. */
  .ds-section-heading__title { max-width: none; }
  .ds-section-heading__intro { justify-self: end; }
}

/* ── Button ────────────────────────────────────────────────────────────────────────────────── */

.ds-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-height: 48px;
  padding-inline: 24px;
  border: 0;
  border-radius: var(--ds-radius-control);
  background: none;
  cursor: pointer;
  text-decoration: none;
  transition: background-color 150ms ease, color 150ms ease, box-shadow 150ms ease;
}
.ds-button:focus-visible { outline: var(--ds-focus-width) solid var(--ds-focus); outline-offset: var(--ds-focus-offset); }
/* Disabled, whether a real button or a link told to stand down: dimmed and inert to the pointer. */
.ds-button:disabled, .ds-button[aria-disabled='true'] { opacity: 0.45; cursor: default; }
/* Busy is not disabled, even though it declines a second press: the label has changed to say what
   is happening, and a reader has to be able to read it. `Element=Submit, State=Submitting` dims the
   whole control to 70%, not to the 45% of a control that is unavailable. After the disabled rule
   because a busy button is also disabled and these two carry the same weight. */
.ds-button[aria-busy='true'] { opacity: 0.7; cursor: progress; }
/* Lime is the primary in both themes; it is the one place the accent is spent. */
.ds-button--primary { background: var(--ds-accent); color: var(--ds-ink-on-accent-control); }
.ds-button--primary:hover { background: color-mix(in srgb, var(--ds-accent) 88%, black); }
.ds-button--secondary { box-shadow: inset 0 0 0 1px currentColor; color: inherit; }
.ds-button--secondary:hover { background: color-mix(in srgb, currentColor 8%, transparent); }
/* The master's third style, Style=Black: the ink as the fill and the ink's inverse as the label, so it
   is black on a light field and white on the dark ladder and on every dark band. Hover and pressed are
   the canvas's state/black-* and state/white-* rungs: the ink at 80% and 70% over the ground. */
.ds-button--ink { background: var(--ds-ink); color: var(--ds-ink-inverse); }
.ds-button--ink:hover { background: color-mix(in srgb, var(--ds-ink) 80%, var(--ds-surface-field, var(--ds-surface))); }
.ds-button--ink:active { background: color-mix(in srgb, var(--ds-ink) 70%, var(--ds-surface-field, var(--ds-surface))); }
.ds-button--quiet { padding-inline: 0; min-height: auto; color: inherit; }
.ds-button--quiet:hover { color: var(--ds-ink); }
.ds-button--block { width: 100%; }
@media (max-width: 767.98px) {
  /* On a phone a call to action is full width, which is the rule the estate settled on. */
  .ds-button--stack-block { width: 100%; }
}

/* ── Link ──────────────────────────────────────────────────────────────────────────────────── */

.ds-link {
  color: inherit;
  text-decoration: none;
  transition: opacity 150ms ease;
}
.ds-link:hover { opacity: 0.7; }
.ds-link:focus-visible { outline: var(--ds-focus-width) solid var(--ds-focus); outline-offset: var(--ds-focus-offset); }
/* The space is a margin, not a space in the content: motion.css makes this box inline-block so the
   arrow can move on hover, and an inline-block collapses the leading white space inside it. The
   glyph is declared twice on purpose: `content: <string> / <alt>` keeps the arrow out of the link's
   accessible name — read aloud it is only "right arrow" after every link — but WebKit understands
   that syntax only from Safari 17.4, and an engine that cannot parse a declaration drops it whole.
   The plain declaration stands where the pair is not understood; the alt-text form wins where it is.
   Both, in this order, or iOS 16 loses every arrow in the estate. */
.ds-link--arrow::after { content: '\2192'; content: '\2192' / ''; margin-inline-start: 0.35em; white-space: nowrap; }
/* The way onward from a card is set in the accent ink on every master that has one — brand blue on
   a light ground, lime on a dark card, the product's own accent on a product one. It is the card
   link specifically: a pathway link or an escape hatch is a `cta` and stays the ink it inherits,
   which is why the colour hangs off the type class the canvas gives each of them. The blue field is
   the exception the canvas makes — there the link is white, because lime on blue measures 3.7:1.
   Only where the link stands on the blue or on a tint of it: a light ground inside the band names
   its own card-link ink in the restoring list, so its link keeps the ground's accent. */
.ds-t-footer.ds-link--arrow, .ds-t-product-cta.ds-link--arrow { color: var(--ds-accent-ink, currentColor); }
.ds-section--brand .ds-t-footer.ds-link--arrow { color: var(--ds-card-link-ink, var(--ds-ink-on-brand)); }

/* ── Theme toggle ──────────────────────────────────────────────────────────────────────────── */

/* Every site draws it as a 20px moon on the light page and a 20px sun on the dark one, in the bar's
   muted ink until the pointer reaches it. The mark is 20px in the layout; the padding and the
   matching negative margin make the hit target 40px without moving anything beside it. */
.ds-theme-toggle {
  display: inline-flex;
  flex: none;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  margin: -10px;
  padding: 10px;
  border: 0;
  background: none;
  color: var(--ds-ink-muted);
  cursor: pointer;
  transition: color var(--ds-fast, 140ms) var(--ds-ease, ease);
}
.ds-theme-toggle__mark { display: inline-flex; flex: none; width: 20px; height: 20px; }
/* The bare ring the canvas still draws in the toggle's place, for a site that opts into it. */
.ds-theme-toggle__ring { border: 1.5px solid currentColor; border-radius: 50%; }
.ds-theme-toggle:focus-visible { outline: var(--ds-focus-width) solid var(--ds-focus); outline-offset: var(--ds-focus-offset); }
@media (hover: hover) { .ds-theme-toggle:hover { color: var(--ds-ink); } }

/* ── Chip and eyebrow ──────────────────────────────────────────────────────────────────────── */

.ds-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 22px;
  padding: 5px 8px;
  border-radius: var(--ds-radius-chip);
  background: color-mix(in srgb, currentColor 10%, transparent);
  color: inherit;
}
/* The tones on the canvas: the tint; the 24% hairline a chip row uses; the solid inverse that labels
   a section; and the accent — filled for a selected card, as ink in its own hairline, as ink on its
   own tint. */
.ds-chip--outline { background: none; box-shadow: inset 0 0 0 1px color-mix(in srgb, currentColor 24%, transparent); }
.ds-chip--invert { background: var(--ds-surface-invert); color: var(--ds-ink-on-invert); }
.ds-chip--accent { background: var(--ds-accent); color: var(--ds-ink-on-accent-control); }
.ds-chip--accent-outline { background: none; color: var(--ds-accent-ink, var(--ds-accent)); box-shadow: inset 0 0 0 1px currentColor; }
.ds-chip--accent-soft { background: var(--ds-surface-accent); color: var(--ds-accent-ink, var(--ds-ink-on-accent)); }
/* The states of the planned product interface — stock in, running low, out — as the live product
   sites draw them: the state as ink on its own field. One vocabulary: the same `--ds-ok`/`--ds-warn`/
   `--ds-bad` pairs the kit chip and the app kit read, re-pointed by the product theme in the dark, so
   a chip and a cutout beside it can never disagree. Outside a product page the tokens are undeclared
   and the canvas's own light values stand in, because the corporate set has no design for a state. */
.ds-chip--quiet { background: color-mix(in srgb, currentColor 8%, transparent); color: var(--ds-ink-muted); }
.ds-chip--ok { background: var(--ds-ok-field, var(--state-ok-field)); color: var(--ds-ok, var(--state-ok)); }
.ds-chip--warn { background: var(--ds-warn-field, var(--state-warn-field)); color: var(--ds-warn, var(--state-warn)); }
.ds-chip--bad { background: var(--ds-bad-field, var(--state-bad-field)); color: var(--ds-bad, var(--state-bad)); }
/* The icon before a chip's label: 14px on the canvas, in the chip's own ink unless a row says otherwise. */
.ds-chip__icon { display: inline-flex; flex: none; width: 14px; height: 14px; }
.ds-chip__icon > svg { width: 100%; height: 100%; display: block; }

/* ── Card ──────────────────────────────────────────────────────────────────────────────────── */

.ds-card {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: var(--space-sm);
  background: var(--ds-surface);
  color: var(--ds-ink);
  border-radius: var(--ds-radius-card);
  box-shadow: inset 0 0 0 1px var(--ds-rule);
}
/* Feature card grid Size=Mobile: 24 in from the edge and 16 between cards; 32 and 24 from 768. */
@media (min-width: 768px) { .ds-card { padding: var(--space-md); } }
.ds-card-grid { --ds-card-gap: var(--space-xs); }
@media (min-width: 768px) { .ds-card-grid { --ds-card-gap: var(--space-sm); } }
/* A paper card hovers on its own field; an outline card paints none, so it keeps the band's pair
   instead of the one `.ds-card` restores from the theme, and the band's whole ladder with it, as the
   outline callout does: restored, it set black ink on the blue band and on the black one. */
.ds-card--paper { background: var(--ds-surface-paper); --ds-surface-field: var(--ds-surface-paper); color: var(--ds-ink); }
.ds-card--outline { background: transparent; box-shadow: inset 0 0 0 1px var(--ds-rule); --ds-surface-hover: inherit; --ds-surface-active: inherit; --ds-ink: inherit; --ds-ink-inverse: inherit; --ds-ink-body: inherit; --ds-ink-muted: inherit; --ds-ink-placeholder: inherit; --ds-rule: inherit; --ds-rule-strong: inherit; --ds-rule-outline: inherit; --ds-accent-ink: inherit; --ds-focus: inherit; --ds-surface-tint: inherit; --ds-card-link-ink: inherit; }
/* On the blue band a card is a tint of the band, not a light ground: `Feature card grid · card`
   Field=Blue paints it white at 10% over the blue under a white hairline at 12%, its copy white at
   95% and its link white, and the hover adds the state pair's 3%. So it keeps the band's ladder
   rather than restoring the page's. The light theme's alone: in the dark the band is the panel and
   the master sets the card on a ladder rung with the dark ink and the lime link, which is the card
   `.ds-card` restores. Zero specificity past `.ds-card`, so the link card's hover still paints. */
:where(.ds-section--brand) .ds-card:where(:not(.ds-card--paper, .ds-card--outline)):where(:not(:where(.dark, [data-theme='dark']) *)) {
  --ds-ink: var(--ds-ink-on-brand);
  --ds-ink-inverse: inherit;
  --ds-ink-body: rgb(255 255 255 / 0.95);
  --ds-ink-muted: rgb(255 255 255 / 0.95);
  --ds-ink-placeholder: inherit;
  --ds-rule: rgb(255 255 255 / 0.12);
  --ds-rule-strong: inherit;
  --ds-rule-outline: inherit;
  --ds-accent-ink: inherit;
  --ds-focus: inherit;
  --ds-card-link-ink: initial;
  --ds-surface-field: rgb(255 255 255 / 0.1);
  --ds-surface-hover: color-mix(in srgb, var(--ds-surface-tint) var(--ds-surface-mix), var(--ds-surface-field));
  --ds-surface-active: color-mix(in srgb, var(--ds-surface-tint) var(--ds-surface-mix-strong), var(--ds-surface-field));
  background: var(--ds-surface-field);
  color: var(--ds-ink);
}
/* Every slot is a heading or a paragraph; the 12 between them is the card's gap, never a browser margin. */
.ds-card__eyebrow, .ds-card__title, .ds-card__subtitle { margin: 0; }
.ds-card__body { margin: 0; color: var(--ds-ink-body); }
.ds-card__eyebrow, .ds-card__subtitle { color: var(--ds-ink-muted); }
.ds-card__link { margin-block-start: auto; padding-block-start: 12px; }
/* The stat takes the auto margin and the link after it takes none: two auto margins split the free
   space and float the stat mid-card. */
.ds-card__stat { margin: 0; margin-block-start: auto; padding-block-start: 12px; }
.ds-card__stat ~ .ds-card__link { margin-block-start: 0; }
/* Under the pointer a link card takes its hover field and the strong rule — the lift is motion.css's
   — and pressed, the active field. The hover painter sits behind `hover: hover` so a tap on a phone
   does not leave the tint stuck on the card until the next tap elsewhere; `:focus-within` is not
   gated, because it is how the keyboard sees the same state. */
@media (hover: hover) {
  .ds-card--link:hover { background: var(--ds-surface-hover); box-shadow: inset 0 0 0 1px var(--ds-rule-strong); }
}
.ds-card--link:focus-within { background: var(--ds-surface-hover); box-shadow: inset 0 0 0 1px var(--ds-rule-strong); }
.ds-card--link:active { background: var(--ds-surface-active); }

/* ── Divider ───────────────────────────────────────────────────────────────────────────────── */

.ds-divider { border: 0; border-block-start: 1px solid var(--ds-rule); margin: 0; }

/* ── Media ─────────────────────────────────────────────────────────────────────────────────── */

.ds-media {
  display: block;
  position: relative;
  overflow: hidden;
  background: var(--ds-surface-raised); color: var(--ds-ink);
  aspect-ratio: var(--ds-aspect, 16 / 9);
}
.ds-media > img { display: block; width: 100%; height: 100%; object-fit: cover; }
.ds-media--square { --ds-aspect: 1; }
.ds-media--portrait { --ds-aspect: 3 / 4; }
/* A screenshot shown as a framed screen. It shows the capture from the OTHER theme — a light shot on
   a light page dissolves into it, and the inversion is what makes the product read as a screen
   rather than as more page — inside the master's hairline at the chip radius, and at its own shape:
   a screenshot is evidence, and a slot that crops it cuts off what it is evidence of. */
.ds-media--screen { aspect-ratio: auto; border: 1px solid var(--ds-rule-strong); border-radius: var(--ds-radius-chip); }
/* The master draws the frame at 40% both themes (`screenshot slot · 16:9`, strokes #000/#fff at 0.4).
   Light is the shared strong hairline already; the dark one is 45%, and moving that token would move
   every input border in the set, so this frame — and only this frame — carries the canvas's 40%. */
:where(.dark, [data-theme='dark']) .ds-media--screen { border-color: color-mix(in srgb, #ffffff 40%, transparent); }
.ds-media--screen > img, .ds-media--screen .ds-themed-image__art { width: 100%; height: auto; }
.ds-media--screen .ds-lockup__art--light { display: none; }
.ds-media--screen .ds-lockup__art--dark { display: block; }
:where(.dark, [data-theme='dark']) .ds-media--screen .ds-lockup__art--light { display: block; }
:where(.dark, [data-theme='dark']) .ds-media--screen .ds-lockup__art--dark { display: none; }
/* A client's mark where there is no screenshot (Case study card Media=Mark): centred at its own
   height on field/paper, dark/raised in the dark, and in the ink of the theme rather than of the band
   around the slot — the box is its own field, so a hero on the blue field still takes the dark art on
   the light box. */
.ds-media:has(> .ds-lockup) { display: grid; place-items: center; padding: 24px; background: var(--ds-surface-paper); color: var(--ds-ink); }
/* A one-art lockup is an <img> itself: it keeps the height it was given, not the slot's fill, and the
   centre, not the start the base rule gives a bare image. */
.ds-media > .ds-lockup { place-self: center; max-width: 100%; }
.ds-media > img.ds-lockup, .ds-media > .ds-lockup .ds-lockup__art { width: auto; max-width: 100%; object-fit: contain; }
.ds-media:has(> .ds-lockup) .ds-lockup__art--light { display: block; }
.ds-media:has(> .ds-lockup) .ds-lockup__art--dark { display: none; }
:where(.dark, [data-theme='dark']) .ds-media:has(> .ds-lockup) .ds-lockup__art--light { display: none; }
:where(.dark, [data-theme='dark']) .ds-media:has(> .ds-lockup) .ds-lockup__art--dark { display: block; }
/* When the screen is the way into the product, the frame is the control and takes the focus ring. */
a.ds-media--screen { color: inherit; text-decoration: none; }
a.ds-media--screen:focus-visible { outline: var(--ds-focus-width) solid var(--ds-focus); outline-offset: var(--ds-focus-offset); }

/* ── Hero ──────────────────────────────────────────────────────────────────────────────────── */

/* On a phone the band takes the section rhythm (64) and its content sets the height. Wide, 70 above
   and below: the band is 828 tall on the canvas with a 687 copy column centred in it, and the
   section's own 120 pushed it past that. */
.ds-hero { display: flex; align-items: center; overflow: hidden; padding-block: var(--ds-section-y); min-height: 100svh; }
/* The bar is 72 tall on a wide viewport and 64 on a phone; the hero takes the rest and stops, so
   whatever follows it is always the next thing on screen rather than a surprise. */
@media (min-width: 768px) {
  .ds-hero { padding-block: 70px; --ds-band-top: 70px; }
}
.ds-hero__ground { position: absolute; inset: 0; pointer-events: none; }
/* The packets ride inside the ground, so a drifting ground carries them with it. Lime is what the
   hero already spends its accent on — the eyebrow and the primary button — and the live corporate
   hero spends it here too. At rest they paint nothing: only the animation brings a mark up, so a
   reader who has asked for less motion, or a browser without offset-path, sees an empty layer. */
.ds-hero__packets { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; }
.ds-hero__packet { fill: var(--ds-accent); opacity: 0; }
/* On a phone the band is one column of copy from edge to edge, and the 1440×640 sheet is cropped so
   hard that a rail stops reading as a rail crossing a band and starts reading as a lime mark landing
   inside the eyebrow or the second button. So the packets are the wide band's, as the hero's picture
   is: below the copy-and-picture breakpoint there is no room for either. */
@media (max-width: 767.98px) {
  .ds-hero__packets { display: none; }
}
.ds-hero__inner { position: relative; display: grid; gap: var(--space-lg); align-items: center; }
/* Two columns only when there is room for both: 720 of copy, 420 of picture, 40 between, the picture
   as tall as the copy. Below 1280 the band is the copy alone — the canvas draws the picture beside
   the copy or not at all, and stacked under it the hero runs past any first screen. */
@media (min-width: 1280px) {
  .ds-hero__inner--picture { grid-template-columns: minmax(0, 720px) minmax(0, 420px); justify-content: space-between; gap: 40px; align-items: stretch; }
}
/* 12 between the lines on a phone, 32 wide — the canvas's, both of them. */
.ds-hero__copy { display: flex; flex-direction: column; gap: 12px; min-width: 0; }
/* 720 wide, the canvas's Copy frame in every Size=Desktop variant, with a second column or without. */
@media (min-width: 768px) { .ds-hero__copy { gap: 32px; max-width: 720px; } }
/* The eyebrow is a paragraph; without this it brings the browser's 1em above and below into the gap. */
.ds-hero__eyebrow { margin: 0; }
.ds-hero__headline { margin: 0; max-width: 16ch; }
.ds-hero__lede { margin: 0; max-width: 560px; color: var(--ds-ink-body-on-brand); }
.ds-section--default .ds-hero__lede, .ds-section--paper .ds-hero__lede { color: var(--ds-ink-body); }
/* The meta line sits over the ground, and where a mark of the tile runs under it the ink meets the
   field plus the tile's 8%, not the bare field: the master's 80% white measures 3.97:1 there. The
   ladder's body rung clears it, on every field the hero takes. */
.ds-hero__meta { margin: 0; max-width: 600px; color: var(--ds-ink-body); }
/* On a phone the meta line reads as a caption under the stats, where the Size=Mobile master puts it.
   The DOM keeps the wide order — after the buttons it qualifies — and only the layout moves it. It
   sits 8 under the stats, the master's own band, not the copy's 12. */
@media (max-width: 767.98px) {
  .ds-hero__meta { order: 1; }
  .ds-hero__meta:has(~ .ds-hero__stats) { margin-block-start: -4px; }
}
/* On the hero's own field the eyebrow is where the accent is spent. */
.ds-section--brand .ds-hero__eyebrow, .ds-section--invert .ds-hero__eyebrow { color: var(--ds-accent); }
/* 12 between the stacked buttons on a phone, 16 side by side wide — the masters' own gaps. */
.ds-hero__actions { display: flex; flex-wrap: wrap; gap: 12px; }
@media (min-width: 768px) { .ds-hero__actions { gap: 16px; } }
/* The tiles fill the row whatever their count, as the canvas's FILL tiles do. On a phone the hero's own
   compact tiles, two to a line with an odd last tile across the line: 68 tall, 8/12 inside, 4 between
   label and figure, 8 apart. Wide, one line, the same tiles as the stats row: 136 tall, 24 inside, 8
   between, 16 apart. The field's own ink at 10% either way, which is the white 10% both Size=Mobile
   masters paint on it. */
.ds-hero__stats { display: flex; flex-wrap: wrap; gap: 8px; margin: 0; }
@media (min-width: 768px) { .ds-hero__stats { flex-wrap: nowrap; gap: 16px; } }
/* The figures line up across the row whether or not a label wraps: the tiles stretch to the tallest
   and each one pushes its figure to the bottom. A row of numbers that does not align reads as a
   mistake even when every number is right. */
/* The tile is the field's ink at 10% mixed INTO the field, opaque, rather than laid over it: a
   translucent tile stacks on the ground's own 8% under every label, and white on the two together is
   4.34:1 at the chip's 10px. Opaque, it is the same colour wherever the tile has no mark, and the
   ground stops at its edge. */
.ds-hero__stat { display: flex; flex: 1 1 calc(50% - 4px); flex-direction: column; justify-content: space-between; gap: 4px; min-width: 0; min-height: 68px; padding: 8px 12px; background: color-mix(in srgb, currentColor 10%, var(--ds-surface-field, var(--ds-surface))); }
@media (min-width: 768px) { .ds-hero__stat { flex: 1 1 0; gap: 8px; min-height: 136px; padding: 24px; } }
.ds-hero__figure { margin: 0; }
/* The picture is in the band only where the canvas puts it: beside the copy from 1280, where it
   stretches to the copy's height. Below that the Size=Mobile master is the shape — copy alone — and
   a picture stacked under it would carry the band past the first screen at every tablet width. */
.ds-hero__picture { display: none; min-width: 0; overflow: hidden; border: 1px solid rgb(255 255 255 / 0.3); background: rgb(255 255 255 / 0.08); }
@media (min-width: 1280px) { .ds-hero__picture { display: block; min-height: 420px; } }
.ds-hero__picture > img { display: block; width: 100%; height: 100%; object-fit: cover; }
/* Beside the copy, something that is not a picture: 720 of copy, 360 of aside, the rest between them,
   the aside centred on the copy as the canvas centres the rail. Below 1280 it goes under the copy —
   in a hairline frame, 32 inside, where the band has the room for one, and bare on a phone, where it
   has not: 32 of padding and a hairline either side cost 66, and the clients hero with the rail
   framed stood 820 at 390 against the 780 a phone's first screen leaves (rule 35). */
.ds-hero__aside { min-width: 0; }
@media (min-width: 768px) { .ds-hero__aside { padding: 32px; border: 1px solid var(--ds-rule-strong); } }
/* The frame is for what is in it. An aside whose component rendered nothing is an empty div, and an
   empty div with 32 of padding and a hairline is a 66px box around nothing under the copy. */
.ds-hero__aside:empty { display: none; }
@media (min-width: 1280px) {
  .ds-hero__inner--aside { grid-template-columns: minmax(0, 720px) minmax(0, 360px); justify-content: space-between; gap: 40px; }
  .ds-hero__aside { padding: 0; border: 0; }
}

/* ── Hero rail ─────────────────────────────────────────────────────────────────────────────── */

/* The stages of a delivery down a 3px accent rule: 24 from the label to the list, 32 between stages,
   24 from the rule to the numeral, 16 from the numeral to the name — the master's measures. The rule
   and the numerals take the field's accent ink, so on white they are blue rather than lime on white. */
.ds-herorail { display: flex; flex-direction: column; gap: 24px; min-width: 0; }
.ds-herorail__label { margin: 0; }
.ds-herorail__items { display: flex; flex-direction: column; gap: 32px; margin: 0; padding: 0 0 0 24px; list-style: none; border-inline-start: 3px solid var(--ds-accent-ink, var(--ds-accent)); }
.ds-herorail__item { display: flex; align-items: center; gap: 16px; min-width: 0; }
.ds-herorail__n { flex: none; color: var(--ds-accent-ink, var(--ds-accent)); }
.ds-herorail__title { margin: 0; min-width: 0; overflow-wrap: anywhere; }

/* ── Nav ───────────────────────────────────────────────────────────────────────────────────── */

.ds-nav { --ds-nav-gap: 24px; position: relative; z-index: 10; background: var(--ds-surface); color: var(--ds-ink); border-block-end: 1px solid var(--ds-rule); }
/* Pinned, the bar is the surface at 85% over an 8px blur of whatever has scrolled under it — at rest
   that resolves to the surface itself, so the bar matches its master until the page moves.
   The blur is painted by a layer of the bar's own rather than by the bar: `backdrop-filter` on an
   element makes it the containing block for every `position: fixed` descendant, so the bar carrying
   it turned the menu sheet below — fixed to the viewport — into a 1px strip inside a 64px bar, with
   the page behind it locked and nothing to read. A pseudo-element has no descendants to capture. */
.ds-nav--sticky { position: sticky; top: 0; background: color-mix(in srgb, var(--ds-surface) 85%, transparent); }
.ds-nav--sticky::before { content: ''; position: absolute; inset: 0; z-index: -1; -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px); pointer-events: none; }
/* One gap between every item on the bar, the master's: 32 from 1280 up, 24 below, between the links,
   the toggle, the action and the menu alike (owner, 28 Sep 2026: the gaps read uneven). */
@media (min-width: 1280px) { .ds-nav { --ds-nav-gap: 32px; } }
.ds-nav__bar { display: flex; align-items: center; gap: var(--ds-nav-gap); min-height: var(--ds-nav-height); }
/* --ds-nav-pinned is the band a pinned bar covers at the top of the window, and 0 where no bar pins:
   everything that sticks or lands under the bar clears it by reading this token, never a literal.
   The corporate bar's box is its height plus the 1px border; the product bar draws its hairline
   inside the box. */
:where(:root, .ds-root):has(.ds-nav--sticky) { --ds-nav-pinned: calc(var(--ds-nav-height) + 1px); }
:where(:root, .ds-root):has(.ds-nav--sticky.ds-nav--product) { --ds-nav-pinned: var(--ds-nav-height); }
/* Lockup, a 1×20 hairline, then the section label — the master's own brand group. */
.ds-nav__brand { display: inline-flex; align-items: center; gap: 16px; margin-inline-end: auto; }
.ds-nav__label { position: relative; padding-inline-start: 17px; color: var(--ds-ink-muted); }
.ds-nav__label::before { content: ''; position: absolute; inset-inline-start: 0; inset-block-start: 50%; width: 1px; height: 20px; background: var(--ds-rule); transform: translateY(-50%); }
.ds-nav__links { display: none; gap: var(--ds-nav-gap); }
/* The bar carries its links from 1280 up, and the menu button below it, which is where the master
   puts the change — not at the tablet breakpoint the rest of the library uses. */
@media (min-width: 1280px) { .ds-nav__links { display: flex; align-items: center; } }
/* A bar item never wraps and never overflows; both are the same defect seen from two sides. */
.ds-nav__link { white-space: nowrap; color: var(--ds-ink-muted); }
.ds-nav__link[aria-current='page'] { color: var(--ds-ink); }
.ds-nav__tools { display: flex; align-items: center; gap: var(--ds-nav-gap); }
/* A bar that asks for it draws the 1×20 hairline the Doc shell master carries before its tools — the
   document sites' bar does, the marketing bar does not. The master sets the same gap either side of
   the line as it sets between the links, so the padding is that gap plus the line's own pixel. */
.ds-nav__tools--divided { position: relative; padding-inline-start: 29px; }
.ds-nav__tools--divided::before { content: ''; position: absolute; inset-inline-start: 0; inset-block-start: 50%; width: 1px; height: 20px; background: var(--ds-rule); transform: translateY(-50%); }
/* A control has one home at any width: the bar or the sheet, never both. The bar and the sheet each
   hold a copy, so every line that shows one states the other on the same line — apart they drift,
   and they had: the secondary link stood in the bar from 768 while the open sheet carried it too, so
   at every iPad width a reader met "Sign in" twice at once, and the CTA the same way from 600. The
   bar never wraps and never overflows, which is why the copies move at all; `inline` is the anchor's
   own display, blockified by the tools row either way. */
.ds-nav__secondary { display: none; } .ds-nav__drawer-secondary { display: block; }
@media (min-width: 768px) { .ds-nav__secondary { display: inline; } .ds-nav__drawer-secondary { display: none; } }
/* On the narrowest phones the lockup stands without its label. */
@media (max-width: 479.98px) { .ds-nav__label { display: none; } }
/* The account bar's own measures (Nav / account 1213:7286): the row runs at 32 from the breakpoint up
   and 24 below it, the section label leaves with the links rather than at 480, and the CTA is the
   drawer's block button below the breakpoint, on the bar only where there is no drawer. The menu
   control gives its 10px of hit-target slack back so the reader sees the master's 24. */
.ds-nav--account .ds-nav__tools { gap: 24px; }
.ds-nav--account .ds-nav__menu { margin-inline-start: -10px; }
@media (max-width: 1279.98px) { .ds-nav--account .ds-nav__label { display: none; } .ds-nav--account .ds-nav__action { display: none; } .ds-nav--account .ds-nav__drawer-action { display: inline-flex; } }
@media (min-width: 1280px) { .ds-nav--account .ds-nav__bar, .ds-nav--account .ds-nav__tools { gap: 32px; } }
/* The signed-in mark the master draws after the theme control: 32 square, square-cornered, whatever
   the site puts inside it. */
.ds-nav__avatar { display: inline-flex; flex: none; align-items: center; justify-content: center; width: 32px; height: 32px; overflow: hidden; background: color-mix(in srgb, currentColor 8%, transparent); }
.ds-nav__avatar > img, .ds-nav__avatar > svg { display: block; width: 100%; height: 100%; object-fit: cover; }
.ds-nav__action { white-space: nowrap; display: none; } .ds-nav__drawer-action { display: inline-flex; }
@media (min-width: 600px) { .ds-nav__action { display: inline-flex; } .ds-nav__drawer-action { display: none; } }
/* The one menu control for both sets: the lucide glyph every Nav master draws in its `Menu button ·
   <1280` frame — 20×20 in the bar's ink, `menu` closed and `x` while the drawer is open — never a
   word. The master draws the glyph alone, so the same trick as the theme control beside it: padding
   and a matching negative margin make the hit target 40px while the layout still sees a 20×20 glyph.
   The glyph therefore ends on the bar's inline padding (346–366 on the 390 master), and the 2px on
   the start side is the 12 the master adds to the bar's own 12 gap, less the 10 of slack — so the
   glyph stands 24 from the control before it, as it does on the master. */
.ds-nav__menu { display: inline-flex; align-items: center; justify-content: center; width: 40px; height: 40px; margin: -10px; padding: 10px; border: 0; background: none; color: inherit; cursor: pointer; }
@media (min-width: 1280px) { .ds-nav__menu { display: none; } }
.ds-nav__menu:focus-visible { outline: var(--ds-focus-width) solid var(--ds-focus); outline-offset: var(--ds-focus-offset); }
.ds-nav__drawer { border-block-start: 1px solid var(--ds-rule); }
.ds-nav__drawer-inner { display: flex; flex-direction: column; gap: 0; padding-block: 8px var(--space-sm); }
/* The links are a navigation landmark of their own — the bar's own <nav> is display:none at the
   width the drawer shows, so a reader below the breakpoint has exactly one set of links to find. */
.ds-nav__drawer-links { display: flex; flex-direction: column; }
/* 48px rows: 16 above, 16 below, the link's own line between. */
.ds-nav__drawer-link { padding-block: 16px; color: var(--ds-ink-muted); }
.ds-nav__drawer-link[aria-current='page'] { color: var(--ds-ink); }
.ds-nav__drawer .ds-button { margin-block-start: 16px; }
/* A bar or menu link arrives at full ink under the pointer rather than fading: the live bars darken
   their links, and a muted link that fades further is harder to read for having been pointed at. */
.ds-nav__link:hover, .ds-nav__drawer-link:hover { opacity: 1; color: var(--ds-ink); }
@media (min-width: 1280px) { .ds-nav__drawer { display: none; } }

/* ── Product nav ───────────────────────────────────────────────────────────────────────────── */

/*
 * The product bar has two looks and the page decides between them (`NavProduct` `overHero` /
 * `pinned`). Pinned, it is the page's field at 85% over a blur with a hairline under it — the
 * master's opaque bar, on an opaque page. Clear, it is nothing at all: no field, no hairline, the
 * dark ink ladder and the white lockup on the deep band beneath, until that band has passed under
 * it. The classes are the component's states; every value here is the theme's.
 *
 * The hairline is a 1px ::after and not a border, so the bar's box is exactly --ds-nav-height. A
 * border makes it one pixel taller than the negative margin that pulls the band up under it, and
 * that pixel of page showed as a light line above the deep hero at every width.
 */
.ds-nav--product { border-block-end: 0; background: color-mix(in srgb, var(--ds-surface) 85%, transparent); }
/* The 24px blur is a layer of the bar's own, for the reason the corporate sticky bar states above:
   `backdrop-filter` on the bar makes it the containing block for every `position: fixed` descendant,
   and the product menu sheet is fixed — so the bar carrying it collapsed the sheet into the 64px bar
   (measured 390×0 where the master draws 390×780). Setting the filter to `none` while the drawer is
   open does not undo it either: the sheet mounts inside the bar's subtree. A pseudo-element has no
   descendants to capture, so the blur stays and the sheet reaches the viewport. */
.ds-nav--product::before { content: ''; position: absolute; inset: 0; z-index: -1; -webkit-backdrop-filter: blur(24px); backdrop-filter: blur(24px); pointer-events: none; }
.ds-nav--product::after { content: ''; position: absolute; inset-inline: 0; inset-block-end: 0; height: 1px; background: var(--ds-rule); opacity: 1; }
/* Floats over what follows it: the band starts at the top of the page and runs under the bar. */
/* The band under the bar starts at the top of the page: the pull equals the bar's whole box, sticky or
   not, which is the height token plus the corporate bar's 1px border, or a line of page showed between
   the bar and the hero on a phone (owner, 28 Sep 2026). The product bar has no border. */
.ds-nav--over { margin-block-end: calc(-1 * var(--ds-nav-height) - 1px); }
.ds-nav--product.ds-nav--over { margin-block-end: calc(-1 * var(--ds-nav-height)); }
/* The band under the bar adds the bar's height, from the one height token, to its own top padding.
   A band whose top padding is not the section rhythm declares it as `--ds-band-top` beside that padding. */
.ds-nav--over + .ds-section { padding-block-start: calc(var(--ds-nav-height) + var(--ds-band-top, var(--ds-section-y))); }
/* The first BAND of main, not its first child: a framework drops a <script> ahead of the hero.
   The list names each wrapper a component draws around its first band; nav-band-clearance.test.mjs derives it. */
.ds-nav--over + main > .ds-section:nth-child(1 of .ds-section, .ds-article, .ds-apply) { padding-block-start: calc(var(--ds-nav-height) + var(--ds-band-top, var(--ds-section-y))); }
.ds-nav--over + main > .ds-article:nth-child(1 of .ds-section, .ds-article, .ds-apply) > .ds-article__hero { padding-block-start: calc(var(--ds-nav-height) + var(--ds-band-top, var(--ds-section-y))); }
.ds-nav--over + main > .ds-apply:nth-child(1 of .ds-section, .ds-article, .ds-apply) > .ds-apply__band { padding-block-start: calc(var(--ds-nav-height) + var(--ds-band-top, var(--ds-section-y))); }
/* At rest over the band. Its ink ladder is the dark one, from the list at the top of this file, so
   the ring on the deep field is the product's lift and not the brand purple at 1.9:1. */
/* Three looks over a hero (useHeroFloat): clear at the top of the page; veiled once the reader has
   scrolled while the hero is still under the bar, the hero's own field at 85% over the blur so the
   band's copy passes under the bar and never through its links; pinned once the hero has passed.
   The veil's field is the hero's: the product hero is the invert band, the corporate hero its own. */
.ds-nav--clear { background: transparent; border-block-end-color: transparent; }
.ds-nav--clear::before { -webkit-backdrop-filter: none; backdrop-filter: none; }
.ds-nav--clear::after { opacity: 0; }
.ds-nav--veil { background: color-mix(in srgb, var(--ds-nav-field, var(--ds-surface-invert)) 85%, transparent); border-block-end-color: transparent; }
.ds-nav--veil::after { opacity: 0; }
.ds-nav:not(.ds-nav--product) { --ds-nav-field: var(--ds-surface-hero); }
/* The bar's quiet ink over the hero: the ladder's 78% white measures 4.49:1 on the brand field seen
   through the veil (#d2dceb on #3460a7, axe on clients.cuesoft.io), one hundredth under AA for the
   10px section label and the 14px links, and 84% measures 4.21:1 once the hero's 8% white pattern
   sits under the clear bar (#dee5f1 on #426bad, axe on cueta.cuesoft.io). 92% clears both. */
.ds-nav--clear, .ds-nav--veil { --ds-ink-muted: rgb(255 255 255 / 0.92); --ds-ink-placeholder: rgb(255 255 255 / 0.92); }
/* Open, bar and drawer are one opaque sheet on the page's field, whatever is behind them. */
.ds-nav--product.ds-nav--open { z-index: 60; background: var(--ds-surface); color: var(--ds-ink); }
.ds-nav--product.ds-nav--open::before { -webkit-backdrop-filter: none; backdrop-filter: none; }
/* The product menu is a sheet, not a band. `Nav / product` Size=Mobile Drawer=Open draws 390×780
   under a 64 bar: the drawer hangs off the bar and covers the page to the bottom edge, above the
   consent bar and the back-to-top control, and while it is open the page behind it holds its place.
   Its rows are the master's — 72 tall (18 above, the 24/36 line, 18 below) in full ink over a
   hairline, the action full width 16 under the last one — and its field is the one the bar paints,
   so bar and sheet read as a single surface. The master fills both with dark/base in Dark while the
   product theme's dark surface is the brand's own deep; that is the product dark ladder's call
   (DARK-01), not a fill this one component may settle against the bar it hangs from. */
.ds-nav--product .ds-nav__drawer { position: fixed; inset: var(--ds-nav-height) 0 0; overflow-y: auto; overscroll-behavior: contain; border-block-start: 0; background: var(--ds-surface); color: var(--ds-ink); }
.ds-nav--product .ds-nav__drawer-inner { padding-block: 8px 24px; }
/* 72px rows, as the master measures them: 18 above, the 24/36 line, 18 below, and the hairline drawn
   INSIDE the row the way a Figma stroke is — a border would add its pixel to every row and stand the
   sheet five taller than the master. */
.ds-nav--product .ds-nav__drawer-link { padding-block: 18px; box-shadow: inset 0 -1px 0 var(--ds-rule); color: var(--ds-ink); }
/* The page behind an open sheet holds its place. The attribute is set on the sheet's own document,
   so a preview frame locks itself and not the page around it. */
body[data-ds-drawer-open] { overflow: hidden; }

/* Text for a screen reader only. Not display:none, which takes it out of the reader too, and not
   zero width/height, which some readers skip. The :not(:focus) lets a skip link show itself. */
/* One lockup in the markup, both arts in it, CSS choosing — the same way every other difference
   between the themes is handled here. */
.ds-lockup { display: inline-flex; flex: none; align-items: center; }
.ds-lockup__art { display: block; width: auto; }
/* A picture that swaps with the theme fills the slot it is given, unlike a lockup, which is sized
   optically by its own height. Its two arts are lockup arts as well, so this comes AFTER the
   lockup's `width: auto`: same specificity, and the last one standing is the one on screen. */
.ds-themed-image { display: block; width: 100%; height: 100%; }
.ds-themed-image__art { width: 100%; height: 100%; object-fit: cover; }
.ds-lockup__art--dark { display: none; }
:where(.dark, [data-theme='dark']) .ds-lockup__art--light { display: none; }
:where(.dark, [data-theme='dark']) .ds-lockup__art--dark { display: block; }
/* On a field that is dark whatever the theme, the white art is the right one both ways. */
:where(.ds-section--brand, .ds-section--invert, .ds-psection--deep, .ds-footer, .ds-pfooter, .ds-nav--clear, .ds-nav--veil) .ds-lockup__art--light { display: none; }
:where(.ds-section--brand, .ds-section--invert, .ds-psection--deep, .ds-footer, .ds-pfooter, .ds-nav--clear, .ds-nav--veil) .ds-lockup__art--dark { display: block; }

/* The mark takes the ink of whatever it is in, and the size the component asks for. */
.ds-tool-mark, .ds-icon { display: inline-flex; flex: none; }
.ds-icon > svg { width: 100%; height: 100%; display: block; }
/* The list tick: never squeezed by the line beside it, and on the text's midline when it sits in a run. */
.ds-tick { flex: none; vertical-align: -0.125em; }
/* The five sizes the set is drawn at; the svg fills the box, so the stroke scales with it. */
.ds-icon--14 { width: var(--ds-icon-14); height: var(--ds-icon-14); }
.ds-icon--16 { width: var(--ds-icon-16); height: var(--ds-icon-16); }
.ds-icon--18 { width: var(--ds-icon-18); height: var(--ds-icon-18); }
.ds-icon--20 { width: var(--ds-icon-20); height: var(--ds-icon-20); }
.ds-icon--24 { width: var(--ds-icon-24); height: var(--ds-icon-24); }
.ds-tool-mark > svg { width: 100%; height: 100%; display: block; }

.ds-sr-only:not(:focus):not(:active) { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }

/* ── Footer ────────────────────────────────────────────────────────────────────────────────── */

/* A panel, not a band: black in the light, one rung above the page in the dark. The master's 1px
   inside top stroke — white at 12%, both themes — is drawn as an inset shadow so it adds no height.
   The last block on the page keeps its last line clear of whatever is docked to the foot of the
   window: the consent bar's published height, and on a phone the lane the back-to-top control needs. */
.ds-footer { padding-block: var(--ds-section-y) calc(max(var(--ds-section-y), var(--ds-dock-lane, 0px)) + var(--ds-dock, 0px)); background: var(--ds-surface-invert-panel); color: var(--ds-ink-on-invert); box-shadow: inset 0 1px 0 0 var(--ds-rule-on-invert); }
.ds-footer__columns { display: grid; gap: var(--space-lg); }
@media (min-width: 700px) { .ds-footer__columns { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
/* Equal columns, 32 apart, as many as the footer holds, brand column included: the master's four
   for three link columns, three for two. A fixed count of four left a site with two link columns an
   empty track down the right of its footer (owner, 28 Sep 2026). */
@media (min-width: 1000px) { .ds-footer__columns { grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr); gap: 32px; } }
.ds-footer__brand { display: flex; flex-direction: column; align-items: flex-start; gap: 16px; }
.ds-footer__blurb { margin: 0; max-width: 42ch; opacity: 0.75; }
/* 20px glyphs on the live footers' 36px pitch; the 24px box is the pointer-target minimum, and its
   2px either side is pulled back so the first glyph sits on the column's edge. */
.ds-footer__social { display: flex; flex-wrap: wrap; gap: 12px; margin: 0 0 0 -2px; padding: 0; list-style: none; }
.ds-footer__social-link { display: inline-flex; align-items: center; justify-content: center; width: 24px; height: 24px; color: var(--ds-ink-muted); }
.ds-footer__social-link:focus-visible { outline: var(--ds-focus-width) solid var(--ds-focus); outline-offset: var(--ds-focus-offset); }
.ds-footer__column { display: flex; flex-direction: column; gap: 12px; }
.ds-footer__heading { margin: 0 0 2px; opacity: 0.6; }
/* A column link is FULL ink. Both footer masters fill every one of them with `brand/white` outright
   — no alpha — and reserve the fade for the voices that are not the list: the blurb at 70%, the
   heading and the utility row at 60%. The 85% here was a third step nothing draws, and it read as a
   fourth grey beside them. */
.ds-footer__link { opacity: 1; }
.ds-footer__rule { margin-block: var(--space-lg) var(--space-sm); border-color: color-mix(in srgb, currentColor 22%, transparent); }
/* The masters' own measures, corporate and product alike: the row that holds the links group and the
   copyright is 24 apart on both axes — 24 across on the desktop bar, 24 down where the phone stacks
   them — and inside the links group a wrapped line drops 12, so the group reads as one block against
   the 24 that separates it from the copyright. */
.ds-footer__utility { display: flex; flex-wrap: wrap; align-items: center; gap: 24px; }
.ds-footer__utility-links { display: flex; flex-wrap: wrap; gap: 12px 24px; }
/* The row's one control is drawn as its neighbours are: a button that acts, in the clothes of a link.
   Only the control's own chrome is reset; the type comes from the role class beside it. */
.ds-footer__utility-button { appearance: none; background: none; border: 0; padding: 0; margin: 0; color: inherit; cursor: pointer; text-align: start; }
/* Both masters set the utility row at 60% of the footer's ink, the column links at full; it comes up
   to full under the pointer or the keyboard, as the live footers' control does. The rule sits on the
   row's children, so a node the site drew itself reads the same as a typed item beside it. */
.ds-footer__utility-links > * { opacity: 0.6; }
.ds-footer__utility-links > :hover, .ds-footer__utility-links > :focus-visible { opacity: 1; }
.ds-footer__copyright { margin: 0; opacity: 0.6; }
@media (min-width: 700px) { .ds-footer__copyright { margin-inline-start: auto; } }

/* ── Stats ─────────────────────────────────────────────────────────────────────────────────── */

.ds-stats { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; margin: 0; }
@media (min-width: 768px) { .ds-stats { grid-template-columns: repeat(var(--ds-stats-cols, 4), minmax(0, 1fr)); } }
/* A stat tile is a black tile with a white figure on it — that is the master, on a page of any
   colour. Inside a band that is already dark it stops being a tile and becomes a tint, because a
   black tile on a black band is not a tile. */
.ds-stats__item { display: flex; flex-direction: column; justify-content: space-between; gap: 8px; min-height: 136px; padding: 24px; background: color-mix(in srgb, currentColor 8%, transparent); color: inherit; }
.ds-stats__label { color: inherit; }
.ds-stats__figure, .ds-stats__note { margin: 0; }

/* ── Logo band ─────────────────────────────────────────────────────────────────────────────── */

/* The band is its own rule in the page, not a section: a hairline above and 40 either side, as the
   master draws it, whatever the section rhythm around it is doing. */
.ds-logo-band { padding-block: var(--space-lg); --ds-band-top: var(--space-lg); border-block-start: 1px solid var(--ds-rule); }
/* Caption first, then the marks, 28 apart — the master's measure. */
.ds-logo-band > .ds-container { display: flex; flex-direction: column; gap: 28px; align-items: center; text-align: center; }
.ds-logo-band__caption { margin: 0; color: var(--ds-ink-muted); }
.ds-logo-band__marks { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: var(--space-sm) var(--space-lg); }
/* The marks are the client's own ink at 80%, held back so the caption still leads. The slot hugs its
   mark rather than fixing one height: the marks are sized one by one, 24 to 30, and the row centres
   them — a slot that clamped them all to one figure overflowed the tall ones and stranded the short. */
.ds-logo-band__mark { display: inline-flex; flex: none; align-items: center; opacity: 0.8; }
/* A bare picture with no height of its own takes the middle of that range; a lockup's inline height
   wins over this. It reaches the slot's own child only. A descendant rule would also reach inside a
   component that manages its own children — a lockup hides one of its two arts by theme, and a rule
   like this one outranked that and showed both at once. */
.ds-logo-band__mark > :where(img, svg) { display: block; height: 28px; width: auto; }

/* ── Person ────────────────────────────────────────────────────────────────────────────────── */

/* The master's card: every slot inset 32 from the card's edge and 12 apart, the portrait a fixed 200
   tall across the card's width (`People card row`, FILL × FIXED at both sizes). The inset counts the
   card's own hairline, as the canvas's inside stroke does, so the padding is 32 less that edge. The
   card fills its cell and the meta line takes the space left, so it lands at the foot of every card
   in a row however long each bio runs. */
.ds-person { position: relative; display: flex; flex-direction: column; gap: 12px; height: 100%; padding: calc(32px - var(--ds-person-edge-w, 1px)); padding-inline-start: calc(32px - var(--ds-person-start-w, var(--ds-person-edge-w, 1px))); border: var(--ds-person-edge-w, 1px) solid var(--ds-rule); border-inline-start: var(--ds-person-start-w, var(--ds-person-edge-w, 1px)) solid var(--ds-rule); }
/* Joined into one frame: each card after the first in its row drops its left hairline, so an internal
   division is the 1px the master draws and not two of them. By row, not by `:first-child`, so a
   second row — or the card a centred last row begins with — keeps its own. The CELL carries the
   decision and the card reads it, because the card is not the grid's own child: a reveal wraps every
   one, and what a grid puts in a cell is the grid's business. A rule that named the card as a child
   would match nothing and fail nothing; an inherited property reaches it through whatever stands
   between them. */
@media (min-width: 1000px) {
  .ds-people .ds-grid { gap: 0; }
  .ds-people .ds-grid[data-cols='2'] > *:not(:nth-child(2n + 1)),
  .ds-people .ds-grid[data-cols='3'] > *:not(:nth-child(3n + 1)),
  .ds-people .ds-grid[data-cols='4'] > *:not(:nth-child(4n + 1)) { --ds-person-start-w: 0px; }
}
/* One column is the master's Size=Mobile: one stroked frame, the cards unstroked inside it, and a
   hairline between each two with 24 above and 24 below it. The frame is the grid's, so its sides run
   unbroken through those gaps, and it is drawn inside the grid's box as the canvas strokes inside,
   so the cards keep the full width; the hairline is the lower card's, 24 above its own edge. */
@media (max-width: 599.98px) {
  .ds-people .ds-grid { row-gap: 49px; box-shadow: inset 0 0 0 1px var(--ds-rule); }
  .ds-people .ds-grid > * { --ds-person-edge-w: 0px; }
  .ds-people .ds-grid > * + * .ds-person::before { content: ''; position: absolute; inset-inline: 0; inset-block-start: -25px; height: 1px; background: var(--ds-rule); }
}
/* Where cards stand side by side their slots share the row's tracks, so a role that wraps in one
   card moves every bio in the row with it and the row stays level, the way the card grid's slots do.
   The master hugs the role to its one line, so nothing is reserved for a second: the tracks take the
   tallest card's line count, whatever that is, and the two-line reservation a lone card keeps is
   dropped. The grid's own row gap is 0 here because a subgrid's slots sit in the grid's gutters: a
   gutter wider than the card's 12 stretches every slot box by the difference, so a second row of
   cards is spaced by its cells instead. */
@media (min-width: 600px) {
  .ds-people .ds-grid { row-gap: 0; }
  .ds-people .ds-grid > .ds-reveal { grid-template-rows: subgrid; grid-row: span var(--ds-person-rows, 5); }
  .ds-people .ds-person { display: grid; grid-template-rows: subgrid; grid-row: span var(--ds-person-rows, 5); gap: 12px; height: auto; align-content: start; }
  .ds-people .ds-person__role { min-height: 0; }
  /* A card with fewer slots than the row still ends on the row's foot, as the stack's meta line does. */
  .ds-people .ds-person > :last-child:not(:first-child) { grid-row-end: -1; }
}
@media (min-width: 600px) and (max-width: 999.98px) {
  .ds-people .ds-grid > .ds-reveal:nth-child(n + 3) { padding-block-start: var(--ds-card-gap); }
}
.ds-person__portrait { aspect-ratio: 1 / 1; height: auto; width: 100%; }
.ds-person__name { margin: 0; }
.ds-people__footnote { margin: 0; color: var(--ds-ink-muted); }
/* The role is where the accent is spent on a person card, so it reads before the bio does. */
.ds-person__role { margin: 0; color: var(--ds-accent-ink, var(--ds-ink-muted)); }
.ds-person__bio { margin: 0; color: var(--ds-ink-body); }
.ds-person__meta { margin: 0; margin-block-start: auto; color: var(--ds-ink-muted); }
.ds-person__links { display: flex; flex-wrap: wrap; gap: 12px; }

/* ── Accordion ─────────────────────────────────────────────────────────────────────────────── */

.ds-accordion { display: flex; flex-direction: column; gap: var(--ds-heading-gap); }
/* The rows and the action under them reveal as one block, so they share a wrapper that keeps the stack's gap. */
.ds-accordion__body { display: flex; flex-direction: column; gap: var(--ds-heading-gap); }
/* The count line stands on the rows, so the two sit in one column with no gap between them. */
.ds-accordion__list { display: flex; flex-direction: column; }
/* "5 courses · 20 modules · 4 weeks": the line the live tiers draw over their course rows, carrying
   the rule the first row would otherwise draw. */
.ds-accordion__count { margin: 0; padding-block-end: 12px; border-block-end: 1px solid var(--ds-rule); color: var(--ds-ink-muted); }
.ds-accordion__rows { border-block-start: 1px solid var(--ds-rule); }
.ds-accordion__count + .ds-accordion__rows { border-block-start: 0; }
.ds-accordion__item { border-block-end: 1px solid var(--ds-rule); }
.ds-accordion__a { padding-block-end: 20px; max-width: 78ch; color: var(--ds-ink-body); }
/* What the row teaches, in order: a two-digit index in tabular figures, then the module. */
.ds-accordion__modules { display: grid; gap: 6px; margin: 12px 0 0; padding: 0; list-style: none; }
.ds-accordion__module { display: flex; gap: 10px; align-items: baseline; color: var(--ds-ink-body); }
.ds-accordion__module-index { flex: none; color: var(--ds-ink-muted); font-variant-numeric: tabular-nums; }
.ds-accordion__module-text { min-width: 0; }
.ds-accordion__action { display: flex; }

/* ── Contact band ──────────────────────────────────────────────────────────────────────────── */

/* Band: the copy holds the canvas's 720 column and the action stands at its foot, level with the
   heading's last line rather than floating beside its middle. The row's own column gap is zero — the
   master sets the button flush at the copy's right edge — and its ROW gap is 32, which is what the
   390 master measures between the heading's last line and the full-width button (Band row gap 32,
   button top 215, band 390x327 on 2541:302837). */
.ds-contact-band__row { display: flex; flex-wrap: wrap; align-items: flex-end; gap: var(--space-md) 0; }
.ds-contact-band__copy { flex: 0 1 720px; min-width: 0; }
.ds-contact-band__title { margin: 0; }
.ds-contact-band__eyebrow { margin: 0; }
.ds-contact-band__eyebrow, .ds-contact-band .ds-section-heading__eyebrow { color: var(--ds-accent-ink, var(--ds-ink-muted)); }
/* Cards: heading, action and the two cards, each a heading gap apart, as the canvas stacks them. */
.ds-contact-band__stack { display: flex; flex-direction: column; align-items: flex-start; gap: var(--ds-heading-gap); }
.ds-contact-band__stack > .ds-section-heading { align-self: stretch; }
.ds-contact-band__cards { display: grid; gap: var(--ds-card-gap); align-self: stretch; }
@media (min-width: 900px) { .ds-contact-band__cards { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
/* A card, and the direct panel, is one translucent step above the field it sits on: the field's own
   ink at 10%, so it reads on the blue band, on the dark ladder and on a light page alike. */
.ds-contact-card { display: flex; flex-direction: column; gap: 8px; padding: var(--space-sm); background: color-mix(in srgb, currentColor 10%, transparent); }
.ds-contact-panel { display: grid; gap: var(--space-sm); padding: var(--space-sm); background: color-mix(in srgb, currentColor 10%, transparent); }
/* On a blue field that step reads #476EAF, and the band's 90% body ink measures 4.48:1 on it where AA
   wants 4.5. Inside the step the body ink is 95% white, as the Side cards master letters it (4.79:1);
   the Direct master's 90% is the canvas's to take back. */
.ds-section--brand :is(.ds-contact-card, .ds-contact-panel) { --ds-ink-body: rgb(255 255 255 / 0.95); }
/* On a phone the canvas keeps 24 inside a card and inside the panel, its columns flush; on a wide
   viewport the card and the panel take 32 and each panel column another 32. */
@media (min-width: 768px) {
  .ds-contact-card { padding: var(--space-md); }
  .ds-contact-panel { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-md); padding: var(--space-md); }
  .ds-contact-panel__col { padding-inline: var(--space-md); }
}
/* The panel's eyebrows are where its accent is spent; a side card's eyebrows are the field's own ink. */
.ds-contact-panel .ds-contact-block__eyebrow { color: var(--ds-accent-ink, var(--ds-ink-muted)); }
/* Except on the brand band, where the accent is lime and the panel lightens the field under it: the
   panel is white at 10% over the blue, which reads #476EAF, and 11px lime on that measures 4.31:1
   where AA wants 4.5. Ink on blue is white — the same reading the programme band's callout label
   took — and here it is FULL white (5.10:1), because the 85% the callout can afford measures 4.19:1
   over the lightened panel. The master paints this eyebrow brand/lime and its siblings beside it
   white, so the canvas wants the write-back; the estate's contrast rule decides it meanwhile.
   The raised and accent fields are the same case the other way: the step darkens them, and the blue
   accent on it measures 4.37:1 and 4.32:1, so the eyebrow there is the field's ink too. */
:is(.ds-section--brand, .ds-section--raised, .ds-section--accent) .ds-contact-panel .ds-contact-block__eyebrow { color: var(--ds-ink); }
.ds-contact-block { display: flex; flex-direction: column; gap: 8px; }
.ds-contact-block__eyebrow { margin: 0; }
.ds-contact-block__body, .ds-contact-block__line, .ds-contact-block__step-text { margin: 0; color: var(--ds-ink-body); }
.ds-contact-block__lines { margin: 0; padding: 0; list-style: none; }
.ds-contact-block__steps { display: flex; flex-direction: column; gap: 8px; margin: 0; padding: 0; list-style: none; }
.ds-contact-block__step { display: flex; gap: 10px; }
.ds-contact-block__step-n { flex: none; padding-block-start: 4px; }

/* ── Not found ─────────────────────────────────────────────────────────────────────────────── */

/* The page a wrong address lands on. Top-aligned, and as tall as the master draws it — 480 on the
   phone, 612 from 768 up — so the footer does not ride up under a one-line heading. Both masters
   share this block: the two sets differ in their faces, fields and button, not in this rhythm. */
.ds-notfound { min-height: 480px; }
@media (min-width: 768px) { .ds-notfound { min-height: 612px; } }
.ds-notfound__inner { display: flex; flex-direction: column; align-items: flex-start; gap: 32px; min-width: 0; }
.ds-notfound__copy { display: flex; flex-direction: column; align-items: flex-start; gap: 16px; min-width: 0; }
@media (min-width: 768px) { .ds-notfound__inner { gap: 40px; } .ds-notfound__copy { gap: 20px; } }
.ds-notfound__eyebrow { margin: 0; color: var(--ds-accent-ink); }
/* 760 is the master's copy column, and it beats the display role's own 15ch/21ch measure because
   the master sets the heading to the column and not to its own text. */
.ds-notfound__title { margin: 0; max-width: 760px; }
.ds-notfound__body { margin: 0; max-width: 45ch; color: var(--ds-ink-body); }

/* ── Callout ───────────────────────────────────────────────────────────────────────────────── */

/* A white panel with a hairline: on a white page it reads as the hairline alone, on the blue programme
   band as the white card the canvas draws there. */
.ds-callout { display: flex; flex-direction: column; gap: 16px; padding: var(--space-sm); background: var(--ds-surface); color: var(--ds-ink); box-shadow: inset 0 0 0 1px var(--ds-rule); }
/* Kind=Standard pads 24 at Size=Mobile and 32 at Size=Desktop; chip, statement, body and actions stand 16 apart at both. */
@media (min-width: 768px) { .ds-callout { padding: var(--space-md); } }
.ds-callout--accent { background: var(--ds-surface-accent); color: var(--ds-ink-on-accent); box-shadow: none; }
.ds-callout__chip { align-self: flex-start; }
/* The same slot as a label rather than a chip — the form the programme band's master draws, where
   the line is an eyebrow in the field's accent ink and the box has no chip in it at all. */
.ds-callout__label { align-self: flex-start; margin: 0; color: var(--ds-accent-ink); }
.ds-callout__statement { margin: 0; max-width: 46ch; }
/* Every Kind letters its body in full ink, brand black or white, not the body rung. */
.ds-callout__body { margin: 0; color: var(--ds-ink); }
.ds-callout--accent .ds-callout__body { color: inherit; }
.ds-callout__actions { display: flex; flex-wrap: wrap; gap: 16px; }
/* An action's leading mark takes the master's slot (Button / corporate Icon=Leading, `icon · 20`): 20
   square, so a smaller mark still sets the label where the master does, 8 after the slot. */
.ds-callout__actions .ds-button__icon { justify-content: center; min-inline-size: 20px; block-size: 20px; }
/* On a phone the actions are the master's stack (Kind=Standard, Size=Mobile: 294 wide, 48 tall, 12
   apart, one under the other) — at the SAME width the buttons inside them go full width, because
   `block='mobile'` and this rule are one decision and a seam between them is a row of full-width
   buttons pretending to be a stack. */
@media (max-width: 767.98px) {
  .ds-callout__actions { flex-direction: column; flex-wrap: nowrap; align-items: stretch; gap: 12px; }
}

/* ── Centred band heads ────────────────────────────────────────────────────────────────────── */

/* Chip, heading and sub stand 40 apart at every width: the Testimonial and Comparison table masters
   draw one 40 column, the same token Ask Ace's head spaces by. */
.ds-compare__head-block, .ds-quotes__head { display: flex; flex-direction: column; align-items: center; gap: var(--space-lg); text-align: center; }

/* ── Testimonial ───────────────────────────────────────────────────────────────────────────── */

/* One centred column: chip, heading, sub, then the quote and who said it on a 640 measure. The
   heading is centred and the quote is not — that asymmetry is the master's, and it is what stops a
   long quotation reading as a centred poem. */
.ds-quotes { display: flex; flex-direction: column; align-items: center; gap: var(--space-lg); }
.ds-quotes__title { margin: 0; max-width: 760px; }
.ds-quotes__intro { margin: 0; max-width: 520px; color: var(--ds-ink-body); }
/* One quote at a time in a track the browser scrolls and snaps itself: a swipe on a phone, the dots
   or the arrow keys elsewhere. Nothing advances on its own. The scrollbar is hidden because the
   dots are the indicator; the track stays keyboard-scrollable. */
.ds-quotes__track { position: relative; display: flex; width: 100%; overflow-x: auto; overscroll-behavior-x: contain; scroll-snap-type: x mandatory; scrollbar-width: none; }
.ds-quotes__track::-webkit-scrollbar { display: none; }
.ds-quotes__slide { display: flex; flex: 0 0 100%; justify-content: center; scroll-snap-align: start; scroll-snap-stop: always; }
.ds-quotes__track:focus-visible { outline: var(--ds-focus-width) solid var(--ds-focus); outline-offset: var(--ds-focus-offset); }
.ds-quotes__dots { display: flex; }
/* 24×24 targets around 8px ink: WCAG 2.5.8 measures the target, not the ink. The ink is the canvas's
   8px square; the targets sit edge to edge so none overlaps its neighbour. */
.ds-quotes__dot { display: grid; place-items: center; width: 24px; height: 24px; padding: 0; border: 0; color: inherit; background: none; cursor: pointer; }
.ds-quotes__dot::before { content: ''; width: 8px; height: 8px; background: currentColor; opacity: 0.4; transition: opacity var(--ds-fast, 140ms) var(--ds-ease, ease), background-color var(--ds-fast, 140ms) var(--ds-ease, ease); }
@media (hover: hover) {
  .ds-quotes__dot:hover::before { opacity: 0.7; }
}
.ds-quotes__dot[aria-current='true']::before { background: var(--ds-accent-ink); opacity: 1; }
.ds-quotes__dot:focus-visible { outline: var(--ds-focus-width) solid var(--ds-focus); outline-offset: var(--ds-focus-offset); }
/* The attribution is pinned to the foot of the slide so it lands at the same y on every quote,
   whatever its length; the track is as tall as the longest. */
.ds-quote { display: flex; flex-direction: column; gap: 40px; width: 100%; max-width: 640px; margin: 0; }
.ds-quote__body { margin: 0; }
.ds-quote__by { display: flex; align-items: center; gap: 12px; margin-block-start: auto; }
.ds-quote__portrait { width: 44px; flex: none; }
.ds-quote__meta { --ds-gap: 4px; }
.ds-quote__role { color: var(--ds-ink-muted); }

/* ── Fields ────────────────────────────────────────────────────────────────────────────────── */

.ds-field { display: flex; flex-direction: column; gap: 6px; }
/* Full ink and sentence case, as every live form writes its labels; the hint under it is the muted line. */
.ds-field__label { margin: 0; }
.ds-field__hint { margin: 0; color: var(--ds-ink-muted); }
.ds-field__error { margin: 0; color: var(--ds-error); }

.ds-input {
  width: 100%;
  min-height: 52px;
  padding: 0 16px;
  color: inherit;
  /* The well the master fills, not the page: the corporate sets point --ds-surface-sunken back at the
     surface, the product set at neutral/sunken in the light and the raised rung in the dark. */
  background: var(--ds-surface-sunken);
  /* A product scope names its own field edge, --ds-rule-field, the live sites' card border (#e4e7ec in
     the light, white at 10% in the dark). Elsewhere --ds-rule-strong: on a corporate page black at 40%
     in the light and white at 45% in the dark, until a scope such as the Ace console or a cutout
     re-points it. The Form / product master strokes the field black at 40% in the light and white at 15%
     in the dark; the canvas has not been reconciled with the live sites yet. */
  border: 1px solid var(--ds-rule-field, var(--ds-rule-strong));
  border-radius: var(--ds-radius-control);
}
/* A control never falls back to the browser's own face: without a type role it takes the text around it (zero specificity, so a type role wins). */
:where(.ds-input) { font: inherit; }
.ds-input[type='date'], .ds-input[type='time'], .ds-input[type='datetime-local'], .ds-input[type='month'], .ds-input[type='week'] { display: block; width: 100%; min-width: 0; -webkit-appearance: none; appearance: none; text-align: start; }
.ds-input::-webkit-date-and-time-value { text-align: start; min-height: 1.5em; }
/* The calendar the browser draws takes its colour from the theme's color-scheme; Form / corporate draws it at 70% of the ink in both themes. */
.ds-input::-webkit-calendar-picker-indicator { opacity: 0.7; }
.ds-input::placeholder { color: var(--ds-ink-placeholder); }
/* And the empty text the browser draws ITSELF, which ::placeholder never reaches: the date field's
   dd/mm/yyyy is painted in shadow nodes that inherit this control's own ink, so the one empty date in
   a form shouted at full strength beside every muted placeholder next to it. The Input primitive
   marks a value-less date, time, datetime-local, month or week with data-empty; a date the reader has
   entered drops the mark and comes back to the ink.
   Not while the field has the focus: a half-typed date is no value yet, so the mark is still on it,
   and muting the digits the reader is putting in reads as a field refusing them. The browser's own
   highlight on the segment being edited is what says where they are. */
.ds-input[type='date'][data-empty]:not(:focus), .ds-input[type='time'][data-empty]:not(:focus), .ds-input[type='datetime-local'][data-empty]:not(:focus), .ds-input[type='month'][data-empty]:not(:focus), .ds-input[type='week'][data-empty]:not(:focus) { color: var(--ds-ink-placeholder); }
.ds-input:focus-visible { outline: var(--ds-focus-width) solid var(--ds-focus); outline-offset: var(--ds-focus-offset); }
.ds-field--invalid .ds-input { border-color: var(--ds-error); }
/* The same border wherever the control itself says it is invalid, with or without a Field around it. */
.ds-input[aria-invalid='true'] { border-color: var(--ds-error); }
.ds-input--area { min-height: 140px; padding: 14px; resize: vertical; }
/* ── Select ────────────────────────────────────────────────────────────────────────────────── */

/* The trigger is an .ds-input so it sits in a form row like every other control, plus the chevron
   the master draws: 14×8 at a 1.5 stroke, in the ink rather than the muted placeholder beside it. */
.ds-select__trigger { display: flex; align-items: center; justify-content: space-between; gap: 12px; text-align: start; cursor: pointer; }
.ds-select__trigger > span:first-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ds-select__trigger[data-placeholder] { color: var(--ds-ink-placeholder); }
/* Open shows on the control as well as under it: the border takes the ink, which reads on every
   field the form sits on — the focus blue would vanish on the blue band. */
.ds-select__trigger[data-state='open'] { border-color: var(--ds-ink); }
.ds-select__trigger:disabled { opacity: 0.45; cursor: default; }
.ds-select__chevron { flex: none; display: inline-flex; color: var(--ds-ink); transition: transform var(--ds-fast, 140ms) var(--ds-ease, ease); }
.ds-select__trigger[data-state='open'] .ds-select__chevron { transform: rotate(180deg); }

/* ── Date ──────────────────────────────────────────────────────────────────────────────────── */

/* The Date master draws the field the Select's shape: 52px, the date or dd/mm/yyyy in the body face,
   the master's glyph at the right edge. The trigger is what the reader sees and presses; the native
   control lies under it, full size, so the browser anchors its own picker to the field. It is never
   display:none — a control that is not rendered cannot open its picker. With `data-inline` the
   native control is the field and the rules above for a self-drawn date apply to it instead. */
.ds-date { position: relative; display: block; }
.ds-date__trigger { display: flex; align-items: center; justify-content: space-between; gap: 12px; text-align: start; cursor: pointer; }
.ds-date__value { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ds-date__trigger[data-placeholder] { color: var(--ds-ink-placeholder); }
.ds-date__trigger:disabled { opacity: 0.45; cursor: default; }
/* 70% of the ink on the master, light and dark: the body rung is the ladder's step for it. */
.ds-date__glyph { flex: none; display: inline-flex; color: var(--ds-ink-body); }
.ds-date__native { position: absolute; inset: 0; width: 100%; height: 100%; margin: 0; padding: 0; border: 0; opacity: 0; pointer-events: none; }
/* The native control holds the focus while its picker is up; the field draws the ring the press had. */
.ds-date:has(> .ds-date__native[data-ring]:focus) > .ds-date__trigger { outline: var(--ds-focus-width) solid var(--ds-focus); outline-offset: var(--ds-focus-offset); }

/* The panel carries the scope its trigger sat in (`.ds-product`) and paints nothing itself: the box
   inside it paints the field and restores the ink ladder, because a scope and a ladder restore on one
   element point at each other. The box also holds the master's 4px above and below the rows, outside
   the scrolling list, so the list's edges are row edges in every scroll position.

   The BOX is also where the corner, the rule and the lift are, and the panel is a bare positioner.
   They used to sit on the panel, which paints nothing, and the box's paint reached them only through
   the panel's `overflow: hidden`. Square that was exact; rounded to 6px the clip antialiases, and
   the seam it leaves let the page show through — up the left edge of an open list, that was the
   focused trigger's purple border, drawn in slivers. A rounded edge has to be an edge of the thing
   that paints, not a clip over it. */
.ds-select__panel { --ds-select-row: 39px; --ds-select-chrome: 10px; z-index: 40; min-width: var(--radix-select-trigger-width); max-height: var(--radix-select-content-available-height); }
/* The rung the open panel floats on, named by the panel and painted by the box inside it. The
   corporate Select master fills the panel with the page's own field — dark/base over dark/base — so
   the surface is the default; the product masters float it one rung up and say so below. */
.ds-select__box { display: flex; flex-direction: column; min-height: 0; overflow: hidden; padding-block: 4px; border: 1px solid var(--ds-rule-strong); border-radius: var(--ds-radius-control); background: var(--ds-select-field, var(--ds-surface)); color: var(--ds-ink); box-shadow: var(--ds-shadow-raised); }
/* The list ends on a whole row. Its height is a count of rows — eight at most, fewer when the window
   leaves less — and the chrome the panel spends around it (border, the 4px, the scroll strips) is
   taken off the space before the count is made. A list scrolled away from both of its ends carries
   both strips and has to pay for both: where the window is what caps the panel rather than the
   eight-row count, a height that counted one strip would leave the panel taller than the space Radix
   measured the moment the second appeared, and `overflow: hidden` would cut it. */
.ds-select__panel:has(.ds-select__scroll) { --ds-select-chrome: 34px; }
.ds-select__panel:has(.ds-select__scroll--up):has(.ds-select__scroll--down) { --ds-select-chrome: 58px; }
.ds-select__list { max-height: round(down, min(var(--ds-select-row) * 8, var(--radix-select-content-available-height, 100vh) - var(--ds-select-chrome)), var(--ds-select-row)); overflow-y: auto; }
/* Radix shows each strip only while the list can scroll that way and steps it one row at a time; its
   viewport hides the scrollbar, so the strips are the affordance — and the one above is what tells a
   reader who opened the list on a chosen option that there are options over the edge. */
.ds-select__scroll { display: flex; align-items: center; justify-content: center; height: 24px; color: var(--ds-ink); cursor: default; }
.ds-select__scroll--up svg { transform: rotate(180deg); }
/* A row is the master's: 8/14 padding on the 23px body line, 39 tall. */
.ds-select__option { display: flex; align-items: center; justify-content: space-between; gap: 12px; min-height: var(--ds-select-row); padding: 8px 14px; line-height: 23px; cursor: pointer; outline: none; }
/* Radix moves this attribute with the pointer AND the keyboard, so one rule covers both and the
   list never shows two highlighted rows at once. */
/* One rung off the panel, whichever set is drawing it: field/paper under a white corporate panel, and
   on the product set the same well the master fills the trigger with. The rung is a token rather than
   a second selector because it is the LADDER that differs between the sets, not the rule — and the
   product set had been left on --ds-surface-paper, which is the brand lilac in the light (1.03:1
   against the panel) and the panel's own rung in the dark. */
.ds-select__option[data-highlighted] { background: var(--ds-surface-row-hover); color: var(--ds-ink); }
/* The paper fill is a hover, not a focus indicator (1.09:1). While a key is moving the highlight the
   row draws the ring every other control draws, inside the row so a rounded product panel does not
   clip it; the panel says which hand is on it, because the browser's focus-visible heuristic cannot. */
.ds-select__panel[data-keyboard] .ds-select__option[data-highlighted] { outline: var(--ds-focus-width) solid var(--ds-focus); outline-offset: var(--ds-focus-offset); }
.ds-select__option[data-state='checked'] { font-weight: 700; }
.ds-select__option[data-disabled] { opacity: 0.45; cursor: default; }
.ds-select__tick { flex: none; display: inline-flex; }

.ds-check { display: flex; align-items: flex-start; gap: 10px; cursor: pointer; }
.ds-check__label { color: var(--ds-ink-body); }

/* ── Form ──────────────────────────────────────────────────────────────────────────────────── */

/* The form sits on the band it is given — the canvas paints no panel behind the corporate one and a
   white panel behind the product one. 28px between rows, as the apply masters are set. */
.ds-form { display: flex; flex-direction: column; gap: 28px; max-width: 720px; }
.ds-form--done { gap: 12px; }
/* The regions that take the focus pad themselves clear of the one ring (--ds-focus-inset, theme.css),
   and hand the padding back as margin so the text, the measure and the layout do not move. Content
   box, so the 720 measure is still the text's. */
.ds-form--done, .ds-tabs__panel { box-sizing: content-box; padding: var(--ds-focus-inset); margin: calc(-1 * var(--ds-focus-inset)); }
/* The panel the form becomes once it is sent, as the example masters set it: eyebrow in the accent
   ink, the form's heading, a lede, the actions in a row, a caption. 16 between rows on the mobile
   master, 24 on the desktop one. Two actions sit side by side at 12 and stack full width on a phone,
   because a site can have two next steps — a call to book and a WhatsApp to say hello on. */
.ds-form__done { display: flex; flex-direction: column; gap: 16px; }
@media (min-width: 768px) { .ds-form__done { gap: 24px; } }
.ds-form__eyebrow { margin: 0; color: var(--ds-accent-ink, var(--ds-ink-muted)); }
.ds-form__lede { margin: 0; color: var(--ds-ink); }
.ds-form__note { margin: 0; color: var(--ds-ink-muted); }
/* The actions share one width: the master pairs the consultation-call and WhatsApp buttons at 285
   each, the widest label's, so the pair reads as one row rather than two hugged pills. A grid of
   equal columns sized to fit does that; on a phone the same grid stacks and fills the panel. */
.ds-form__actions { display: grid; grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr); gap: 12px; width: fit-content; max-width: 100%; }
@media (max-width: 767.98px) { .ds-form__actions { grid-auto-flow: row; grid-auto-columns: auto; width: 100%; } }
.ds-form__title { margin: 0; }
.ds-form__intro { margin: 0; color: var(--ds-ink-body); }
/* One field to a row unless a row says otherwise. The master pairs exactly one thing — first name
   and last name — and a form that pairs everything by default puts unrelated fields side by side. */
.ds-form__fields { display: flex; flex-direction: column; gap: 28px; }
.ds-form__row { display: grid; gap: 28px 16px; }
@media (min-width: 600px) { .ds-form__row { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
/* A field that asks for a sentence takes the whole row; a name takes half. */
/* ── Choice cards ──────────────────────────────────────────────────────────────────────────── */

/* A choice too big to be a radio button: a category, a name, and the line that explains it. */
.ds-choice { display: grid; gap: var(--space-xs); }
@media (min-width: 700px) {
  .ds-choice[data-columns='2'] { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .ds-choice[data-columns='3'] { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .ds-choice[data-columns='4'] { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}
/* The card's own field, and a hairline at 30% of the ink rather than the 40% a section takes:
   `Element=Radio card` bounds it with #000 at 30% on paper and white at 20–30% in the dark, and its
   field is one rung up from the page there — dark/raised over dark/base — never level with it. */
.ds-choice__card {
  display: flex; flex-direction: column; align-items: flex-start; gap: 6px;
  padding: 16px; text-align: start; cursor: pointer;
  border: 1px solid color-mix(in srgb, var(--ds-ink) 30%, transparent); border-radius: var(--ds-radius-card);
  background: var(--ds-surface-control); color: var(--ds-ink);
  transition: background-color var(--ds-fast, 140ms) var(--ds-ease, ease), border-color var(--ds-fast, 140ms) var(--ds-ease, ease), color var(--ds-fast, 140ms) var(--ds-ease, ease);
}
@media (hover: hover) { .ds-choice__card:hover:not([data-state='checked']):not(:disabled) { border-color: var(--ds-ink); } }
/* The chosen card takes the selected field under a 2px rule. The second px is an inset ring rather
   than a wider border, so the card measures exactly what it measured unchosen and a row of them
   keeps one height. Colour is not the only signal either: the control carries aria-checked and the
   chip on the title line says the word. */
.ds-choice__card[data-state='checked'] {
  background: var(--ds-selected-surface); color: var(--ds-selected-ink);
  border-color: var(--ds-selected-rule); box-shadow: inset 0 0 0 1px var(--ds-selected-rule);
}
.ds-choice__card:focus-visible { outline: var(--ds-focus-width) solid var(--ds-focus); outline-offset: var(--ds-focus-offset); }
.ds-choice__card:disabled { opacity: 0.45; cursor: default; }
/* Siblings stretch to one height in the grid, and the price block sits at the foot of each, so the
   figures line up across a row whatever the copy above them runs to. */
.ds-choice__card--full { grid-column: 1 / -1; }
/* The title's line, with room at its right for the chip. `Top` in every radio-card master is this
   row: the title left, the chip right, 12 between them. A title that wraps keeps the chip on its
   first line rather than centred against the block. */
.ds-choice__head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; align-self: stretch; }
/* Rendered only in the card that is the answer — Radix's indicator draws no node otherwise — so an
   unchosen title runs the full width of its card the way the example masters set it. */
.ds-choice__mark { flex: none; padding: 4px 8px; border-radius: var(--ds-radius-chip); background: var(--ds-accent); color: var(--ds-ink-on-accent-control); }
.ds-choice--dense .ds-choice__card { gap: 4px; }
.ds-choice--dense .ds-choice__tag { margin-block-start: 2px; }
.ds-choice__eyebrow, .ds-choice__meta, .ds-choice__footnote { color: var(--ds-ink-muted); }
.ds-choice__meta { margin-block-start: -2px; }
.ds-choice__body, .ds-choice__price-note { color: var(--ds-ink-body); }
.ds-choice__tag { color: var(--ds-accent-ink, var(--ds-ink-muted)); }
.ds-choice__pricing { display: flex; flex-direction: column; gap: 4px; margin-block-start: auto; padding-block-start: 6px; }
/* On the selected field every secondary line is the selected ink at 70%, as the master sets its
   body — including the tag, whose accent ink would be the light theme's blue on a black card. The
   price is the ink outright: the card's one accent is the chip. */
.ds-choice__card[data-state='checked'] :is(.ds-choice__eyebrow, .ds-choice__meta, .ds-choice__body, .ds-choice__tag, .ds-choice__price-note, .ds-choice__footnote) { color: inherit; opacity: 0.7; }

/* ── Chip group ────────────────────────────────────────────────────────────────────────────── */

/* A few short choices in a row: 48px options, a 30% hairline, the chosen one filled with the accent. */
.ds-chipgroup { display: grid; gap: 12px; }
@media (min-width: 600px) {
  .ds-chipgroup[data-columns='2'] { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .ds-chipgroup[data-columns='3'] { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .ds-chipgroup[data-columns='4'] { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}
.ds-chipgroup__option {
  display: inline-flex; align-items: center; justify-content: center; min-height: 48px; padding: 0 16px;
  border: 1px solid color-mix(in srgb, currentColor 30%, transparent); border-radius: var(--ds-radius-chip);
  background: none; color: inherit; text-align: center; cursor: pointer;
  transition: background-color var(--ds-fast, 140ms) var(--ds-ease, ease), border-color var(--ds-fast, 140ms) var(--ds-ease, ease), color var(--ds-fast, 140ms) var(--ds-ease, ease);
}
@media (hover: hover) { .ds-chipgroup__option:hover:not([data-state='checked']):not(:disabled) { border-color: currentColor; } }
.ds-chipgroup__option[data-state='checked'] { background: var(--ds-accent); border-color: var(--ds-accent); color: var(--ds-ink-on-accent-control); }
.ds-chipgroup__option:focus-visible { outline: var(--ds-focus-width) solid var(--ds-focus); outline-offset: var(--ds-focus-offset); }
.ds-chipgroup__option:disabled { opacity: 0.45; cursor: default; }
/* Form / product Element=Chip group: hug chips 32 tall with 12 side padding in a wrapping row 8 apart on both axes, never full-width buttons. */
.ds-product .ds-chipgroup { display: flex; flex-wrap: wrap; gap: 8px; }
.ds-product .ds-chipgroup__option { min-height: 32px; padding: 0 12px; }

/* ── File and counted controls ─────────────────────────────────────────────────────────────── */

.ds-file { position: relative; display: flex; flex-wrap: wrap; align-items: center; gap: 16px; }
.ds-file .ds-file__input { position: absolute; inset: 0; width: 100%; height: 100%; margin: 0; padding: 0; opacity: 0; clip-path: none; overflow: hidden; cursor: pointer; z-index: 1; }
.ds-file__button { cursor: pointer; }
.ds-file__name { color: var(--ds-ink-muted); }
.ds-file__input:focus-visible + .ds-file__button { outline: var(--ds-focus-width) solid var(--ds-focus); outline-offset: var(--ds-focus-offset); }
.ds-counted { display: flex; flex-direction: column; gap: 6px; }
.ds-counted__count { color: var(--ds-ink-muted); }

/* ── Phone ─────────────────────────────────────────────────────────────────────────────────── */

/* The dialling code and the number are two answers, so they are two controls. */
.ds-phone { display: flex; gap: 8px; }
.ds-phone__code { flex: 0 0 auto; width: 9rem; }
.ds-phone__number { flex: 1; min-width: 0; }

.ds-form__notice { margin: 0; color: var(--ds-ink-muted); max-width: 62ch; }
.ds-form__consent { margin: 0; color: var(--ds-ink-muted); max-width: 62ch; }
/* The product set says a whole submission failed in one line under the fields — `Element=Form
   error`, the caption in the state colour — where the corporate set draws the card below. */
.ds-form__error { margin: 0; color: var(--ds-error); }
/* The corporate set's `Element=Alert`: the card under the button, on the control's own field inside
   a hairline, with the state colour spent on a 3px rule down its leading edge and nowhere else. The
   rule replaces that edge's hairline rather than sitting inside it, which puts the copy the
   master's 20 from the rule and leaves the other three edges as they are drawn. */
.ds-form__alert {
  display: flex; flex-direction: column; gap: 4px; padding: 16px 20px;
  background: var(--ds-surface-control); color: var(--ds-ink);
  border: 1px solid var(--ds-rule-strong); border-inline-start: 3px solid var(--ds-error);
}
.ds-form__alert-title, .ds-form__alert-body { margin: 0; }
.ds-form__alert-body { color: var(--ds-ink-body); }
/* The form is a flex column, so a button in it stretches to the column unless it is told not to.
   Full width is the phone's rule, not every form's. */
.ds-form > .ds-button { align-self: flex-start; }
@media (max-width: 767.98px) { .ds-form > .ds-button.ds-button--stack-block { align-self: stretch; } }

/* ── Media card ────────────────────────────────────────────────────────────────────────────── */

.ds-media-cards__inner { display: flex; flex-direction: column; gap: var(--ds-heading-gap); }
.ds-media-cards__grid { display: grid; gap: var(--ds-card-gap); }
@media (min-width: 900px) { .ds-media-cards__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

/* The card stands dark in both themes: what the company publishes is its own surface, and the master
   draws it that way on the light page as well as the dark one. 32 inside from the fold up, 20 between
   the rows, as the master measures. The focus ring on this ground is `state/focus-on-dark` — the
   accent; the brand blue measures 2.78:1 against the card and fails 1.4.11. */
.ds-media-card { --ds-focus: var(--ds-accent); display: flex; flex-direction: column; gap: 20px; height: 100%; padding: var(--space-sm); background: var(--ds-surface-standing); color: var(--ds-ink); }
@media (min-width: 768px) { .ds-media-card { padding: var(--space-md); } }
.ds-media-card__top { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
/* The mark sits in the master's 56px black tile, fitted inside it: the tile is the size, not the art,
   so a wide wordmark and a square one read at one weight. Black in both themes — it is the tile the
   marks are drawn for. */
.ds-media-card__mark { display: grid; place-items: center; flex: none; width: 56px; height: 56px; margin-inline-start: auto; background: var(--brand-black); }
.ds-media-card__mark > img, .ds-media-card__mark > svg { display: block; width: auto; height: auto; max-width: 100%; max-height: 100%; align-self: center; justify-self: center; }
.ds-media-card__row { display: flex; gap: 16px; align-items: flex-start; }
/* 80 square, as the master draws the slot; empty, it is the placeholder's 8% white. */
.ds-media-card__cover { flex: none; width: 80px; background: rgb(255 255 255 / 0.08); }
.ds-media-card__body { margin: 0; color: rgb(255 255 255 / 0.85); }
/* The control that loads a third party's player says so on itself, and is a real button: the press
   puts the player in its place. The hover is the live site's — the hairline turns to the accent. */
.ds-media-card__play { display: flex; flex-direction: column; align-items: center; gap: 8px; width: 100%; margin: 0; padding: 12px; border: 1px solid rgb(255 255 255 / 0.3); background: none; color: inherit; font: inherit; text-align: center; cursor: pointer; transition: border-color var(--ds-fast, 140ms) var(--ds-ease, ease); }
@media (hover: hover) { .ds-media-card__play:hover { border-color: var(--ds-accent); } }
/* focus/ring 3, focus/offset 2, in state/focus-on-dark — the values the v3.9 canvas gives this
   control, and the one ring the whole library draws. */
.ds-media-card__play:focus-visible { outline: var(--ds-focus-width) solid var(--ds-focus); outline-offset: var(--ds-focus-offset); }
.ds-media-card__play-label { display: inline-flex; align-items: center; gap: 8px; }
.ds-media-card__play-glyph { width: 0; height: 0; border-inline-start: 8px solid currentColor; border-block-start: 5px solid transparent; border-block-end: 5px solid transparent; }
.ds-media-card__play-note { color: rgb(255 255 255 / 0.55); }
/* The master's 57px spacer and the card gap after it hold the links off the player from 768 up; the 390 master hides it. */
@media (min-width: 768px) { .ds-media-card__player:not(:last-child) { margin-block-end: calc(57px + 20px); } }
/* Loaded: the third party's frame, full width, at the height it asks for, on the slot's own 8% white
   so the card does not read as a hole while somebody else's player is still coming. */
.ds-media-card__frame { display: block; width: 100%; border: 0; background: rgb(255 255 255 / 0.08); }
.ds-media-card__posts { display: flex; flex-direction: column; margin: 0; padding: 0; list-style: none; }
.ds-media-card__post { display: flex; flex-direction: column; gap: 4px; padding-block: 20px; }
/* The master's rule is an inside stroke: it takes its 1px out of the 20 above, not the post's height. */
.ds-media-card__post + .ds-media-card__post { border-block-start: 1px solid rgb(255 255 255 / 0.15); padding-block-start: calc(20px - 1px); }
.ds-media-card__post-link { color: inherit; text-decoration: none; }
.ds-media-card__post-link:focus-visible { outline: var(--ds-focus-width) solid var(--ds-focus); outline-offset: var(--ds-focus-offset); }
@media (hover: hover) { .ds-media-card__post-link:hover { text-decoration: underline; } }
.ds-media-card__post-date { color: rgb(255 255 255 / 0.55); }
.ds-media-card__links { display: flex; flex-wrap: wrap; gap: 8px 20px; margin-block-start: auto; }
/* The master spreads the row across the card from the fold up and packs it left on a phone. */
@media (min-width: 768px) { .ds-media-card__links { justify-content: space-between; } }
/* The one place the accent is spent on this card. */
.ds-media-card__link { color: var(--brand-lime); }

/* ── Values ────────────────────────────────────────────────────────────────────────────────── */

.ds-values { display: flex; flex-direction: column; gap: var(--space-md); }
.ds-values__group { display: flex; flex-direction: column; gap: 10px; }
.ds-values__label { color: var(--ds-ink-muted); }
/* A hairline grid drawn by the gaps, so every cell shares one rule with its neighbour rather than
   each drawing its own and doubling it. */
.ds-values__grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1px; margin: 0; background: var(--ds-rule); border: 1px solid var(--ds-rule); }
/* An odd count stacks on a phone, as the Mobile master stacks ACE: two across would leave a hole. */
@media (max-width: 699.98px) { .ds-values__grid:has(> :nth-child(odd):last-child) { grid-template-columns: minmax(0, 1fr); } }
@media (min-width: 700px) {
  .ds-values__grid { grid-template-columns: repeat(min(3, var(--ds-values-cols, 3)), minmax(0, 1fr)); }
  .ds-values__cell { grid-column-end: span var(--ds-values-span-md, 1); }
}
@media (min-width: 1000px) {
  .ds-values__grid { grid-template-columns: repeat(var(--ds-values-cols, 3), minmax(0, 1fr)); }
  .ds-values__cell { grid-column-end: span var(--ds-values-span, 1); }
}
.ds-values__cell { display: flex; flex-direction: column; gap: 4px; padding: 14px 16px; background: var(--ds-surface); color: var(--ds-ink); }
/* The letter is the accent's job here: it is what makes the acronym legible as one. */
.ds-values__letter { color: var(--ds-accent-ink, var(--ds-accent)); }
.ds-section--brand .ds-values__cell, .ds-section--invert .ds-values__cell { background: transparent; color: inherit; }
.ds-values__word { display: flex; flex-direction: column; gap: 4px; margin: 0; }
.ds-values__body { margin: 2px 0 0; color: var(--ds-ink-body); }
.ds-values__footnote { margin: 0; max-width: 62ch; color: var(--ds-ink-muted); }

/* ── Steps ─────────────────────────────────────────────────────────────────────────────────── */

/* The canvas rules a process with the 12% hairline, never the ink. Rows: each step under its own
   rule, the index in a narrow column beside the copy, the copy at a readable measure while the rule
   runs the full list — and the list can be capped, as the live process lists are at 760. Columns:
   one hairline box with the cards divided by the same line. Each card draws its own top and left
   edges and the list closes the right and bottom; a last row the count does not fill is shared out
   among the cards in it, so the box holds at any count and never frames an open cell. */
.ds-steps { display: grid; margin: 0; padding: 0; list-style: none; }
.ds-steps__item { display: flex; }
.ds-steps__copy { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.ds-steps--rows { grid-template-columns: minmax(0, 1fr); gap: 16px; }
.ds-steps--rows .ds-steps__item { gap: 24px; padding-block: 20px; border-block-start: 1px solid var(--ds-rule); }
.ds-steps--rows .ds-steps__n { flex: none; min-width: 2ch; }
.ds-steps--rows .ds-steps__copy { max-width: 664px; }
.ds-steps--columns { border-inline-end: 1px solid var(--ds-rule); border-block-end: 1px solid var(--ds-rule); }
/* A card is a subgrid of the row's four slots — index, title, body, meta — so every body in the row
   starts on one y whatever its title needed. The 12 between slots is a margin on each slot that
   follows another, not a row gap, so a slot a step does not have leaves no space behind. */
.ds-steps--columns .ds-steps__item { display: grid; grid-row: span 4; grid-template-rows: subgrid; align-content: start; padding: var(--space-md); border-block-start: 1px solid var(--ds-rule); border-inline-start: 1px solid var(--ds-rule); }
.ds-steps--columns .ds-steps__item > * + * { margin-block-start: 12px; }
@media (min-width: 600px) {
  .ds-steps--columns { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  /* The master fixes the title slot at two lines of web/h4, so a row of one-line titles starts its
     bodies where a row with a two-line title would. The mobile master hugs the title. */
  .ds-steps--columns .ds-steps__title { min-block-size: 2lh; }
}
/* Two columns up to 1000 whatever count was asked for; an odd last card takes the row. */
@media (min-width: 600px) and (max-width: 999.98px) { .ds-steps--columns .ds-steps__item:last-child:nth-child(odd) { grid-column: span 2; } }
@media (min-width: 1000px) {
  .ds-steps--rows { gap: 12px; }
  /* The tracks are the smallest count every possible remainder divides — 2, 6, 12, 60 — so a short
     last row shares its width in whole tracks: its first card is the nth-child(Nn+1) that is also
     among the last N − 1, and the cards after it are the rest of that row. */
  .ds-steps--cols-2 .ds-steps__item:last-child:nth-child(odd) { grid-column: span 2; }
  .ds-steps--cols-3 { grid-template-columns: repeat(6, minmax(0, 1fr)); }
  .ds-steps--cols-3 .ds-steps__item { grid-column: span 2; }
  .ds-steps--cols-3 .ds-steps__item:nth-child(3n+1):nth-last-child(1) { grid-column: span 6; }
  .ds-steps--cols-3 .ds-steps__item:nth-child(3n+1):nth-last-child(2), .ds-steps--cols-3 .ds-steps__item:nth-child(3n+1):nth-last-child(2) ~ .ds-steps__item { grid-column: span 3; }
  .ds-steps--cols-4 { grid-template-columns: repeat(12, minmax(0, 1fr)); }
  .ds-steps--cols-4 .ds-steps__item { grid-column: span 3; }
  .ds-steps--cols-4 .ds-steps__item:nth-child(4n+1):nth-last-child(1) { grid-column: span 12; }
  .ds-steps--cols-4 .ds-steps__item:nth-child(4n+1):nth-last-child(2), .ds-steps--cols-4 .ds-steps__item:nth-child(4n+1):nth-last-child(2) ~ .ds-steps__item { grid-column: span 6; }
  .ds-steps--cols-4 .ds-steps__item:nth-child(4n+1):nth-last-child(3), .ds-steps--cols-4 .ds-steps__item:nth-child(4n+1):nth-last-child(3) ~ .ds-steps__item { grid-column: span 4; }
  .ds-steps--cols-5 { grid-template-columns: repeat(60, minmax(0, 1fr)); }
  .ds-steps--cols-5 .ds-steps__item { grid-column: span 12; }
  .ds-steps--cols-5 .ds-steps__item:nth-child(5n+1):nth-last-child(1) { grid-column: span 60; }
  .ds-steps--cols-5 .ds-steps__item:nth-child(5n+1):nth-last-child(2), .ds-steps--cols-5 .ds-steps__item:nth-child(5n+1):nth-last-child(2) ~ .ds-steps__item { grid-column: span 30; }
  .ds-steps--cols-5 .ds-steps__item:nth-child(5n+1):nth-last-child(3), .ds-steps--cols-5 .ds-steps__item:nth-child(5n+1):nth-last-child(3) ~ .ds-steps__item { grid-column: span 20; }
  .ds-steps--cols-5 .ds-steps__item:nth-child(5n+1):nth-last-child(4), .ds-steps--cols-5 .ds-steps__item:nth-child(5n+1):nth-last-child(4) ~ .ds-steps__item { grid-column: span 15; }
}
.ds-steps__footnote { margin: 0; max-width: 70ch; color: var(--ds-ink-muted); }
/* The index is where the accent is spent in a numbered process. */
.ds-steps__n { color: var(--ds-accent-ink, var(--ds-ink-muted)); }
.ds-steps__title, .ds-steps__body, .ds-steps__meta { margin: 0; }
.ds-steps__body { color: var(--ds-ink-body); }
.ds-steps__meta { color: var(--ds-ink-muted); }

/* ── Story band ────────────────────────────────────────────────────────────────────────────── */

.ds-story { position: relative; isolation: isolate; }
.ds-story__picture { position: absolute; inset: 0; z-index: -1; }
.ds-story__picture > :not(.ds-story__scrim) { width: 100%; height: 100%; object-fit: cover; display: block; }
/* The scrim is what makes the copy legible over any photograph, rather than over the one the
   design was drawn with. */
.ds-story__scrim { position: absolute; inset: 0; background: color-mix(in srgb, var(--brand-black) 72%, transparent); }
.ds-story__inner { display: flex; flex-direction: column; gap: var(--space-sm); align-items: flex-start; max-width: 960px; }
.ds-story--center .ds-story__inner { margin-inline: auto; align-items: center; text-align: center; }
.ds-story__title, .ds-story__body { margin: 0; }
/* The masters set the heading FIXED 760 and the paragraph FIXED 560 on Size=Desktop, FILL on Size=Mobile:
   an unmeasured paragraph ran the 960 of the column and lost a line. */
.ds-story__title { max-width: 760px; }
.ds-story__body { max-width: 560px; }

/* ── Rows ──────────────────────────────────────────────────────────────────────────────────── */

/* Three columns, not a flex row that lets each value land wherever its own length puts it: the
   term, the value, and the way to change it, so the values line up down the list and the actions
   line up down the edge. */
.ds-row-item { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: baseline; gap: 8px 24px; padding-block: 16px; border-block-end: 1px solid var(--ds-rule); }
.ds-row-item:not(.ds-row-item--editing):has(.ds-row-item__action) { grid-template-columns: minmax(0, 1fr) auto 5rem; }
/* Long terms and long values share one row: both may break rather than run into each other. */
.ds-row-item__term, .ds-row-item__value { min-width: 0; overflow-wrap: anywhere; }
.ds-row-item__main { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.ds-row-item__chips { display: inline-flex; flex-wrap: wrap; gap: 6px; margin-block-start: 6px; }
/* Each row is changed on its own, so the way to change it sits on the row. */
.ds-row-item__action { justify-self: end; align-self: center; padding: 0; border: 0; background: none; cursor: pointer; }
.ds-settings__eyebrow { color: var(--ds-ink-muted); }
.ds-row-item__hint { color: var(--ds-ink-muted); }
.ds-row-item__value { color: var(--ds-ink-body); text-align: end; }
.ds-row-item__control { display: flex; align-items: center; gap: 12px; }
/* Editing in place: the field takes the value's cell and the two answers take the action's. */
.ds-row-item--editing { grid-template-columns: minmax(0, 1fr) minmax(0, 2fr); align-items: center; }
.ds-row-item__edit { display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: 8px 16px; min-width: 0; }
.ds-row-item__field { flex: 1 1 12rem; min-width: 0; }
.ds-row-item__edit-actions { display: inline-flex; align-items: center; gap: 16px; }
.ds-row-item__action:disabled { opacity: 0.45; cursor: default; }
.ds-row-item__cancel { opacity: 0.7; }
/* A refused save says why on a line of its own under the field, in the state colour — the Form master's error caption. */
.ds-row-item__error { flex-basis: 100%; margin: 0; color: var(--ds-error); }
@media (max-width: 599.98px) {
  /* On a phone the way to change a row sits under it, so term and value keep the width. */
  .ds-row-item:not(.ds-row-item--editing):has(.ds-row-item__action) { grid-template-columns: minmax(0, 1fr) minmax(0, auto); }
  .ds-row-item:not(.ds-row-item--editing) > .ds-row-item__action { grid-column: 1 / -1; justify-self: end; }
  .ds-row-item--editing { grid-template-columns: minmax(0, 1fr); }
  .ds-row-item__edit { justify-content: flex-start; }
  .ds-row-item__field { flex-basis: 100%; }
  /* The message stays against the field; Save and Cancel take the last line. */
  .ds-row-item__edit-actions { order: 1; }
}

.ds-list-row { display: flex; gap: 12px; padding-block: 10px; }
.ds-list-row__icon { flex: none; display: inline-flex; width: var(--ds-icon-14); height: var(--ds-icon-14); margin-block-start: 5px; }
.ds-list-row__copy { display: flex; flex-direction: column; gap: 2px; }
.ds-list-row__body { color: var(--ds-ink-body); }

/* ── Facts ─────────────────────────────────────────────────────────────────────────────────── */

.ds-facts { display: grid; gap: 24px; margin: 0; }
@media (min-width: 600px) { .ds-facts { grid-template-columns: repeat(min(2, var(--ds-facts-cols, 4)), minmax(0, 1fr)); } }
@media (min-width: 1000px) { .ds-facts { grid-template-columns: repeat(var(--ds-facts-cols, 4), minmax(0, 1fr)); } }
/* The icon slot keeps its height whether the card has one or not, so labels line up across a row. */
.ds-facts__item { --ds-facts-icon: 20px; display: flex; flex-direction: column; gap: 12px; padding: 32px; background: var(--ds-surface); color: var(--ds-ink); box-shadow: inset 0 0 0 1px var(--ds-rule); }
.ds-factsblock { display: flex; flex-direction: column; gap: var(--ds-heading-gap); }
.ds-facts__icon { display: inline-flex; width: var(--ds-facts-icon); height: var(--ds-facts-icon); min-height: 20px; margin-block-end: 4px; color: var(--ds-accent-ink, var(--ds-ink)); }
.ds-facts__label { color: var(--ds-ink-muted); }
.ds-facts__value { margin: 0; }
.ds-facts__body { margin: 0; color: var(--ds-ink-body); }

/* ── Case study ────────────────────────────────────────────────────────────────────────────── */

.ds-case { display: flex; flex-direction: column; gap: 12px; height: 100%; padding: 24px; background: var(--ds-surface); color: var(--ds-ink); box-shadow: inset 0 0 0 1px var(--ds-rule); }
.ds-case__media { aspect-ratio: 16 / 9; }
/* Adjacent cards sharing one hairline: the grid paints the rule through 1px gaps and the cards, which
   paint their own surface, cover the rest. Alone, a card keeps its own hairline. */
.ds-case-grid { display: flex; flex-direction: column; gap: var(--ds-heading-gap); }
.ds-case-grid .ds-grid { gap: 1px; padding: 1px; background: var(--ds-rule); }
.ds-case-grid .ds-case { box-shadow: none; }
/* A case study in a grid is a subgrid of the row's slots — media, chips, index, title, body, tools,
   link — so a title that runs to one line beside a sibling's two takes one line's height and the
   bodies below it open on the same line. The reservation this replaces was a fixed 2.5em on the
   title: it held the row level, and it held it by leaving 60px of nothing under every single-line
   title in the estate's own work grid. The card spans as many rows as the fullest study in the grid
   fills, and the copy wrapper hands its children straight through to those shared rows. */
.ds-case-grid .ds-case,
.ds-grid > .ds-case { display: grid; grid-template-rows: subgrid; grid-row: span var(--ds-case-rows, 7); gap: 12px; height: auto; align-content: start; }
.ds-case-grid .ds-grid > .ds-reveal { grid-template-rows: subgrid; grid-row: span var(--ds-case-rows, 7); }
.ds-case-grid .ds-case__copy,
.ds-grid > .ds-case > .ds-case__copy { display: var(--ds-case-copy, contents); grid-row: var(--ds-case-side, auto); }
/* A picture in a shared row keeps its own ratio and never takes its height from the row. A box sized
   by `aspect-ratio` that is also asked to stretch has two heights at once, and the two engines
   settle that differently: WebKit sizes the shared row from the stretched height, leaves the row
   shorter than the ratio, and the picture then spills into the rows below it — on the estate's work
   grid the status chips landed inside the screenshot and the client's name sat on its bottom edge,
   at every width, while Chromium reconciled the same pair the other way and hid the fault. Pinning
   the picture to the start of its row leaves the ratio as its only height, and both engines then
   size every row of the card to the same pixel. */
.ds-case-grid .ds-case > .ds-case__media,
.ds-grid > .ds-case > .ds-case__media { align-self: start; grid-row: var(--ds-case-side, auto); }
/* The tools and the link close the card. In the shared rows they are rows of their own, so the auto
   margin that pushed them down in a flex column would split the free space and float them; the row
   they sit in does the pinning now. */
.ds-case-grid .ds-case__tools,
.ds-grid > .ds-case .ds-case__tools { margin-block-start: var(--ds-case-push, 0); align-self: var(--ds-case-tools-align, end); }
/* The study that spans the rest of a short last row keeps its picture at one column's width, the
   size of every picture above it, and sets its copy beside it as one column down the shared rows.
   49px is the 1px rule plus both 24px paddings, so the copy starts on the next column's content edge. */
@media (min-width: 600px) and (max-width: 999.98px) {
  .ds-case-grid .ds-grid[data-fill]:not([data-cols='1']) > :nth-child(odd):last-child > .ds-case { grid-template-columns: calc((100% - (var(--ds-fill-span) - 1) * 49px) / var(--ds-fill-span)) minmax(0, 1fr); column-gap: 49px; --ds-case-side: 1 / -1; --ds-case-copy: flex; --ds-case-push: auto; --ds-case-tools-align: stretch; }
}
@media (min-width: 1000px) {
  .ds-case-grid .ds-grid[data-fill][data-cols='2'] > :not(:nth-child(2n)):last-child > .ds-case,
  .ds-case-grid .ds-grid[data-fill][data-cols='3'] > :not(:nth-child(3n)):last-child > .ds-case,
  .ds-case-grid .ds-grid[data-fill][data-cols='4'] > :not(:nth-child(4n)):last-child > .ds-case,
  .ds-case-grid .ds-grid[data-fill][data-cols='5'] > :not(:nth-child(5n)):last-child > .ds-case,
  .ds-case-grid .ds-grid[data-fill][data-cols='6'] > :not(:nth-child(6n)):last-child > .ds-case { grid-template-columns: calc((100% - (var(--ds-fill-span) - 1) * 49px) / var(--ds-fill-span)) minmax(0, 1fr); column-gap: 49px; --ds-case-side: 1 / -1; --ds-case-copy: flex; --ds-case-push: auto; --ds-case-tools-align: stretch; }
}
/* A case study with a way through tints under the pointer and while that link has focus; the shot's
   scale is motion.css's. In a grid the tint is what keeps the shared hairline bed covered, which is
   why the token is opaque. */
@media (hover: hover) {
  .ds-case:has(.ds-case__link):hover { background: var(--ds-surface-hover); }
}
.ds-case:has(.ds-case__link):focus-within { background: var(--ds-surface-hover); }
.ds-case:has(.ds-case__link):active { background: var(--ds-surface-active); }
.ds-case__status.ds-chip--outline { background: none; color: inherit; }
.ds-case__chips { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.ds-case__copy { display: flex; flex-direction: column; gap: 12px; flex: 1; }
/* The project's own name is the accented line; the sector beside the status chip is a quiet one. */
.ds-case__index { color: var(--ds-accent-ink, var(--ds-ink)); }
.ds-case__sector { color: var(--ds-ink-muted); }
.ds-case__title { margin: 0; }
.ds-case__body { margin: 0; color: var(--ds-ink-body); }
/* The tools and the link close the card, so they sit at its foot and line up across the row. */
.ds-case__tools { display: flex; flex-wrap: wrap; gap: 8px 16px; margin: 0; margin-block-start: auto; padding: 12px 0 0; list-style: none; }
.ds-case__tool { display: inline-flex; align-items: center; gap: 8px; }
.ds-case__tool-mark { display: inline-flex; flex: none; width: 20px; height: 20px; }
.ds-case__tool-mark > *, .ds-panel__tool-mark > * { width: 100%; height: 100%; }
.ds-case__link { margin-block-start: 0; }

/* ── Comparison ────────────────────────────────────────────────────────────────────────────── */

/* Narrow viewports scroll the table sideways inside its own box rather than squashing the columns
   to nothing; the page itself never scrolls. */
.ds-compare { display: flex; flex-direction: column; gap: var(--space-lg); }
.ds-compare__title { margin: 0; max-width: 22ch; }
.ds-compare__intro { margin: 0; max-width: 60ch; color: var(--ds-ink-body); }
/* Positioned so the hidden twins in its cells can never widen the page; focusable so a keyboard
   reaches the columns a phone hides. */
.ds-compare__scroll { position: relative; width: 100%; max-width: 900px; margin-inline: auto; overflow-x: auto; -webkit-overflow-scrolling: touch; }
.ds-compare__scroll:focus-visible { outline: var(--ds-focus-width) solid var(--ds-focus); outline-offset: var(--ds-focus-offset); }
.ds-compare__table { width: 100%; min-width: 560px; border-collapse: collapse; text-align: start; }
.ds-compare__caption { padding-block-end: 12px; color: var(--ds-ink-muted); text-align: start; }
.ds-compare__head { padding: 16px 20px; text-align: start; color: var(--ds-ink-muted); border-block-end: 1px solid var(--ds-rule-strong); vertical-align: bottom; }
/* The data columns centre their marks, so their headers centre too; the leading column keeps start. */
.ds-compare__head + .ds-compare__head { text-align: center; }
.ds-compare__row-label { width: 26%; padding: 36px 20px; text-align: start; font-weight: inherit; color: var(--ds-ink-body); border-block-end: 1px solid var(--ds-rule); }
.ds-compare__cell { padding: 36px 20px; text-align: center; border-block-end: 1px solid var(--ds-rule); }
/* The site's own column is set apart by a field, not by a colour: the comparison has to keep
   reading as a comparison. */
/* The site's own column is the one being argued for, so it carries the accent — the header in it
   outright, the cells in a tint of it. That is where the accent is spent on this surface. */
.ds-compare__head.ds-compare__col--own { background: var(--ds-accent); color: var(--ds-ink-on-accent-control); }
.ds-compare__cell.ds-compare__col--own { background: color-mix(in srgb, var(--ds-accent) 35%, transparent); }

/* A phone gets the Size=Mobile master rather than a sideways scroll: the column headers once across
   the top, then every row as its own block — the label on a line of its own, the answers under it in
   the same columns the headers stand in, 8 between them and 20 above and below, which is the 126 the
   canvas draws. Nothing leaves the screen, so the box has nothing left to scroll; the table stays a
   table, so a screen reader still announces the row and the column. The leading column's header
   names the row labels, which now stand on their own, so it keeps its place in the table and leaves
   the layout. */
@media (max-width: 599.98px) {
  .ds-compare__scroll { overflow-x: visible; }
  .ds-compare__table { display: block; min-width: 0; }
  .ds-compare__caption { display: block; }
  .ds-compare__table > thead, .ds-compare__table > tbody { display: block; }
  .ds-compare__table > thead > tr, .ds-compare__table > tbody > tr { display: flex; flex-wrap: wrap; gap: 8px; }
  .ds-compare__table > thead > tr { border-block-end: 1px solid var(--ds-rule-strong); }
  .ds-compare__table > tbody > tr { padding-block: 20px; border-block-end: 1px solid var(--ds-rule); }
  .ds-compare__head:first-child {
    position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
    overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0;
  }
  .ds-compare__head { flex: 1 1 0; min-width: 0; padding: 16px 8px; text-align: center; border-block-end: 0; }
  .ds-compare__row-label { flex: 0 0 100%; width: auto; padding: 0 8px; border-block-end: 0; }
  .ds-compare__cell { flex: 1 1 0; min-width: 0; padding: 16px 8px; border-block-end: 0; }
}

/* ── Signal band ───────────────────────────────────────────────────────────────────────────── */

/* Equal cells across the full width with a hairline between and 32px of air in each: the band is 77
   tall on the canvas and the statements sit in thirds, not in a centred cluster. */
.ds-signal { padding-block: 0; --ds-band-top: 0px; }
.ds-signal > .ds-container { max-width: none; padding-inline: 0; }
.ds-signal__list { display: grid; grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr); margin: 0; padding: 0; list-style: none; }
.ds-signal__item { display: flex; align-items: center; justify-content: center; padding: 32px; text-align: center; }
.ds-signal__item + .ds-signal__item { border-inline-start: 1px solid color-mix(in srgb, currentColor 15%, transparent); }
@media (max-width: 767.98px) {
  .ds-signal__list { grid-auto-flow: row; }
  .ds-signal__item + .ds-signal__item { border-inline-start: 0; border-block-start: 1px solid color-mix(in srgb, currentColor 15%, transparent); }
}

/* ── Programme ─────────────────────────────────────────────────────────────────────────────── */

/* The Top row: copy over the picture 24 apart on a phone, side by side 40 apart from the tablet. */
.ds-programme__inner { display: grid; gap: var(--space-sm); }
/* Two columns from the tablet width, which is where the live tiers turn (`md:grid md:grid-cols-12`
   on cueta) and therefore where the picture can take a side at all. Below it the band is one column
   and the copy always leads.
   The copy column is the canvas's 560 at the design width (48% of the 1180 measure lands on it) and
   never narrower than the heading's longest word: the desktop face is 65px, "Engineering" wants
   320px, and half of a 768 measure is 302. The picture takes what is left, which is the canvas's
   580 at 1440. */
@media (min-width: 768px) {
  .ds-programme__inner--picture { grid-template-columns: clamp(336px, 48%, 560px) minmax(0, 1fr); gap: 40px; align-items: start; }
  /* The mirrored band draws the picture first. Only the drawing order moves: the copy stays first in
     the DOM, so reading order and the picture's alt text still follow the programme. */
  .ds-programme__inner--mirrored { grid-template-columns: minmax(0, 1fr) clamp(336px, 48%, 560px); }
  .ds-programme__inner--mirrored > .ds-programme__picture { order: -1; }
}
.ds-programme__copy { position: relative; display: flex; flex-direction: column; gap: 16px; min-width: 0; }
/* The ghost number is decoration: it sits behind the copy and never takes space from it. */
/* Behind the copy and out of its way: it sits in the margin the copy does not use, and disappears
   entirely when there is no margin to sit in. A decoration that lands on top of a word is worse
   than no decoration. */
/* The number sits in a column of its own rather than over the copy. Overlapping type is not a
   design decision the system makes, and an absolute layer over a paragraph is one bad line break
   away from being exactly that. */
.ds-programme__eyebrow { width: max-content; max-width: 100%; margin: 0; color: var(--ds-ink-muted); }
/* Each line of a listed eyebrow is a paragraph of the master's, spaced 12 below the one above. */
.ds-programme__eyebrow-line { display: block; }
.ds-programme__eyebrow-line + .ds-programme__eyebrow-line { margin-block-start: 12px; }
.ds-programme__title, .ds-programme__blurb, .ds-programme__meta { margin: 0; }
/* The master sets the blurb and the pairs FIXED 520 in the 560 copy column, and FILL on a phone. */
.ds-programme__blurb { color: var(--ds-ink-body); max-width: 520px; }
.ds-programme__pairs { display: grid; gap: 16px; max-width: 520px; margin: 0; }
.ds-programme__pair-label { color: var(--ds-ink-muted); }
.ds-programme__pair-value { margin: 6px 0 0; color: var(--ds-ink-body); }
.ds-programme__group { display: flex; flex-direction: column; gap: 12px; }
.ds-programme__group-label { margin: 0; color: var(--ds-ink-muted); }
.ds-programme__meta { color: var(--ds-ink-muted); }
.ds-programme__cta { align-self: flex-start; }
/* The image slot is FIXED 420 tall on both masters — 580 wide beside the copy, 342 under it — so
   the picture is a height, not an aspect: at 342 an aspect drew it 248. */
.ds-programme__picture { width: 100%; height: 420px; aspect-ratio: auto; }

/* The band runs as one column of parts under the programme itself, at the section's own rhythm. */
.ds-programme > .ds-container { display: flex; flex-direction: column; gap: var(--ds-heading-gap); }
/* The band's own callout is the outlined one at the band's measure; everything about its ink is the
   Kind=Outline rule's, because a box that paints nothing is not this band's special case. */
.ds-programme__callout { width: 100%; max-width: 720px; align-self: flex-start; }
/* Lime is the accent ink on every dark field, but on the brand blue a 13px lime line measures
   2.4:1. White carries the label there — the tone the live tier table names and the master fills. */
.ds-section--brand .ds-callout--outline .ds-callout__label { color: rgb(255 255 255 / 0.85); }
.ds-programme__courses { display: flex; flex-direction: column; gap: 10px; }
/* The master's Chip row is FIXED 885 wide in the Skills group, so a seventh chip wraps as it does
   there. The canvas rounds each chip to a whole pixel and tier 1's six sum to exactly 885; the
   browser's run a fraction wider, so the measure carries the 3px parity tolerance. */
.ds-programme__chips { display: flex; flex-wrap: wrap; gap: 8px; max-width: 888px; }
/* A tool is known by its mark, so the name never stands alone when a mark is given. */
/* The tool row wraps 28 across and 12 down wide; on a phone the master stacks it, one tool a row, 24 apart. */
.ds-programme__tools { display: flex; flex-wrap: wrap; gap: 12px 28px; margin: 0; padding: 0; list-style: none; }
@media (max-width: 767.98px) { .ds-programme__tools { flex-direction: column; flex-wrap: nowrap; gap: 24px; } }
.ds-programme__tool { display: flex; align-items: center; gap: 8px; }
.ds-programme__tool-mark { display: inline-flex; flex: none; width: 20px; height: 20px; }
.ds-programme__tool-mark > * { width: 100%; height: 100%; }
.ds-programme__tool-name { color: var(--ds-ink-body); }
/* The master's top stroke sits inside its 24 of padding, so the rule and the padding add to 24. */
.ds-programme__mentors { display: flex; flex-direction: column; gap: 12px; align-items: flex-start; width: 100%; padding-block-start: 23px; border-block-start: 1px solid color-mix(in srgb, currentColor 20%, transparent); }
@media (min-width: 768px) { .ds-programme__mentors { flex-direction: row; justify-content: space-between; align-items: center; } }

/* ── Accordion (Radix) ─────────────────────────────────────────────────────────────────────── */

.ds-accordion__header { margin: 0; }
/* A row is 88 with its rule, as the masters draw it: the stroke sits inside the Row frame, so the
   button leaves the rule's pixel out of its own height and its bottom padding. */
.ds-accordion__q { display: flex; align-items: center; gap: 16px; width: 100%; min-height: 87px; padding: 20px 0 19px; border: 0; background: none; color: inherit; text-align: start; cursor: pointer; }
.ds-accordion__q:focus-visible { outline: var(--ds-focus-width) solid var(--ds-focus); outline-offset: var(--ds-focus-offset); }
.ds-accordion__question { min-width: 0; }
/* A plus that turns 45° into a cross as the row opens, which is what the masters draw and what the
   live rows draw — not a chevron, and not a minus. Two rules rather than a glyph, so it carries no
   font and no language. It rests at muted ink and takes the accent ink under the pointer or the
   ring: brand blue on paper, lime on a blue or black field, exactly as the live glyph does. */
.ds-accordion__toggle { position: relative; flex: none; margin-inline-start: auto; width: 14px; height: 14px; color: var(--ds-ink-muted); transition: transform var(--ds-fast, 140ms) var(--ds-ease, ease), color var(--ds-fast, 140ms) var(--ds-ease, ease); }
.ds-accordion__toggle::before, .ds-accordion__toggle::after { content: ''; position: absolute; inset-inline: 0; inset-block-start: 50%; height: 2px; margin-block-start: -1px; background: currentColor; }
.ds-accordion__toggle::after { transform: rotate(90deg); }
.ds-accordion__q:focus-visible .ds-accordion__toggle { color: var(--ds-accent-ink); }
/* Open turns the plus 45° into a cross; it does not drop a bar to leave a minus. Both masters draw
   the cross now, and cueta.cuesoft.io rotates one `+` for its FAQ and its course rows alike. */
.ds-accordion__q[data-state='open'] .ds-accordion__toggle { transform: rotate(45deg); }
@media (hover: hover) { .ds-accordion__q:hover .ds-accordion__toggle { color: var(--ds-accent-ink); } }
/* Radix measures the panel and publishes its height, so it can open and close without a fixed one. */
.ds-accordion__panel { overflow: hidden; }
@media (prefers-reduced-motion: no-preference) {
  .ds-accordion__panel[data-state='open'] { animation: ds-accordion-open 200ms ease-out; }
  .ds-accordion__panel[data-state='closed'] { animation: ds-accordion-close 160ms ease-in; }
}
@keyframes ds-accordion-open { from { height: 0; } to { height: var(--radix-accordion-content-height); } }
@keyframes ds-accordion-close { from { height: var(--radix-accordion-content-height); } to { height: 0; } }

/* ── Tabs (Radix) ──────────────────────────────────────────────────────────────────────────── */

.ds-tabs { display: flex; flex-direction: column; gap: var(--space-sm); }
/* A ruled strip with the chosen tab filled in the accent, which is how the master marks it — an
   underline would be a second way of saying selected in a system that already has one. */
.ds-tabs__list { display: flex; flex-wrap: wrap; gap: 4px; padding: 6px; border: 1px solid var(--ds-rule); }
.ds-tabs__tab { padding: 8px 14px; border: 0; background: none; color: var(--ds-ink-muted); cursor: pointer; transition: background-color var(--ds-fast, 140ms) var(--ds-ease, ease), color var(--ds-fast, 140ms) var(--ds-ease, ease); }
@media (hover: hover) { .ds-tabs__tab:hover[data-state='inactive'] { background: var(--ds-surface-paper); color: var(--ds-ink); } }
.ds-tabs__tab[data-state='active'] { background: var(--ds-accent); color: var(--ds-ink-on-accent-control); }
.ds-tabs__tab:focus-visible { outline: var(--ds-focus-width) solid var(--ds-focus); outline-offset: var(--ds-focus-offset); }
/* Radix gives the panel tabindex="0": it is a tab stop, so it rings like one. */
.ds-tabs__panel:focus-visible { outline: var(--ds-focus-width) solid var(--ds-focus); outline-offset: var(--ds-focus-offset); }

/* ── Showcase panel ────────────────────────────────────────────────────────────────────────── */

.ds-showcase-tabs { display: flex; flex-direction: column; gap: var(--ds-heading-gap); }

/* The panel says the same things in the same order for every product, which is what makes three
   products one thing to learn rather than three. */
/* Stacked, the two halves meet on a hairline, the way the Size=Mobile master draws them — copy, one
   1px divider, showcase, gap 0, 24 padding either side — and a gap between them instead reads as two
   panels and leaves a hole between the links and the framed screen. Side by side the master's
   padding is 32 and the same rule turns on its side. */
.ds-panel { display: grid; gap: 0; border: 1px solid var(--ds-rule); }
@media (min-width: 1000px) { .ds-panel { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); } }
.ds-panel__copy { display: flex; flex-direction: column; gap: 14px; min-width: 0; padding: var(--space-sm); }
.ds-panel__aside { display: flex; flex-direction: column; gap: 16px; min-width: 0; padding: var(--space-sm); border-block-start: 1px solid var(--ds-rule); }
@media (min-width: 1000px) {
  .ds-panel__copy, .ds-panel__aside { padding: var(--space-md); }
  .ds-panel__aside { border-block-start: 0; border-inline-start: 1px solid var(--ds-rule); }
}
.ds-panel__eyebrow, .ds-panel__label { color: var(--ds-ink-muted); }
.ds-panel__title { margin: 0; max-width: 20ch; }
.ds-panel__block { display: flex; flex-direction: column; gap: 4px; }
.ds-panel__body, .ds-panel__note { margin: 0; color: var(--ds-ink-body); }
.ds-panel__tools { display: flex; flex-wrap: wrap; gap: 8px 16px; margin: 4px 0 0; padding: 0; list-style: none; }
.ds-panel__tool { display: inline-flex; align-items: center; gap: 8px; color: var(--ds-ink-body); }
.ds-panel__tool-mark { display: inline-flex; flex: none; width: 20px; height: 20px; }
.ds-panel__links { display: flex; flex-wrap: wrap; gap: 16px; margin-block-start: auto; padding-block-start: 6px; }
/* The master accents 'link 1' only — the way onward into the product — and leaves 'link 2' in the
   ink (`copy/links/link 2`, brand/black on light and brand/white on dark), which is how live cuelabs
   draws 'Read the code'. One accent to a panel: two of them is two ways onward. */
.ds-panel__links > .ds-link--arrow ~ .ds-link--arrow { color: var(--ds-ink); }
.ds-panel__list-block { display: flex; flex-direction: column; gap: 8px; }
.ds-panel__list { display: flex; flex-direction: column; gap: 0; margin: 0; padding: 0; list-style: none; }
/* Each item is ruled off from the one above it rather than bulleted: the list is a set of scoped
   issues, and a bullet would make them read as prose. */
.ds-panel__list-item { padding: 10px 0 10px 14px; border-inline-start: 2px solid var(--ds-rule); }
.ds-panel__list-item + .ds-panel__list-item { border-block-start: 1px solid var(--ds-rule); }

/* ── Checkbox and switch (Radix) ───────────────────────────────────────────────────────────── */

.ds-check__box {
  flex: none; display: inline-flex; align-items: center; justify-content: center;
  width: 20px; height: 20px; margin-block-start: 2px; padding: 0;
  border: 1px solid var(--ds-rule-strong); border-radius: var(--ds-radius-thumb); background: var(--ds-surface); color: var(--ds-ink); cursor: pointer;
}
.ds-check__box[data-state='checked'] { background: var(--ds-ink); color: var(--ds-surface); border-color: var(--ds-ink); }
.ds-check__box:focus-visible { outline: var(--ds-focus-width) solid var(--ds-focus); outline-offset: var(--ds-focus-offset); }
.ds-check__tick { display: inline-flex; }

/* Figma Toggle row (1213:6753): 44×24 track at the chip radius, 20px knob inset 2px, no border.
   On = accent track + the ink that sits on the accent (black in both themes). Off = strong rule
   track + page-coloured knob. The knob moves by its logical inset so RTL slides the other way. */
.ds-switch { display: flex; align-items: center; gap: 12px; cursor: pointer; }
.ds-switch:has(.ds-switch__track:disabled) { cursor: default; }
.ds-switch__track { position: relative; flex: none; width: 44px; height: 24px; padding: 0; border: 0; border-radius: var(--ds-radius-chip); background: var(--ds-rule-strong); color: var(--ds-ink); cursor: pointer; }
.ds-switch__track[data-state='checked'] { background: var(--ds-accent); color: var(--ds-ink-on-accent-control); }
.ds-switch__track:focus-visible { outline: var(--ds-focus-width) solid var(--ds-focus); outline-offset: var(--ds-focus-offset); }
.ds-switch__track:disabled { opacity: 0.45; cursor: default; }
.ds-switch__thumb { position: absolute; inset-block-start: 2px; inset-inline-start: 2px; display: block; width: 20px; height: 20px; border-radius: var(--ds-radius-chip); background: var(--ds-surface); color: var(--ds-ink); }
.ds-switch__track[data-state='checked'] .ds-switch__thumb { inset-inline-start: 22px; background: var(--ds-ink-on-accent-control); color: var(--ds-accent); }

/* ── Products ──────────────────────────────────────────────────────────────────────────────── */

/* The card's slots are rows of the grid it sits in, so a tagline that wraps in one card moves every
   card's body together. The same treatment as the tour cards, for the same reason. */
.ds-products { display: grid; row-gap: 10px; column-gap: var(--ds-card-gap); }
@media (min-width: 600px) { .ds-products { grid-template-columns: repeat(min(2, var(--ds-products-cols, 3)), minmax(0, 1fr)); } }
@media (min-width: 1000px) { .ds-products { grid-template-columns: repeat(var(--ds-products-cols, 3), minmax(0, 1fr)); } }
.ds-products__item { display: grid; grid-row: span 6; grid-template-rows: subgrid; gap: 10px; padding: var(--space-md); box-shadow: inset 0 0 0 1px var(--ds-rule); }
/* The tile paints no field of its own, so its states are the band's pair — white mixed into the
   blue on the brand band, the theme's ink into the page elsewhere. */
@media (hover: hover) {
  .ds-products__item:has(.ds-products__link):hover { background: var(--ds-surface-hover); box-shadow: inset 0 0 0 1px var(--ds-rule-strong); }
}
.ds-products__item:has(.ds-products__link):focus-within { background: var(--ds-surface-hover); box-shadow: inset 0 0 0 1px var(--ds-rule-strong); }
.ds-products__item:has(.ds-products__link):active { background: var(--ds-surface-active); }
.ds-products__top { grid-row: 1; display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.ds-products__mark { display: flex; align-items: center; height: 22px; }
.ds-products__mark > :where(img, svg) { display: block; height: 100%; width: auto; }
.ds-products__status { color: var(--ds-ink-muted); }
.ds-products__name { grid-row: 2; margin: 0; }
.ds-products__tagline { grid-row: 3; margin: -4px 0 0; color: var(--ds-accent-ink, var(--ds-ink-muted)); }
.ds-products__body { grid-row: 4; margin: 0; color: var(--ds-ink-body); }
.ds-products__tags { grid-row: 5; display: flex; flex-wrap: wrap; gap: 6px 14px; margin-block-start: 2px; }
.ds-products__tag { color: var(--ds-ink-muted); }
/* A closing line with nowhere to go (`ds-products__end`) holds the link's row but not its class: the
   tile's states hang off `.ds-products__link`, and a card with nothing to follow has none. */
.ds-products__link, .ds-products__end { grid-row: 6; align-self: end; padding-block-start: 12px; }
.ds-products__end { margin: 0; }
@media (max-width: 599px) {
  .ds-products { display: flex; flex-direction: column; gap: var(--ds-card-gap); }
  .ds-products__item { display: flex; flex-direction: column; }
  /* In the column the grid's `align-self: end` acts on the cross axis and pushes the line to the right
     edge; it stays on the left like every other line of the card, as the showcase's link does. */
  .ds-products__link, .ds-products__end { margin-block-start: auto; align-self: start; }
}

/* ── Article ───────────────────────────────────────────────────────────────────────────────── */

.ds-article { display: flex; flex-direction: column; }
/* The study's own band: copy on the left at 600, the picture beside it at 540 × 16:9. */
@media (min-width: 768px) { .ds-article__hero { padding-block: 96px; --ds-band-top: 96px; } }
.ds-article__hero-inner { display: grid; gap: 40px; }
@media (min-width: 1000px) { .ds-article__hero-inner { grid-template-columns: minmax(0, 600px) minmax(0, 540px); align-items: center; } }
.ds-article__hero-copy { display: flex; flex-direction: column; align-items: flex-start; gap: 20px; }
.ds-article__back { align-self: flex-start; }
.ds-article__status { color: var(--ds-accent-ink, var(--ds-accent)); }
.ds-article__programme { color: var(--ds-ink-muted); }
.ds-article__eyebrow { color: var(--ds-ink-muted); }
.ds-article__title, .ds-article__lede { margin: 0; }
.ds-article__lede { max-width: 60ch; color: var(--ds-ink-body); }
.ds-article__hero-meta { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 16px 40px; }
.ds-article__pair { display: flex; flex-direction: column; gap: 6px; }
.ds-article__pair-label { color: var(--ds-ink-muted); }
.ds-article__media { aspect-ratio: 16 / 9; }
.ds-article__body { display: grid; gap: var(--space-lg); padding-block: var(--space-xl); }
@media (min-width: 1000px) { .ds-article__body { grid-template-columns: minmax(0, 220px) minmax(0, 1fr); gap: 64px; align-items: start; } }
.ds-article__side { display: flex; flex-direction: column; gap: var(--space-md); }
.ds-article__marker { display: flex; flex-direction: column; gap: 4px; padding-inline-start: 16px; border-inline-start: 1px solid var(--ds-rule); }
.ds-article__marker-label { color: var(--ds-ink-muted); }
.ds-article__facts { display: grid; gap: 16px; margin: 0; }
@media (min-width: 600px) and (max-width: 999.98px) { .ds-article__facts { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.ds-article__fact-label { color: var(--ds-ink-muted); }
.ds-article__fact-value { margin: 4px 0 0; }
/* The facts stay beside the prose as it scrolls: the reader is checking them against what they are
   reading, not reading them once at the top. */
@media (min-width: 1000px) { .ds-article__side { position: sticky; top: calc(var(--ds-nav-pinned, 0px) + var(--space-md)); } }

.ds-article__write-up { display: flex; flex-direction: column; gap: var(--space-lg); min-width: 0; }
/* The sentence the study turns on, pulled out of the prose by a rule rather than by quotation. */
.ds-article__statement { margin: 0; }
.ds-article__block-title { margin: 0; max-width: 760px; }
.ds-article__card-n, .ds-article__capability-n { display: block; margin-block-end: 8px; color: var(--ds-accent-ink, var(--ds-ink-muted)); }
/* Index and text are two columns, as on the master: a wrapped line starts under the text, never under the index. */
.ds-article__capability-n { margin: 0; }
.ds-article__block { display: flex; flex-direction: column; gap: 12px; }
.ds-article__block-eyebrow { color: var(--ds-ink-muted); }
.ds-article__rows { display: flex; flex-direction: column; margin: 0; padding: 0; list-style: none; }
.ds-article__row { display: flex; gap: 16px; padding-block: 16px; border-block-start: 1px solid var(--ds-rule); }
.ds-article__row-n { flex: none; padding-block-start: 3px; color: var(--ds-ink-muted); }
.ds-article__row-copy { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.ds-article__row-title, .ds-article__row-body, .ds-article__card-title, .ds-article__card-body { margin: 0; }
.ds-article__row-body, .ds-article__card-body { color: var(--ds-ink-body); }
/* White cards sharing one hairline: each card rings itself into the 1px gaps and the grid rules its
   own edge. Two across from 700px, where an odd count's last card spans both columns, so the table
   never frames an empty cell; below that every card is a row of its own. */
.ds-article__cards { display: grid; gap: 1px; padding: 1px; background: var(--ds-surface); color: var(--ds-ink); box-shadow: inset 0 0 0 1px var(--ds-rule); }
.ds-article__card { display: flex; flex-direction: column; gap: 6px; padding: var(--space-sm); background: var(--ds-surface); color: var(--ds-ink); box-shadow: 0 0 0 1px var(--ds-rule); }
@media (min-width: 700px) {
  .ds-article__cards { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .ds-article__card:nth-child(odd):last-child { grid-column-end: span 2; }
}
/* The capabilities are the same table at a smaller cell: three across from 600px, where the last
   capability spans the columns its row leaves; one per row below that. */
.ds-article__capabilities { display: grid; gap: 1px; margin: 0; padding: 0; list-style: none; background: var(--ds-rule); border: 1px solid var(--ds-rule); }
.ds-article__capability { display: grid; grid-template-columns: auto minmax(0, 1fr); column-gap: 12px; align-items: center; padding: 12px 14px; background: var(--ds-surface); color: var(--ds-ink); }
@media (min-width: 600px) {
  .ds-article__capabilities { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .ds-article__capability:nth-child(3n + 1):last-child { grid-column-end: span 3; }
  .ds-article__capability:nth-child(3n + 2):last-child { grid-column-end: span 2; }
}

/* Prose: the one place the library sets type on elements it did not render, because the site's
   write-up is markdown and a measure of about 70 characters is what makes it readable. */
.ds-prose {
  max-width: 70ch;
  color: var(--ds-ink-body);
  font-family: 'Fustat Variable', Fustat, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  font-weight: 600; font-size: 15px; line-height: 23px;
}
.ds-prose > * { margin-block: 0 1em; }
.ds-prose > :last-child { margin-block-end: 0; }
.ds-prose h2 { margin-block: 1.6em 0.5em; color: var(--ds-ink); font-weight: 700; font-size: 24px; line-height: normal; letter-spacing: -0.03em; }
.ds-prose h3 { margin-block: 1.4em 0.4em; color: var(--ds-ink); font-weight: 700; font-size: 20px; line-height: 24px; letter-spacing: -0.02em; }
.ds-prose strong { font-weight: 700; }
.ds-prose a { color: inherit; text-underline-offset: 3px; }
.ds-prose ul, .ds-prose ol { padding-inline-start: 1.2em; }
.ds-prose li { margin-block-end: 0.4em; }
.ds-prose blockquote { margin-inline: 0; padding-inline-start: 1em; border-inline-start: 2px solid var(--ds-rule-strong); }
.ds-prose img { max-width: 100%; height: auto; }

/* ── Contributors ──────────────────────────────────────────────────────────────────────────── */

.ds-contributorsblock { display: flex; flex-direction: column; gap: var(--ds-heading-gap); }
/* Chips that hug their names and wrap by whole chips, as the canvas draws them. A grid of equal
   columns made a long handle wrap inside its cell, and a chip whose name runs to two lines drops
   every slot below it out of line with its row. */
.ds-contributors { display: flex; flex-wrap: wrap; gap: 8px; margin: 0; padding: 0; list-style: none; }
.ds-contributors__link { display: flex; gap: 10px; align-items: center; }
/* Each one is a boxed card, densely packed: this is a roll of names and the box is what stops it
   reading as prose. */
/* One rule owns the chip box, so a blank cannot drift from a real one. Geometric text advances on
   every renderer: a blank is sized in the body role's ch and a chip by its shaped name, and Linux
   font hinting rounds those two differently, so eighteen chips wrapped into a row more than eighteen
   blanks on a runner (PR #92, run 34415187568). */
.ds-contributors__item, .ds-contributors__blank { display: flex; gap: 10px; align-items: center; min-width: 0; padding: 8px 12px 8px 8px; text-rendering: geometricPrecision; }
/* The chip is a control's field: white on paper, dark/raised in the dark on any band (Contributor grid
   1135:6691, both themes). */
.ds-contributors__item { background: var(--ds-surface-control); color: var(--ds-ink); box-shadow: inset 0 0 0 1px var(--ds-rule); }
@media (hover: hover) {
  .ds-contributors__item:has(.ds-contributors__link):hover { background: var(--ds-surface-hover); box-shadow: inset 0 0 0 1px var(--ds-rule-strong); }
}
.ds-contributors__item:has(.ds-contributors__link):focus-within { background: var(--ds-surface-hover); box-shadow: inset 0 0 0 1px var(--ds-rule-strong); }
.ds-contributors__item:has(.ds-contributors__link):active { background: var(--ds-surface-active); }
.ds-contributors__avatar { flex: none; display: block; width: 28px; height: 28px; overflow: hidden; background: var(--ds-surface-raised); color: var(--ds-ink); }
.ds-contributors__avatar img { display: block; width: 100%; height: 100%; object-fit: cover; }
/* The two lines sit 2px apart, as the master draws them (Contributor grid 1135:6691, chip 53 tall). */
.ds-contributors__copy { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
/* A chip grows to its name rather than wrapping it: nothing is cut off, the row simply wraps by
   whole chips. */
.ds-contributors__name, .ds-contributors__handle, .ds-contributors__meta { white-space: nowrap; }
/* The master's lead line (its `handle` layer) is full ink whichever prop carries it; a handle steps
   down to the muted ink only under a name. The count is always muted. */
.ds-contributors__name ~ .ds-contributors__handle, .ds-contributors__meta { color: var(--ds-ink-muted); }
/* While the roll is on its way the block holds its place: blank chips in the placeholder ink of the
   band's own ink — the master's pair, 6% on light and 8% on dark —
   and two bars where a headline that counts the people will land. A blank is built out of the chip's
   own parts — the shared box rule above, the avatar slot, one line of each of the chip's two type
   roles — so its height is a real chip's at every type size and nothing below the band moves when
   the roll lands. They take the band's ink rather than the card ladder because they are not cards
   yet, and they carry the chip's own width, so eighteen of them wrap into the rows eighteen chips
   wrap into. Static — nothing outside the hero moves. */
.ds-contributors__blank { flex: none; background: color-mix(in srgb, currentColor var(--ds-placeholder-alpha), transparent); }
.ds-contributors__blank .ds-contributors__avatar { background: none; }
/* A blank hugs its lines the way a chip hugs its name, so its width is the chip's own: the avatar
   slot, the gap and the padding, plus a name line as wide as the master draws that slab's text.
   Eighteen blanks of ONE width pack two to a row where a real roll of names packs one, so the block
   was a row short at 320 and 600 and jumped 122px when the roll landed — the defect a placeholder
   exists to prevent, at the widths nobody measured. The cycle below is the master's own spread
   (Contributor grid 1135:6691, State=Ready, each chip's text box over the body role's `ch`), so
   eighteen blanks wrap into the rows eighteen names wrap into at every width, and a nineteenth
   starts the spread again. */
.ds-contributors__blank-line { display: block; height: 1lh; }
.ds-contributors__blank-line.ds-t-body { width: calc(var(--ds-blank-ch, 7.874) * 1ch); }
.ds-contributors__blank:nth-child(18n + 1) { --ds-blank-ch: 7.874; }
.ds-contributors__blank:nth-child(18n + 2) { --ds-blank-ch: 7.424; }
.ds-contributors__blank:nth-child(18n + 3) { --ds-blank-ch: 10.124; }
.ds-contributors__blank:nth-child(18n + 4) { --ds-blank-ch: 7.199; }
.ds-contributors__blank:nth-child(18n + 5) { --ds-blank-ch: 9.224; }
.ds-contributors__blank:nth-child(18n + 6) { --ds-blank-ch: 7.199; }
.ds-contributors__blank:nth-child(18n + 7) { --ds-blank-ch: 7.312; }
.ds-contributors__blank:nth-child(18n + 8) { --ds-blank-ch: 7.312; }
.ds-contributors__blank:nth-child(18n + 9) { --ds-blank-ch: 9.899; }
.ds-contributors__blank:nth-child(18n + 10) { --ds-blank-ch: 11.024; }
.ds-contributors__blank:nth-child(18n + 11) { --ds-blank-ch: 6.637; }
.ds-contributors__blank:nth-child(18n + 12) { --ds-blank-ch: 6.524; }
.ds-contributors__blank:nth-child(18n + 13) { --ds-blank-ch: 10.799; }
.ds-contributors__blank:nth-child(18n + 14) { --ds-blank-ch: 8.211; }
.ds-contributors__blank:nth-child(18n + 15) { --ds-blank-ch: 15.861; }
.ds-contributors__blank:nth-child(18n + 16) { --ds-blank-ch: 6.074; }
.ds-contributors__blank:nth-child(18n + 17) { --ds-blank-ch: 6.862; }
.ds-contributors__blank:nth-child(18n + 18) { --ds-blank-ch: 8.999; }
.ds-contributors__blank-title { display: grid; grid-auto-rows: 1lh; align-items: center; }
.ds-contributors__blank-bar { display: block; width: 100%; height: 0.78em; background: color-mix(in srgb, currentColor var(--ds-placeholder-alpha), transparent); }
.ds-contributors__blank-bar:last-child { width: 62%; }

/* ── Tools ─────────────────────────────────────────────────────────────────────────────────── */

.ds-tools { display: flex; flex-direction: column; gap: var(--space-sm); }
.ds-tools__caption { color: var(--ds-ink-muted); }
.ds-tools__list { display: flex; flex-wrap: wrap; gap: 16px 28px; margin: 0; padding: 0; list-style: none; }
.ds-tools__item { display: flex; align-items: center; gap: 8px; }
.ds-tools__logo { display: flex; align-items: center; justify-content: center; width: 20px; height: 20px; }
.ds-tools__logo > :where(img, svg) { display: block; height: 100%; width: auto; }
.ds-tools__label { color: inherit; }
/* The headed band (Wrap=2 lines): heading at the section's heading gap, wrapped lines 24 apart; the bare row keeps 16. */
.ds-toolsblock { display: flex; flex-direction: column; gap: var(--ds-heading-gap); }
.ds-toolsblock .ds-tools__list { row-gap: 24px; }

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

.ds-textlistblock { display: flex; flex-direction: column; gap: 12px; }
.ds-textlist__caption { color: var(--ds-ink-muted); }
.ds-textlist { margin: 0; padding: 0; list-style: none; }
@media (min-width: 800px) { .ds-textlist--two { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: var(--space-lg); } }
.ds-textlist__item { display: flex; gap: 12px; padding-block: 14px; border-block-end: 1px solid var(--ds-rule); }
.ds-textlist__icon { flex: none; display: inline-flex; width: var(--ds-icon-14); height: var(--ds-icon-14); margin-block-start: 4px; }
.ds-textlist__copy { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.ds-textlist__body { color: var(--ds-ink-body); }

/* ── Payments ──────────────────────────────────────────────────────────────────────────────── */

.ds-payments { margin: 0; padding: 0; list-style: none; }
.ds-payments__row { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 16px; padding-block: 16px; border-block-end: 1px solid var(--ds-rule); }
.ds-payments__scheme { flex: none; display: inline-flex; align-items: center; height: 20px; }
.ds-payments__scheme > :where(img, svg) { display: block; height: 100%; width: auto; }
.ds-payments__copy { display: flex; flex-direction: column; min-width: 0; }
.ds-payments__expiry, .ds-payments__status { color: var(--ds-ink-muted); }
.ds-payments__action { margin-inline-start: auto; }

/* ── Product: bands and fields ─────────────────────────────────────────────────────────────── */

/* The label a product band opens with, and the brand rule the canvas draws under it: a 32×2
   rectangle in the brand itself, in both themes (`eyebrow row` on every Walkthrough master,
   `brand rule 32×2` on Get started). One class draws it everywhere, because it was three copies
   with three different gaps and no master to settle them; the gap stays each master's own, set
   through --ds-peyebrow-gap, since the canvas genuinely differs by master (walkthrough 8,
   get started 16). A centred head carries the label without the rule, as the card masters have it. */
.ds-peyebrow { display: flex; flex-direction: column; align-items: flex-start; gap: var(--ds-peyebrow-gap, 8px); margin: 0; color: var(--ds-accent-ink, var(--ds-accent)); }
.ds-peyebrow--ruled::after { content: ''; width: 32px; height: 2px; background: var(--ds-accent); }

/* The heading a product band opens with.
   `split` is the Section / product master: the eyebrow and title held to 560 on the left, the intro
   to 420 on the right, the product heading gap under both. `centre` is the head every product card
   master carries and every centred section on the live sites — a 760 column with the intro held to
   640 inside it, 16 between the lines, centred. Both measures are the canvas's, in the canvas's
   pixels rather than a guess in ch. */
.ds-psection__heading { display: grid; gap: 24px; margin-block-end: var(--ds-heading-gap); }
.ds-psection__lead { display: flex; flex-direction: column; gap: 16px; min-width: 0; }
@media (min-width: 1000px) { .ds-psection__heading--split { grid-template-columns: minmax(0, 560px) minmax(0, 420px); justify-content: space-between; align-items: start; } }
.ds-psection__heading--centre { justify-items: center; gap: 16px; text-align: center; }
.ds-psection__heading--centre .ds-psection__lead { align-items: center; }
.ds-psection__heading--centre .ds-peyebrow { align-items: center; }
.ds-psection__heading--centre .ds-psection__title { max-width: 760px; }
.ds-psection__heading--centre .ds-psection__intro { max-width: 640px; }
.ds-psection__title, .ds-psection__intro { margin: 0; }
.ds-psection__intro { color: var(--ds-ink-body); }
/* The row a band closes on: the link or the button the live sections end with, centred under the
   content, the product heading gap below it. */
.ds-psection__actions { display: flex; flex-wrap: wrap; justify-content: center; gap: 12px; margin-block-start: var(--ds-heading-gap); }
.ds-psection--paper { background: var(--ds-surface); color: var(--ds-ink); }
.ds-psection--lilac { background: var(--ds-surface-paper); color: var(--ds-ink); }
.ds-psection--mist { background: var(--ds-surface-paper); color: var(--ds-ink); }
.ds-psection--sand { background: var(--ds-surface-sand); color: var(--ds-ink); }
/* The 404 blueprint's field, and the one product band that does not take one of the four above.
   `Not found / product` draws the band on brand/white and, in the dark, on dark/raised — the rung
   its corporate twin takes — where `Section / product Field=Paper` takes dark/base. One blueprint,
   one field (DARK-01); the role is `--ds-surface-blueprint` in theme-product.css. */
.ds-notfound--product { background: var(--ds-surface-blueprint); color: var(--ds-ink); }
/* The accent used as INK and the accent used as a FIELD are not the same colour on a dark ground:
   the brand purple reads at under 2:1 as a 12px label there, while the same purple as a button with
   white on it reads at 8:1. So the deep field lifts the ink and leaves the field alone.
   The product footer is that same deep field and belongs here: its column headings measure 1.51:1
   in the brand against the cueprise ground and its links ring at 1.80:1, while the lift clears 3:1
   on both brands — which is what `state/focus-product-on-dark` is for, what the master fills those
   headings with, and what the live product sites put in `--ring` on their deep band. */
.ds-psection--deep, .ds-product .ds-section--invert, .ds-shop__band, .ds-pfooter { --ds-accent-ink: var(--ds-lift); --ds-focus: var(--ds-lift); }
.ds-psection--deep { background: var(--ds-surface-invert); color: var(--ds-ink-on-invert); }

/* ── Product hero ──────────────────────────────────────────────────────────────────────────── */

/* The band fills what is left of the first viewport under the bar (64 on a phone, 72 wide) and stops
   at the master's height, so the cutout is cut by the bottom of the screen and never shown whole.
   The BAND is what cuts it, at its own bottom edge, the way `overflow-hidden` on the live product
   <section> does: the panel runs under the section below rather than ending on a strip of field.
   Down only — a clip across would hide real sideways overflow from the responsive check, which
   skips whatever an ancestor cuts off, and nothing in the band is meant to reach past its sides.
   A band carrying a crop keeps no padding under it, because the crop is what reaches that edge; a
   band with no cutout keeps the master's padding, since its copy would otherwise sit on the edge. */
/* The hero is the whole first screen, the bar floating over it (owner ruling 28 Sep 2026, corporate
   and product alike): 100svh, so a phone's browser chrome never pushes the band past the fold. The
   master is drawn to a 900 canvas, 828 under a 72 bar, which is this rule at that canvas. svh alone:
   every browser the sites support has it, and rule 12 refuses a second declaration as a fallback. */
.ds-phero { position: relative; display: flex; flex-direction: column; min-height: 100svh; padding-block: 64px; --ds-band-top: 64px; overflow-y: clip; }
.ds-phero:has(> .ds-phero__crop) { padding-block-end: 0; }
@media (min-width: 768px) { .ds-phero { padding-block: 80px; --ds-band-top: 80px; } .ds-phero:has(> .ds-phero__crop) { padding-block-end: 0; } }
/* The copy block holds the top of the band and hands every spare pixel down to the crop: the auto
   margin under it takes whatever the crop could not (its ceiling is the live site's), so the slack
   a short headline leaves opens BETWEEN the copy and the panel and never under the panel. Flex
   grows the crop first and an auto margin only takes what is left after that, which is the order
   this wants — more panel, then more air over it, never a strip of field below it. */
.ds-phero__inner { position: relative; flex: none; margin-block-end: auto; display: flex; flex-direction: column; gap: 40px; align-items: center; }
/* The Cueprise ground: `Rings layer · 5.5%`, six circles at 80px steps out to 480, white at 5.5%.
   They sit off the band's RIGHT SHOULDER, not behind the copy — the master centres them 240 from the
   right edge and 120 down at 1440, 90 from the right on a phone, and the live site draws the same
   arcs sweeping the right half. Centred, they read as a smudge behind the headline and the band
   loses the depth the arcs give it. One repeating gradient draws the rings; the mask stops it after
   the sixth, so six circles cost one box and no SVG. */
.ds-phero__rings { --ds-phero-ring-x: calc(100% - 90px); --ds-phero-ring-y: 120px; position: absolute; inset: 0; pointer-events: none; background: repeating-radial-gradient(circle at var(--ds-phero-ring-x) var(--ds-phero-ring-y), transparent 0 79px, rgb(255 255 255 / 0.055) 79px 80px); -webkit-mask-image: radial-gradient(circle at var(--ds-phero-ring-x) var(--ds-phero-ring-y), #000 0 480px, transparent 481px); mask-image: radial-gradient(circle at var(--ds-phero-ring-x) var(--ds-phero-ring-y), #000 0 480px, transparent 481px); }
@media (min-width: 768px) { .ds-phero__rings { --ds-phero-ring-x: calc(100% - 240px); } }
/* The column's own rhythm, mobile first, as the two Size masters set it: 16 between the lines of copy
   and 12 between the stacked buttons on the phone, 24 and 16 past 768. Held at the wide gaps at every
   width the band ran 24px past the master on a 390 screen, which is the fold. */
.ds-phero__copy { display: flex; flex-direction: column; align-items: center; gap: 16px; width: 100%; max-width: 820px; min-width: 0; text-align: center; }
.ds-phero__eyebrow { margin: 0; color: var(--ds-accent-ink, var(--ds-accent)); }
.ds-phero__headline { margin: 0; }
.ds-phero__lede { margin: 0; max-width: 640px; color: var(--ds-ink-body-on-brand); }
.ds-phero__actions { display: flex; flex-wrap: wrap; justify-content: center; gap: 12px; }
/* Every `Hero / product` variant strokes `Button / outline` in its label ink at 60%, not the full ink the bare secondary rings in. */
.ds-phero__actions .ds-button--secondary { box-shadow: inset 0 0 0 1px color-mix(in srgb, currentColor 60%, transparent); }
@media (min-width: 768px) { .ds-phero__copy { gap: 24px; } .ds-phero__actions { gap: 16px; } }
/* The crop. The product runs the full measure under the copy and the band cuts it off: the panel is
   taller than the room left for it, so the next section sits on its lower third. The box only
   RESERVES that room, and it is the last thing in the band, so its bottom IS the band's bottom. It
   does not clip: the panel pinned to its top overflows it and is cut by the band, which is why the
   section below lands on the panel rather than beside it.
   The floor is the slot the master draws — 248 on a phone, 356 wide, each reaching its master's own
   bottom edge. The ceiling is the live site's 420 / 460 / 500; between the two the crop takes
   whatever the first screen leaves. */
.ds-phero__crop { --ds-phero-crop-min: 248px; --ds-phero-crop-max: 420px; position: relative; flex: 1 0 auto; min-height: var(--ds-phero-crop-min); max-height: var(--ds-phero-crop-max); margin-block-start: 24px; }
@media (min-width: 768px) { .ds-phero__crop { --ds-phero-crop-min: 356px; --ds-phero-crop-max: 460px; margin-block-start: 40px; } }
@media (min-width: 1280px) { .ds-phero__crop { --ds-phero-crop-max: 500px; } }
/* The panel is pinned to the top of the crop and spans the room the crop reserves: the parallax box
   is the crop's own box, and the container and the frame inside it grow to fill it (and only grow —
   a panel taller than the room keeps its height and runs off the band, which is the point). So the
   frame reaches the band's bottom edge whatever the site drops inside it, and an interface shorter
   than the room sits on the frame's surface rather than on a strip of field. */
.ds-phero__parallax { position: absolute; inset: 0; display: flex; flex-direction: column; }
.ds-phero__parallax > .ds-container { display: flex; flex-direction: column; flex: 1 0 auto; }
/* The frame is the product depth exception: a 6px corner, the cutout hairline and the one shadow the
   site casts. The drawn interface the site drops inside takes the frame. Its floor is the crop's
   floor — the slot the master draws, 248 on a phone and 356 wide — so the frame never opens shorter
   than the room the band reserves for it, even where flex has nothing to grow into. */
.ds-phero__cutout { position: relative; width: 100%; min-width: 0; flex: 1 0 auto; min-height: var(--ds-phero-crop-min); overflow: clip; border: 1px solid var(--ds-app-border); border-radius: var(--ds-app-radius); background: var(--ds-app-surface); color: var(--ds-ink); box-shadow: var(--ds-app-shadow); }
.ds-phero__cutout > * { display: block; width: 100%; height: auto; }
/* On scroll the panel rises faster than the page and shows the rest, and the copy above it rises
   with it: the panel pushes the copy up rather than sliding over it (owner, 28 Sep 2026, cueprise
   on a phone). Scroll-driven, so nothing moves while the visitor reads, and a browser without
   scroll timelines keeps the still frame. */
@media (prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: scroll()) {
    @keyframes ds-phero-parallax { to { transform: translateY(-104px); } }
    .ds-phero__parallax, .ds-phero:has(> .ds-phero__crop) > .ds-phero__inner { animation: ds-phero-parallax linear both; animation-timeline: scroll(root block); animation-range: 0 90vh; }
  }
}

/* ── Product UI kit · dashboard cutout ─────────────────────────────────────────────────────── */

/* The interface itself, not a picture of one. Below 768 the sidebar goes and the panel keeps a real
   screen's width: the KPI rail scrolls sideways inside its own frame instead of the figures being
   squeezed until ₦3,412,800 clips. The frame (corner, hairline, shadow) belongs to whatever places
   the cutout — the hero crop, a cutout card, a walkthrough screen — so the kit paints only its field. */
.ds-dash { display: flex; min-width: 0; background: var(--ds-app-surface); color: var(--ds-ink); text-align: start; }
.ds-dash__side { display: none; }
@media (min-width: 768px) { .ds-dash__side { display: flex; flex: none; flex-direction: column; width: 212px; border-inline-end: 1px solid var(--ds-app-border); background: var(--ds-app-sunken); } }
.ds-dash__tenant { display: flex; align-items: center; gap: 10px; padding: 16px; }
.ds-dash__mark { display: grid; flex: none; place-items: center; width: 32px; height: 32px; border-radius: var(--ds-app-radius); background: var(--ds-app-hover); color: var(--ds-accent-ink); }
.ds-dash__tenant-copy { min-width: 0; }
.ds-dash__tenant-name { display: block; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; color: var(--ds-ink); }
.ds-dash__tenant-meta { display: block; color: var(--ds-ink-muted); }
.ds-dash__nav { flex: 1; padding: 0 10px 16px; }
.ds-dash__group { margin: 0; padding: 16px 10px 6px; color: var(--ds-ink-muted); }
.ds-dash__item { display: flex; align-items: center; gap: 10px; margin-block-end: 2px; padding: 7px 10px; border-radius: var(--ds-app-radius); color: var(--ds-ink-body); }
.ds-dash__item--active { background: var(--ds-app-hover); color: var(--ds-accent-ink); }
.ds-dash__item-icon { display: inline-flex; flex: none; width: 15px; height: 15px; }
.ds-dash__item-icon > * { width: 100%; height: 100%; }
.ds-dash__item-label { flex: 1; min-width: 0; }
/* Status chips: the product set's chip corner is 2px; the tone is the field and the ink together. */
.ds-dash__chip { display: inline-flex; flex: none; align-items: center; padding: 2px 8px; border-radius: var(--ds-radius-chip); background: var(--ds-app-sunken); color: var(--ds-ink-muted); font-variant-numeric: tabular-nums; }
.ds-dash__chip--ok { background: var(--ds-app-ok-field); color: var(--ds-app-ok); }
.ds-dash__chip--warn { background: var(--ds-app-warn-field); color: var(--ds-app-warn); }
.ds-dash__chip--bad { background: var(--ds-app-bad-field); color: var(--ds-app-bad); }
.ds-dash__user { display: flex; align-items: center; gap: 10px; padding: 16px; border-block-start: 1px solid var(--ds-app-border); color: var(--ds-ink-body); }
.ds-dash__avatar { display: grid; flex: none; place-items: center; width: 28px; height: 28px; border-radius: 50%; background: var(--ds-app-hover); color: var(--ds-accent-ink); }
.ds-dash__main { flex: 1; min-width: 0; padding: 16px; }
@media (min-width: 640px) { .ds-dash__main { padding: 20px; } }
.ds-dash__head { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; }
.ds-dash__title { margin: 0; color: var(--ds-ink); }
.ds-dash__subtitle { margin: 2px 0 0; color: var(--ds-ink-muted); }
.ds-dash__period { display: none; }
@media (min-width: 640px) { .ds-dash__period { display: inline-flex; flex: none; align-items: center; padding: 6px 12px; border: 1px solid var(--ds-app-border); border-radius: var(--ds-app-radius); color: var(--ds-ink-body); font-variant-numeric: tabular-nums; } }
/* The KPI rail. A narrow screen does not reflow these into a 2×2 block in the real product — the row
   stays a row and scrolls sideways, snapping tile by tile — so the cutout does too. It is a focusable
   group, so a keyboard reaches the tiles the pointer would drag to; the scrollbar is hidden because
   the rail announces itself another way, below. At 1280 the four tiles fit and the rail is a grid. */
.ds-dash__kpis { display: flex; gap: 12px; margin-block-start: 16px; padding-block-end: 4px; overflow-x: auto; scroll-snap-type: x mandatory; scrollbar-width: none; }
.ds-dash__kpis::-webkit-scrollbar { display: none; }
@media (min-width: 1280px) { .ds-dash__kpis { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); padding-block-end: 0; overflow-x: visible; } }
.ds-dash__kpi { flex: none; width: 13rem; padding: 12px; border: 1px solid var(--ds-app-border); border-radius: var(--ds-app-radius); scroll-snap-align: start; }
@media (min-width: 1280px) { .ds-dash__kpi { width: auto; } }
/* The peek. Once, a beat after paint, every tile slides 2.75rem left and settles back, so the rail
   shows it moves before anyone has to guess. A transform, not a scroll: nothing is left displaced,
   and a real drag afterwards starts from the top of the rail as usual. */
@media (prefers-reduced-motion: no-preference) and (max-width: 1279px) { .ds-dash__kpi { animation: ds-dash-peek 2.4s cubic-bezier(0.4, 0, 0.2, 1) 1.1s 1 both; } }
@keyframes ds-dash-peek { 0%, 100% { transform: translateX(0); } 38%, 62% { transform: translateX(-2.75rem); } }
.ds-dash__kpi-label { margin: 0; color: var(--ds-ink-muted); }
.ds-dash__kpi-value { display: block; margin-block-start: 6px; color: var(--ds-ink); font-variant-numeric: tabular-nums; }
.ds-dash__kpi-row { display: flex; align-items: center; gap: 8px; margin-block-start: 8px; }
.ds-dash__kpi-compare { display: none; color: var(--ds-ink-muted); }
@media (min-width: 640px) { .ds-dash__kpi-compare { display: inline; } }
.ds-dash__spark { flex: none; width: 64px; height: 24px; color: var(--ds-app-ok); }
.ds-dash__spark--down { color: var(--ds-app-bad); }
.ds-dash__grid { display: grid; gap: 12px; margin-block-start: 12px; }
.ds-dash__grid > * { min-width: 0; }
@media (min-width: 1024px) { .ds-dash__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.ds-dash__card { padding: 16px; border: 1px solid var(--ds-app-border); border-radius: var(--ds-app-radius); }
.ds-dash__card--wide { margin-block-start: 12px; }
.ds-dash__card-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
.ds-dash__card-title { display: flex; align-items: center; gap: 8px; margin: 0; color: var(--ds-ink); }
.ds-dash__card-meta { margin: 2px 0 0; color: var(--ds-ink-muted); }
.ds-dash__card-action { flex: none; color: var(--ds-accent-ink); white-space: nowrap; }
.ds-dash__revenue { display: flex; flex-direction: column; align-items: center; gap: 16px; margin-block-start: 12px; }
@media (min-width: 640px) { .ds-dash__revenue { flex-direction: row; } }
/* The donut is drawn from arc paths, not a rotated dash pattern: dash offsets scatter into fragments
   the moment the radius changes, and this has to survive being scaled down into a hero. */
.ds-dash__donut { flex: none; width: 116px; height: 116px; }
.ds-dash__donut-total { fill: var(--ds-ink); font-variant-numeric: tabular-nums; }
.ds-dash__donut-label { fill: var(--ds-ink-muted); }
.ds-dash__legend { display: grid; flex: 1; gap: 8px; width: 100%; min-width: 0; margin: 0; padding: 0; list-style: none; }
.ds-dash__legend-row { display: flex; align-items: center; gap: 8px; }
.ds-dash__dot { flex: none; width: 8px; height: 8px; border-radius: 50%; }
.ds-dash__legend-name { flex: 1; min-width: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; color: var(--ds-ink-body); }
.ds-dash__legend-share { color: var(--ds-ink-muted); font-variant-numeric: tabular-nums; }
.ds-dash__legend-value { flex: none; width: 68px; text-align: end; color: var(--ds-ink-body); font-variant-numeric: tabular-nums; }
.ds-dash__alerts { display: grid; gap: 8px; margin: 12px 0 0; padding: 0; list-style: none; }
.ds-dash__alert { display: flex; align-items: flex-start; gap: 10px; min-width: 0; padding: 10px; border-radius: var(--ds-app-radius); background: var(--ds-app-sunken); }
.ds-dash__alert-dot { flex: none; width: 10px; height: 10px; margin-block-start: 4px; border-radius: var(--ds-app-radius-thumb); background: var(--ds-ink-muted); }
.ds-dash__alert-dot--bad { background: var(--ds-app-bad); }
.ds-dash__alert-dot--warn { background: var(--ds-app-warn); }
.ds-dash__alert-copy { flex: 1; min-width: 0; }
.ds-dash__alert-title { display: block; color: var(--ds-ink); }
.ds-dash__alert-detail { display: block; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; color: var(--ds-ink-muted); }
.ds-dash__alert-when { flex: none; color: var(--ds-ink-muted); font-variant-numeric: tabular-nums; }
.ds-dash__table { width: 100%; margin-block-start: 12px; border-collapse: collapse; }
.ds-dash__th { padding-block-end: 8px; border-block-end: 1px solid var(--ds-app-border); text-align: start; color: var(--ds-ink-muted); }
.ds-dash__th--num { text-align: end; }
.ds-dash__row { border-block-end: 1px solid var(--ds-app-border); }
.ds-dash__row:last-child { border-block-end: 0; }
.ds-dash__td { padding-block: 10px; text-align: end; color: var(--ds-ink-body); font-variant-numeric: tabular-nums; }
.ds-dash__td--name { text-align: start; }
.ds-dash__branch { display: block; color: var(--ds-ink); }
.ds-dash__area { display: block; color: var(--ds-ink-muted); }
/* Margin joins at 640, stock health at 1024: the columns a phone cannot hold are dropped, not squeezed. */
.ds-dash__col--margin { display: none; }
@media (min-width: 640px) { .ds-dash__col--margin { display: table-cell; } }
.ds-dash__col--health { display: none; }
@media (min-width: 1024px) { .ds-dash__col--health { display: table-cell; padding-inline-start: 24px; text-align: start; } }
.ds-dash__health { display: flex; align-items: center; gap: 8px; }
.ds-dash__bar { width: 100%; max-width: 104px; height: 6px; overflow: hidden; border-radius: var(--ds-radius-chip); background: var(--ds-app-sunken); }
.ds-dash__bar-fill { display: block; height: 100%; border-radius: var(--ds-radius-chip); background: var(--ds-app-ok); }
.ds-dash__bar-fill--warn { background: var(--ds-app-warn); }
.ds-dash__health-figure { color: var(--ds-ink-muted); font-variant-numeric: tabular-nums; }

/* ── Product UI kit · shopfront cutout ─────────────────────────────────────────────────────── */

/* A whole shop, built to be cropped: the bar and the promo band carry the argument, the filter rail
   and the catalogue under them are meant to be half-seen. Same rule as the dashboard — the frame is
   the placer's; below 1024 the shop's own nav goes, below 640 the search box and the band's call.
   Nothing else steps: the Site showcase card at 324 and the mobile product hero at 342 nest this shop
   with the 1180 master's own padding, its 26px headline and its 168px filter rail, so the shop keeps
   them at every width and the window that crops it cuts where the canvas does.
   Those widths are the SHOP's, not the window's: this is footage, so it answers to the size it is
   drawn at, the way the app card the rest of the kit is built on already does. Read from the page
   instead, one shop would keep its six-column catalogue and its 168px filter rail inside a 390-wide
   card on a 1440 screen — the hero's shop and a showcase card's are the same shop at two sizes. */
.ds-shop { min-width: 0; container: ds-shop / inline-size; background: var(--ds-app-surface); color: var(--ds-ink); text-align: start; }
.ds-shop__bar { display: flex; align-items: center; gap: 16px; padding: 12px 20px; border-block-end: 1px solid var(--ds-app-border); }
.ds-shop__store { flex: none; margin: 0; color: var(--ds-ink); }
.ds-shop__nav { display: none; }
@container ds-shop (min-width: 1024px) { .ds-shop__nav { display: flex; align-items: center; gap: 20px; } }
.ds-shop__navitem { color: var(--ds-ink-body); white-space: nowrap; }
.ds-shop__navitem--active { color: var(--ds-ink); text-decoration: underline; text-decoration-color: var(--ds-accent); text-decoration-thickness: 2px; text-underline-offset: 6px; }
.ds-shop__tools { display: flex; align-items: center; gap: 12px; margin-inline-start: auto; color: var(--ds-ink-muted); }
.ds-shop__search { display: none; }
@container ds-shop (min-width: 640px) { .ds-shop__search { display: flex; align-items: center; gap: 8px; padding: 6px 12px; border: 1px solid var(--ds-app-border); border-radius: var(--ds-app-radius); background: var(--ds-app-sunken); color: var(--ds-ink-muted); white-space: nowrap; } }
.ds-shop__icon { display: inline-flex; flex: none; width: 16px; height: 16px; }
.ds-shop__icon > * { width: 100%; height: 100%; }
.ds-shop__icon--sm { width: 14px; height: 14px; }
.ds-shop__cart { display: inline-flex; align-items: center; gap: 6px; color: var(--ds-ink); font-variant-numeric: tabular-nums; }
/* The promo band. A shop's campaign banner is a picture with an offer written on it, so this one is:
   the photograph sits on the right and fades into the field instead of being boxed off from it. On a
   phone there is no room beside the copy, so the same picture goes behind it at a quarter strength. */
.ds-shop__band { position: relative; overflow: hidden; padding: 36px 32px; background: var(--ds-surface-invert); color: var(--ds-ink-on-invert); }
.ds-shop__band-art { position: absolute; inset: 0; pointer-events: none; -webkit-mask-image: linear-gradient(to right, transparent, #000 55%); mask-image: linear-gradient(to right, transparent, #000 55%); }
.ds-shop__band-art > * { display: block; width: 100%; height: 100%; object-fit: cover; object-position: 62% 22%; }
/* Narrow, the picture is BEHIND the copy, so the field goes back over it at 85%: an opacity on the
   art alone is a promise about one photograph, and this slot takes whichever one the site has. The
   veil is a layer rather than a lower opacity so what the reader sees is what a contrast reading of
   the band can measure. Beside the copy — from 640 — there is nothing to hold over, and it goes. */
.ds-shop__band-veil { position: absolute; inset: 0; display: block; background: color-mix(in srgb, var(--ds-surface-invert) 85%, transparent); }
@container ds-shop (min-width: 640px) { .ds-shop__band-art { inset-inline-start: auto; width: 46%; -webkit-mask-image: linear-gradient(to right, transparent, #000 62%); mask-image: linear-gradient(to right, transparent, #000 62%); } }
@container ds-shop (min-width: 640px) { .ds-shop__band-veil { display: none; } }
.ds-shop__band-copy { position: relative; }
@container ds-shop (min-width: 640px) { .ds-shop__band-copy { max-width: 58%; } }
.ds-shop__band-eyebrow { margin: 0; color: var(--ds-accent-ink); }
.ds-shop__band-title { max-width: 22ch; margin: 12px 0 0; color: var(--ds-ink); }
/* The band's call goes narrow, with the nav and the search box. Every master that nests this shop at
   a card's width sets the promo band's `button` hidden on the instance — the Site showcase card at
   324 and the mobile product hero at 342 both draw the band 159 tall, which is the copy WITHOUT it —
   while the 1180 master draws the band 203 and shows it. Kept, the call added its 32 and its 16 of
   margin to a band a fixed window then cropped 19px into the catalogue below, so the crop line ran
   through the middle of the catalogue heading; the rule is the shop's, not that window's, because
   the same shop is nested narrow in the hero as well. */
.ds-shop__band-action { display: none; }
@container ds-shop (min-width: 640px) { .ds-shop__band-action { display: inline-flex; margin-block-start: 16px; padding: 8px 16px; border-radius: var(--ds-app-radius); background: var(--ds-accent); color: var(--ds-ink-on-accent-control); } }
.ds-shop__body { display: flex; }
.ds-shop__filters { display: flex; flex: none; flex-direction: column; gap: 12px; width: 168px; padding: 16px; border-inline-end: 1px solid var(--ds-app-border); }
.ds-shop__filters-head { display: flex; align-items: center; gap: 8px; color: var(--ds-ink); }
.ds-shop__facet--ruled { padding-block-end: 12px; border-block-end: 1px solid var(--ds-app-border); }
.ds-shop__facet-head { display: flex; align-items: center; justify-content: space-between; color: var(--ds-ink); }
.ds-shop__chevron { display: inline-flex; flex: none; width: 12px; height: 12px; color: var(--ds-ink-muted); }
.ds-shop__chevron > * { width: 100%; height: 100%; }
.ds-shop__chevron--open { transform: rotate(180deg); }
.ds-shop__options { display: grid; gap: 6px; margin: 8px 0 0; padding: 0; list-style: none; }
.ds-shop__option { display: flex; align-items: center; gap: 8px; color: var(--ds-ink-body); }
.ds-shop__box { flex: none; width: 10px; height: 10px; border: 1px solid var(--ds-app-border); border-radius: var(--ds-app-radius-thumb); }
.ds-shop__price-label { display: block; color: var(--ds-ink); }
.ds-shop__price-track { height: 4px; margin-block-start: 8px; border-radius: var(--ds-radius-chip); background: var(--ds-app-sunken); }
.ds-shop__price-fill { display: block; height: 100%; border-radius: var(--ds-radius-chip); background: var(--ds-accent); }
.ds-shop__price-range { display: block; margin-block-start: 6px; color: var(--ds-ink-muted); font-variant-numeric: tabular-nums; }
/* Beside the 168 rail a narrow shop has less catalogue than one tile, and the masters that nest it
   there draw the tiles at their own 148 and let the cutout's edge crop them, the sort control with
   them. The catalogue is that edge, so a tile is cut off rather than squeezed until its price and its
   size are printed over each other. */
.ds-shop__catalogue { flex: 1; min-width: 0; padding: 20px; overflow-x: clip; }
.ds-shop__catalogue-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.ds-shop__heading { margin: 0; color: var(--ds-ink); }
.ds-shop__sort { display: inline-flex; align-items: center; gap: 6px; padding: 4px 10px; border: 1px solid var(--ds-app-border); border-radius: var(--ds-app-radius); white-space: nowrap; }
.ds-shop__sort-label { color: var(--ds-ink-muted); }
.ds-shop__sort-value { color: var(--ds-ink); }
/* Two tiles at their own 148 below 640, three from 640, six from 1024 — the second row is the one
   the crop cuts. */
.ds-shop__grid { display: grid; grid-template-columns: repeat(2, 148px); gap: 12px; margin-block-start: 16px; }
@container ds-shop (min-width: 640px) { .ds-shop__grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@container ds-shop (min-width: 1024px) { .ds-shop__grid { grid-template-columns: repeat(6, minmax(0, 1fr)); gap: 16px; } }
.ds-shop__tile { display: block; min-width: 0; }
.ds-shop__thumb { position: relative; display: block; aspect-ratio: 1; overflow: hidden; border: 1px solid var(--ds-app-border); border-radius: var(--ds-app-radius); background: var(--ds-app-sunken); }
.ds-shop__thumb > img { display: block; width: 100%; height: 100%; object-fit: cover; }
.ds-shop__wish { position: absolute; inset-block-start: 6px; inset-inline-end: 6px; display: grid; place-items: center; width: 24px; height: 24px; border-radius: 50%; background: color-mix(in srgb, var(--ds-app-surface) 90%, transparent); color: var(--ds-ink-body); }
.ds-shop__wish > .ds-shop__icon { width: 12px; height: 12px; }
/* The ribbon's field is mixed opaque rather than laid at 80% so the ink is measured against what is
   actually behind it; in the dark the same mix reads as a light band with the card's ink on it. */
.ds-shop__soldout { position: absolute; inset: auto 0 0 0; margin: 0; padding-block: 4px; text-align: center; background: color-mix(in srgb, var(--ds-ink) 80%, var(--ds-app-sunken)); color: var(--ds-app-surface); }
.ds-shop__tile-row { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; margin-block-start: 8px; }
.ds-shop__tile-row--meta { margin-block-start: 2px; }
.ds-shop__name { min-width: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; color: var(--ds-ink); }
.ds-shop__rating { display: inline-flex; flex: none; align-items: center; gap: 2px; color: var(--ds-ink-muted); font-variant-numeric: tabular-nums; }
.ds-shop__star { display: inline-flex; flex: none; width: 10px; height: 10px; color: var(--ds-app-warn); }
.ds-shop__star > * { width: 100%; height: 100%; fill: currentColor; }
.ds-shop__price { color: var(--ds-ink-body); font-variant-numeric: tabular-nums; }
.ds-shop__size { flex: none; color: var(--ds-ink-muted); }
.ds-shop__add { display: flex; align-items: center; justify-content: center; gap: 6px; height: 28px; margin-block-start: 8px; border-radius: var(--ds-app-radius); background: var(--ds-accent); color: var(--ds-ink-on-accent-control); }
.ds-shop__add--notify { border: 1px solid var(--ds-app-border); background: none; color: var(--ds-ink-muted); }

/* ── Product tiles ─────────────────────────────────────────────────────────────────────────── */

/* The band owns its ground, so it is the positioning context for it, and the container above it. */
.ds-psection { position: relative; }
.ds-psection > .ds-container { position: relative; }

.ds-productsblock { display: flex; flex-direction: column; gap: var(--ds-heading-gap); }
/* The card's category and its tagline are the accented lines on the master — lime on the blue
   field, the brand blue on a plain one. */
.ds-products__eyebrow { color: var(--ds-accent-ink, var(--ds-ink-muted)); }
.ds-products__also { display: flex; flex-wrap: wrap; align-items: baseline; gap: 12px 24px; }
.ds-products__also-label { color: var(--ds-ink-muted); }
.ds-products__also-list { display: flex; flex-wrap: wrap; gap: 12px 24px; margin: 0; padding: 0; list-style: none; }

/* ── Slot heights ──────────────────────────────────────────────────────────────────────────── */

/*
 * Cards in a row line up on their slots, not only on their outer boxes. A title that wraps to two
 * lines in one card and one in the next pushes everything below it out of step, and the eye catches
 * that long before it catches a height difference. The masters solve it by fixing the slot; these
 * do the same in line boxes, so the reserved space is two lines of the slot's own type. An em count
 * is not a line box: 2.5em under a 12px mono eyebrow is 30, and its two lines are 32.
 *
 * Two lines is the reservation because two is what these slots are drawn to hold. A third line
 * still pushes — and that is the copy being too long for the card, which is a copy problem.
 */
@media (min-width: 600px) {
  .ds-trade-card__title,
  .ds-int-card__eyebrow,
  .ds-int-card__title,
  .ds-article__card-title,
  .ds-person__role { min-height: 2lh; }
}

/* The measure this slot absorbs, so whatever follows it sits at the foot of every card alike. */
.ds-showcase__body,
.ds-int-card__body,
.ds-addon-card__body { flex: 1; }

/* ── Pathway ───────────────────────────────────────────────────────────────────────────────── */

.ds-stepsblock, .ds-pathway { display: flex; flex-direction: column; gap: var(--ds-heading-gap); }
.ds-pathway__list { display: grid; gap: var(--ds-card-gap); margin: 0; padding: 0; list-style: none; }
/* Three adjacent cards sharing their borders on the canvas, one card to a row below it. */
@media (min-width: 900px) {
  .ds-pathway__list { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 0; }
  .ds-pathway__item + .ds-pathway__item { border-inline-start: 0; }
}
.ds-pathway__item { display: flex; flex-direction: column; gap: 10px; padding: var(--space-sm); border: 1px solid var(--ds-rule); }
/* The stage the reader is at is filled paper on the canvas — the chip says it in words too. */
.ds-pathway__item--here { background: var(--ds-surface-paper); color: var(--ds-ink); }
/* The chip is taller than the index, and only one card has it — so the row keeps the chip's
   height on every card and the names across the three stay on one line. */
.ds-pathway__index-row { display: flex; align-items: center; gap: 12px; min-height: 24px; }
/* The stage number is the accented line on the master, the way a step number is. */
.ds-pathway__index { color: var(--ds-accent-ink, var(--ds-ink-muted)); }
.ds-pathway__name { margin: 0; }
.ds-pathway__tagline { margin: 0; color: var(--ds-ink); }
.ds-pathway__body { margin: 0; color: var(--ds-ink-body); }
/* A stage with nowhere to go closes on plain text in the link's ink and the link's row. */
.ds-pathway__link, .ds-pathway__end { margin-block-start: auto; padding-block-start: 6px; }
.ds-pathway__end { margin-block-end: 0; color: var(--ds-accent-ink, currentColor); }
.ds-section--brand .ds-pathway__end { color: var(--ds-card-link-ink, var(--ds-ink-on-brand)); }

/* ── Product card sections ─────────────────────────────────────────────────────────────────── */

/* Five masters share this heading and their own grid, and nothing else. */
.ds-pcards { display: flex; flex-direction: column; gap: var(--ds-heading-gap); }
.ds-pcards__heading { display: flex; flex-direction: column; align-items: center; gap: 16px; text-align: center; }
.ds-pcards__eyebrow { color: var(--ds-accent-ink, var(--ds-accent)); }
.ds-pcards__title { margin: 0; max-width: 24ch; }
.ds-pcards__intro { margin: 0; max-width: 62ch; color: var(--ds-ink-body); }
.ds-pcards__closing { margin: 0; text-align: center; color: var(--ds-accent-ink, var(--ds-accent)); }
.ds-pcards__linkrow { display: flex; justify-content: center; }

.ds-pcards__grid { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--ds-card-gap); margin: 0; padding: 0; list-style: none; }
.ds-pcards__grid > * { min-width: 0; }
@media (min-width: 600px) { .ds-pcards__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 1000px) {
  .ds-pcards__grid[data-columns='3'] { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .ds-pcards__grid[data-columns='4'] { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}

/* A trade card: the trade, and the one line that matters to it. */
.ds-trade-card { display: flex; flex-direction: column; gap: 14px; padding: var(--space-sm); background: var(--ds-surface); color: var(--ds-ink); border-radius: var(--ds-radius-card); box-shadow: inset 0 0 0 1px var(--ds-rule); }
.ds-trade-card__icon { display: inline-flex; width: 20px; height: 20px; color: var(--ds-accent); }
.ds-trade-card__title { margin: 0; }
/* The line under a product card's title is the quiet one on the canvas — muted, not body ink. */
.ds-trade-card__line { margin: 0; color: var(--ds-ink-muted); }

/* An integration card: the case it covers, the claim, the paragraph. */
.ds-int-card { display: flex; flex-direction: column; gap: 10px; padding: var(--space-sm); background: var(--ds-surface); color: var(--ds-ink); border-radius: var(--ds-radius-card); box-shadow: inset 0 0 0 1px var(--ds-rule); }
.ds-int-card__icon, .ds-eco__icon { display: inline-flex; width: 20px; height: 20px; color: var(--ds-accent-ink, var(--ds-accent)); }
.ds-int-card__icon > *, .ds-eco__icon > * { width: 100%; height: 100%; }
.ds-int-card__eyebrow { color: var(--ds-accent-ink, var(--ds-accent)); }
.ds-int-card__title, .ds-int-card__body { margin: 0; }
.ds-int-card__body { color: var(--ds-ink-muted); }
.ds-int-card__link { margin-block-start: auto; }

/* An add-on card: what it will be, that it is not here yet, and a look at it. */
.ds-addon-card { display: flex; flex-direction: column; gap: 12px; padding: var(--space-sm); background: var(--ds-surface); color: var(--ds-ink); border-radius: var(--ds-radius-card); box-shadow: inset 0 0 0 1px var(--ds-rule); }
/* The tag stays on the title's line. Wrapping it underneath moves it by a whole line in one card
   and not the next, which is the loudest kind of misalignment in a row. It is the mono label the
   live site sets it in, not a chip: at the far end of the row, centred on the title's first line. */
.ds-addon-card__top { display: flex; align-items: flex-start; flex-wrap: nowrap; gap: 16px; }
.ds-addon-card__title { margin: 0; min-width: 0; }
.ds-addon-card__tag { flex: none; margin-inline-start: auto; padding-block-start: 7px; color: var(--ds-accent-ink, var(--ds-accent)); }
.ds-addon-card__body { margin: 0; color: var(--ds-ink-muted); }
/* Side by side, the three slots are rows of the grid rather than of each card, so a title that wraps
   in one card moves every body down with it and a row of one-line titles stays one line tall, as the
   master draws it. */
@media (min-width: 600px) {
  .ds-addon-card { display: grid; grid-row: span 3; grid-template-columns: minmax(0, 1fr); grid-template-rows: subgrid; row-gap: 12px; }
  .ds-addon-card__top { grid-row: 1; }
  .ds-addon-card__body { grid-row: 2; }
}
/* The slot is footage of the coming feature, so it is a viewport onto the planned product UI and not
   a box that UI is poured into. `--ds-footage-width` is the width the canvas draws the fragment at
   (329 desktop, 294 mobile): the fragment is laid out at that width and then scaled to whatever the
   column gives it, so the slot is a container for the `cqw` that scale is read from, and its height
   is the scaled fragment's. The canvas hugs each fragment here — the three image slots are drawn
   168, 156 and 175 tall — so there is no height for the slot to state, and the `aspect-ratio: 16/10`
   that stood here was a fourth height no master draws, moved by the column: it cut the last rows off
   the Business mail list below the design width and stretched the fragment above it. `overflow: clip`
   stays as the floor under a fragment a future master draws taller than its slot. */
.ds-addon-card__media { --ds-footage-width: 294px; container-type: inline-size; margin-block-start: auto; overflow: clip; background: var(--ds-surface); color: var(--ds-ink); border-radius: var(--ds-radius-card); box-shadow: inset 0 0 0 1px var(--ds-rule); }
@media (min-width: 600px) { .ds-addon-card__media { --ds-footage-width: 329px; grid-row: 3; } }
.ds-pcards__vendors { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 12px 24px; }
.ds-pcards__vendors-label { color: var(--ds-ink-muted); }
.ds-pcards__vendor-list { display: flex; flex-wrap: wrap; align-items: center; gap: 12px 24px; margin: 0; padding: 0; list-style: none; }
.ds-pcards__vendor { display: flex; align-items: center; gap: 8px; color: var(--ds-ink-body); }
.ds-pcards__vendor-mark { display: inline-flex; flex: none; width: 20px; height: 20px; }
/* Below 600 the row stacks, one mark per line from the left edge, as the Size=Mobile master lays it. Kept after the base rules so it wins. */
@media (max-width: 599.98px) {
  .ds-pcards__vendors { flex-direction: column; flex-wrap: nowrap; align-items: flex-start; gap: 24px; }
  .ds-pcards__vendor-list { flex-direction: column; flex-wrap: nowrap; align-items: flex-start; gap: 24px; }
}

/* Ecosystem: three cards that are a sequence, so an arrow sits between them. */
.ds-eco { display: grid; gap: var(--ds-card-gap); margin: 0; padding: 0; list-style: none; }
.ds-eco__item { display: flex; flex-direction: column; gap: var(--ds-card-gap); min-width: 0; }
.ds-eco__card { display: flex; flex-direction: column; gap: 10px; height: 100%; padding: var(--space-sm); background: var(--ds-surface); color: var(--ds-ink); border-radius: var(--ds-radius-card); box-shadow: inset 0 0 0 1px var(--ds-rule); }
.ds-eco__eyebrow { color: var(--ds-accent-ink, var(--ds-accent)); }
.ds-eco__name, .ds-eco__body { margin: 0; }
.ds-eco__body { color: var(--ds-ink-muted); }
.ds-eco__link { margin-block-start: auto; }
.ds-eco__note { margin: 0; color: var(--ds-ink-muted); }
.ds-eco__arrow { align-self: center; font-size: 28px; line-height: 1; color: var(--ds-accent-ink, var(--ds-accent)); }
@media (max-width: 999.98px) { .ds-eco__arrow { transform: rotate(90deg); } }
@media (min-width: 1000px) {
  .ds-eco { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  /* The arrow sits in the gap rather than taking a column of its own. */
  .ds-eco__item { position: relative; flex-direction: row; }
  .ds-eco__arrow { position: absolute; inset-inline-start: calc(var(--ds-card-gap) * -1); inset-block-start: 50%; width: var(--ds-card-gap); text-align: center; transform: translateY(-50%); }
  .ds-eco__card { flex: 1; }
}

/* A tour card: the picture first, because the picture is the beat.
 *
 * The three slots are rows of the section's own grid, not of the card, so a title that wraps in one
 * card pushes every card's body down together. Reserving a fixed title height would have done it
 * too, but it would also have opened a gap under the single-line titles the master actually has —
 * the canvas balances these cards without padding them out, and so does this. */
.ds-pcards--tour .ds-pcards__grid { row-gap: 16px; column-gap: var(--ds-card-gap); }
/* One column, and it is the card's own width: the footage is laid out at its master's width before
   it is scaled to the slot, and an auto column would take that unscaled width and push the card's
   title and body out with it. */
.ds-tour-card { display: grid; grid-row: span 3; grid-template-columns: minmax(0, 1fr); grid-template-rows: subgrid; gap: 16px; }
/* The beat's picture is footage too, and the canvas draws it 377 x 200 on the desktop master and
   342 x 200 on the mobile one, with the cutout inside the slot set to fill it. Both numbers are
   stated here: the fragment is laid out at that size and scaled to the column, so the slot's height
   is the scaled fragment's and moves with the width the way the canvas's frame does. A fixed height
   here would leave the scaled fragment short of the slot's bottom edge. */
.ds-tour-card__media { --ds-footage-width: 342px; --ds-footage-height: 200px; container-type: inline-size; grid-row: 1; overflow: clip; background: var(--ds-surface); color: var(--ds-ink); border-radius: var(--ds-radius-card); box-shadow: inset 0 0 0 1px var(--ds-rule); }
@media (min-width: 600px) { .ds-tour-card__media { --ds-footage-width: 377px; } }
/* Every product media slot, one rule.
 *
 * A screenshot is evidence, and cropping it cuts off the thing it is evidence of. These frames show
 * the whole image and let the frame's own field take up whatever is left. A photograph still covers:
 * the edge of a portrait is not information the way the edge of a sidebar is.
 *
 * A live fragment is neither poured into the slot nor cropped by it: it is laid out at the size its
 * master draws — `--ds-footage-width` across, `--ds-footage-height` down where the master fixes it —
 * and then scaled, whole, to the width the column gives it. Pouring it in made the same three rows
 * read one line at 1180 and three at 1280, which is website chrome wearing a screenshot's clothes;
 * painting it at a fixed 377 instead cut 83px off the right of every tour card between the two
 * masters, and left the slot bare beside it at the widths where the column is wider. Scaling keeps
 * the reading the master composed — the same rows, breaking in the same places, at every width — and
 * fills the slot exactly, which is what the canvas does when its frame is resized (owner, 17 Sep
 * 2026; the fixed footage width of 7 September is retired).
 *
 * The scale is the slot's own width over the master's, and CSS has no division of one length by
 * another: `tan(atan2(a, b))` is that ratio as a number, and `zoom` is the one way to apply it that
 * takes the layout with it, so the slot ends where the scaled fragment ends. It reads `cqw`, so
 * every slot carrying this rule declares `container-type: inline-size` — without it `cqw` falls
 * through to the viewport and the fragment is drawn several times over. That a zoomed element's own
 * `cqw` still resolves in its container's space is the one thing here no specification settles, and
 * both engines agree on it today.
 *
 * The rule sets the fragment's size and nothing else: naming a display here made the tour's card a
 * block box and dropped the centring its master lays the content out with.
 *
 * It sizes the slot's child whatever the child is, because there is only one kind of child allowed.
 * The sheet used to carry a second rule beside this one that letterboxed an `<img>` with
 * `object-fit: contain`, which read as permission: it was written for the captures the owner then
 * ruled out, and a picture in a footage slot now fails the gate by name. A rule that styles what the
 * gate refuses is the next author's licence to put it back.
 *
 * `:first-child` rather than `*` because the fragment is the slot's one child and because the size
 * has to outrank the fragment's own: `.ds-app-card` sets `width: 100%` further down this sheet, and
 * at equal specificity the later rule wins and hands the footage the slot's width again — which is
 * the reflow this rule exists to stop. The exemption the old selector spelled `:not(img)` carried
 * that weight by accident; this carries it on purpose. */
.ds-addon-card__media > :first-child,
.ds-tour-card__media > :first-child { flex: none; width: var(--ds-footage-width); max-width: none; min-height: var(--ds-footage-height, 0); zoom: tan(atan2(100cqw, var(--ds-footage-width))); }
.ds-tour-card__title { grid-row: 2; margin: 0; }
.ds-tour-card__body { grid-row: 3; margin: 0; color: var(--ds-ink-muted); }

/* One column: the cards stack, so the row gap between two cards has to be the card gap again. */
@media (max-width: 599px) {
  .ds-pcards--tour .ds-pcards__grid { display: flex; flex-direction: column; gap: var(--ds-card-gap); }
  .ds-tour-card { display: flex; flex-direction: column; }
}

/* ── Checklist ─────────────────────────────────────────────────────────────────────────────── */

/* Heading over a tinted panel over a footnote, all centred: the panel is 560 wide on the canvas and
   sits on the field, not off it. */
.ds-checklist-block { display: flex; flex-direction: column; align-items: center; gap: 56px; text-align: center; }
.ds-checklist-block__heading { display: flex; flex-direction: column; align-items: center; gap: 16px; max-width: 760px; }
.ds-checklist-block__eyebrow { color: var(--ds-accent-ink, var(--ds-accent)); }
.ds-checklist-block__title { margin: 0; }
.ds-checklist-block__intro { margin: 0; max-width: 640px; color: var(--ds-ink-body); }
.ds-checklist { display: flex; flex-direction: column; gap: 14px; width: 100%; max-width: 560px; margin: 0; padding: 32px; text-align: start; background: color-mix(in srgb, currentColor 6%, transparent); border-radius: var(--ds-radius-card); box-shadow: inset 0 0 0 1px color-mix(in srgb, currentColor 15%, transparent); }
.ds-checklist__title, .ds-checklist__intro { margin: 0; }
.ds-checklist__intro { color: var(--ds-ink-body); }
.ds-checklist__list { display: flex; flex-direction: column; gap: 8px; margin: 4px 0 0; padding: 0; list-style: none; }
/* The canvas sets the tick as a 14-wide glyph 10 from the text; the 16 vector and an 8 gap put the
   text at the same 24. */
.ds-checklist__item { display: flex; gap: 8px; align-items: flex-start; color: inherit; }
.ds-checklist__tick { width: var(--ds-icon-16); height: var(--ds-icon-16); margin-block-start: calc((1lh - 16px) / 2); color: var(--ds-accent-ink, var(--ds-accent)); }
.ds-checklist__footer { max-width: 640px; margin: 0; color: var(--ds-ink-muted); }
/* Centred, the whole way down: heading, the line about leaving, the accountability panel and the
   security row. The two cards are the only left-aligned things in it. */
.ds-partnership { display: flex; flex-direction: column; align-items: center; gap: 56px; }
.ds-partnership > * { width: 100%; }
.ds-partnership__heading { display: flex; flex-direction: column; align-items: center; gap: 16px; text-align: center; }
.ds-partnership__eyebrow, .ds-partnership__security-label { color: var(--ds-accent-ink, var(--ds-accent)); }
.ds-partnership__title { margin: 0; max-width: 24ch; }
.ds-partnership__intro { margin: 0; max-width: 62ch; color: var(--ds-ink-body); }

.ds-yours-ours { display: grid; gap: var(--ds-card-gap); margin: 0; padding: 0; list-style: none; }
@media (min-width: 700px) { .ds-yours-ours { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
/* A panel on the field, not a card off it: 6% of the ink over the ground, with a hairline at 15%.
   Painting it in `--ds-surface` made two white cards on a deep purple band, which the canvas does
   not do anywhere in the product set. */
.ds-yours-ours__card { display: flex; flex-direction: column; align-items: flex-start; gap: 12px; padding: var(--space-md); background: color-mix(in srgb, currentColor 6%, transparent); border-radius: var(--ds-radius-card); box-shadow: inset 0 0 0 1px color-mix(in srgb, currentColor 15%, transparent); }
.ds-yours-ours__chip { background: color-mix(in srgb, currentColor 12%, transparent); box-shadow: none; }
.ds-yours-ours__title, .ds-yours-ours__body { margin: 0; }
.ds-yours-ours__body { color: var(--ds-ink-body); }

/* The line about leaving sits under both cards, because it is about both of them. */
.ds-partnership__exit { max-width: 640px; margin: 0 auto; text-align: center; color: var(--ds-ink-muted); }
.ds-partnership__accountability { display: flex; flex-direction: column; align-items: center; gap: 24px; width: 100%; max-width: 720px; margin-inline: auto; padding: var(--space-md); text-align: center; border-radius: var(--ds-radius-card); box-shadow: inset 0 0 0 1px color-mix(in srgb, currentColor 20%, transparent); }
.ds-partnership__statement { margin: 0; max-width: 46ch; }
.ds-partnership__chips { display: flex; flex-wrap: wrap; justify-content: center; gap: 8px; margin: 0; padding: 0; list-style: none; }
/* The accountability chips are outlined, never filled: a 30% hairline and 6/10 inside, as the master draws them. */
.ds-partnership__chips .ds-chip { padding: 6px 10px; box-shadow: inset 0 0 0 1px color-mix(in srgb, currentColor 30%, transparent); }

/* ── Trade chip row ────────────────────────────────────────────────────────────────────────── */

/* Chips hug their labels and wrap by whole chips; 8 between them as the master spaces them. */
.ds-trade-chips { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin: 0; padding: 0; list-style: none; }
.ds-trade-chips--center { justify-content: center; }
/* The icon is the quiet part of a trade chip; the hover in motion.css turns it to the accent. */
.ds-trade-chip .ds-chip__icon { color: var(--ds-ink-muted); }
.ds-trade-chip.ds-chip--accent .ds-chip__icon { color: inherit; }
.ds-partnership__security { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 10px 32px; }
.ds-partnership__security-list { display: flex; flex-wrap: wrap; justify-content: center; gap: 10px 20px; margin: 0; padding: 0; list-style: none; color: var(--ds-ink-body); }
.ds-partnership__security-item { display: inline-flex; align-items: center; gap: 8px; }
/* The vendor's own mark before its name, as the master's Vendor mark instances and the live row carry it. */
.ds-partnership__security-mark { display: inline-flex; flex: none; width: 20px; height: 20px; }

/* ── Get started ───────────────────────────────────────────────────────────────────────────── */

.ds-getstarted__inner { display: grid; gap: var(--space-lg); align-items: start; }
@media (min-width: 1000px) { .ds-getstarted__inner { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: var(--space-xl); } }
.ds-getstarted__copy { display: flex; flex-direction: column; gap: 16px; min-width: 0; }
/* Get started draws the same mark 16 under its label, which is what its master has. */
.ds-getstarted__eyebrow-row { --ds-peyebrow-gap: 16px; }
.ds-getstarted__eyebrow { color: var(--ds-accent-ink, var(--ds-accent)); }
.ds-getstarted__title { margin: 0; max-width: 20ch; }
.ds-getstarted__body { margin: 0; max-width: 52ch; color: var(--ds-ink-body); }
.ds-getstarted__booking { align-self: flex-start; color: var(--ds-accent-ink, var(--ds-accent)); }
/* The timeline: what happens after they send it, marked by the brand's own bullet. */
.ds-getstarted__timeline { display: flex; flex-direction: column; gap: 14px; margin: 6px 0 0; padding: 0; list-style: none; }
.ds-getstarted__step { position: relative; padding-inline-start: 18px; }
.ds-getstarted__step::before { content: ''; position: absolute; inset-inline-start: 0; inset-block-start: 8px; width: 5px; height: 5px; border-radius: 50%; background: var(--ds-accent-ink); }
.ds-getstarted__step-title { display: block; }
.ds-getstarted__step-body { margin: 2px 0 0; color: var(--ds-ink-body); }
/* The form sits on its own card, which is what lifts it off the ground behind it. */
.ds-getstarted__form { min-width: 0; padding: var(--space-md); background: var(--ds-surface); color: var(--ds-ink); border-radius: var(--ds-radius-card); box-shadow: inset 0 0 0 1px var(--ds-rule); }
/* The card is the section's, so the form inside it does not draw its own. */
.ds-getstarted__form > .ds-form { max-width: none; padding: 0; background: none; box-shadow: none; }
/* Stripped of the card, the sent panel is a bare region again, so it pads itself clear of the ring. */
.ds-getstarted__form > .ds-form.ds-form--done { box-sizing: content-box; padding: var(--ds-focus-inset); margin: calc(-1 * var(--ds-focus-inset)); }

/* ── Showcase ──────────────────────────────────────────────────────────────────────────────── */

.ds-showcase-block { display: flex; flex-direction: column; gap: var(--ds-heading-gap); }
.ds-showcase__heading { display: grid; gap: 12px; }
@media (min-width: 1000px) { .ds-showcase__heading { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: var(--space-xl); align-items: start; } }
.ds-showcase__title { margin: 0; max-width: 20ch; }
.ds-showcase__intro { margin: 0; max-width: 43ch; color: var(--ds-ink-body); }

/* Six slots, and they are rows of the row rather than of the card, so a body that runs to four
   lines in one card moves every card's link and list down with it. */
.ds-showcase { display: grid; row-gap: 12px; column-gap: var(--ds-card-gap); margin: 0; padding: 0; list-style: none; }
@media (min-width: 700px) { .ds-showcase { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 1000px) { .ds-showcase { grid-template-columns: repeat(var(--ds-showcase-cols, 3), minmax(0, 1fr)); } }
/* The canvas insets the media: a 24px card with a 324x296 window inside it, not a full-bleed header. */
.ds-showcase__item { display: grid; grid-row: span 6; grid-template-rows: subgrid; gap: 12px; min-width: 0; padding: var(--space-sm); background: var(--ds-surface); color: var(--ds-ink); border-radius: var(--ds-radius-card); box-shadow: inset 0 0 0 1px var(--ds-rule); }
/* A picture of somebody's real site lifts under the pointer and casts the card depth while it does;
   the rise itself is in motion.css with the other hover moves. Behind `hover: hover`, so a touch
   device never holds a state it cannot leave. */
@media (hover: hover) {
  .ds-showcase__item:hover { box-shadow: inset 0 0 0 1px var(--ds-rule), var(--ds-shadow-card); }
}
.ds-showcase__copy { display: contents; }
/* A window on the planned site, not a picture of one: the composition reflows to the card's width,
   fills the window, and whatever runs past 296 is cropped by it — the master's own `media` frame,
   324x296 (294x296 on a phone) and clipping, with the composition set to fill it. 296 is the height
   that ends the shop's window between its Availability and Brand facets, never through a line. The slot used to hold a capture at
   `aspect-ratio: 16 / 9` with `object-fit: contain`, which barred out every shot that was not that
   shape; a fixed shape with `contain` inside it is the letterbox itself. */
.ds-showcase__shot { grid-row: 1; display: block; min-width: 0; height: 296px; overflow: clip; border-radius: var(--ds-radius-card); background: var(--ds-surface); color: var(--ds-ink); }
/* In the flow, so every gate that reads the window's crop reads it the same way; the window's own
   height is what holds the card's rows in step, whatever the composition's length. */
.ds-showcase__shot > * { display: block; width: 100%; min-width: 0; height: auto; min-height: 100%; }
/* A composition in the window is the window's own content, edge to edge: an app card keeps no
   corner, no hairline and no inset of its own there, and a deep block inside it runs the full width,
   so the three windows of a row carry the same weight whichever composition each one holds. */
.ds-showcase__shot > .ds-app-card { border-radius: 0; box-shadow: none; }
.ds-showcase__shot > .ds-app-card > .ds-app-deep { border-radius: 0; }
.ds-showcase__kind { grid-row: 2; color: var(--ds-accent-ink, var(--ds-accent)); }
.ds-showcase__name { grid-row: 3; margin: 0; }
.ds-showcase__body { grid-row: 4; margin: 0; color: var(--ds-ink-muted); }
.ds-showcase__link { grid-row: 5; justify-self: start; align-self: end; }
/* What it comes with sits at the foot of the card, so the lists line up across the row. The label is
   the quiet one on the canvas — muted, not accent — so the accent stays with the kind, the link and
   the ticks. */
.ds-showcase__includes { grid-row: 6; display: flex; flex-direction: column; gap: 8px; align-self: start; padding-block-start: 12px; border-block-start: 1px solid var(--ds-rule); }
.ds-showcase__list-label { color: var(--ds-ink-muted); }
.ds-showcase__list { display: flex; flex-direction: column; gap: 8px; margin: 0; padding: 0; list-style: none; }
.ds-showcase__inc { display: flex; gap: 8px; color: var(--ds-ink-body); }
/* A 20px slot for the tick, as the canvas draws it: the 16 vector and 4 of margin before the 8 gap. */
.ds-showcase__tick { width: var(--ds-icon-16); height: var(--ds-icon-16); margin-block-start: calc((1lh - 16px) / 2); margin-inline-end: 4px; color: var(--ds-accent-ink, var(--ds-accent)); }
/* Below 700 the row is a column and so is each card. This sits after the grid rules on purpose: at
   equal specificity the later rule wins, and the two overrides below have to beat them. */
@media (max-width: 699px) {
  .ds-showcase { display: flex; flex-direction: column; gap: var(--ds-card-gap); }
  .ds-showcase__item { display: flex; flex-direction: column; }
  /* In the column the grid's `align-self: end` would act on the cross axis and push the link to the
     right edge; it stays on the left like every other line of the card. */
  .ds-showcase__link { align-self: start; }
  /* Likewise the list block: its `align-self: start` is the grid's, and in the column it would shrink
     the hairline to the longest line. */
  .ds-showcase__includes { align-self: stretch; }
}

/* ── Industry row ──────────────────────────────────────────────────────────────────────────── */

/* Half the width each and the photograph to the edge: a 1440×420 band on the canvas, the copy in
   the other half with 86px above it and its measure held at 526. */
.ds-industry { display: grid; gap: 0; align-items: stretch; }
@media (min-width: 900px) {
  .ds-industry { grid-template-columns: repeat(2, minmax(0, 1fr)); min-height: 420px; }
  /* The photograph swaps sides without the copy ever changing place in the document. */
  .ds-industry--right .ds-industry__photo { order: 2; }
  .ds-industry--left .ds-industry__copy { padding: 86px 130px 64px 64px; }
  .ds-industry--right .ds-industry__copy { padding: 86px 64px 64px 130px; }
}
.ds-industry__photo { --ds-aspect: auto; width: 100%; min-height: 280px; }
.ds-industry__photo > img { height: 100%; }
.ds-industry__copy { display: flex; flex-direction: column; justify-content: flex-start; gap: 12px; min-width: 0; padding: 48px 24px; }
.ds-industry__eyebrow::after { content: ''; display: block; width: 32px; height: 2px; margin-block-start: 12px; background: currentColor; }
.ds-industry__title, .ds-industry__body { margin: 0; max-width: 526px; }
.ds-industry__body { color: var(--ds-ink-body); }
/* The eyebrow names the trade and the tag line says what that trade keeps — both in the accent's
   ink, because they are the labels this row is filed under. */
.ds-industry__eyebrow, .ds-industry__tag { color: var(--ds-accent-ink, var(--ds-accent)); }
.ds-industry__eyebrow { margin: 0; }
.ds-industry__tag { margin: 0; color: var(--ds-ink-muted); }
.ds-industry__points { display: flex; flex-direction: column; gap: 6px; margin: 0; padding-inline-start: 1.1em; color: var(--ds-ink-body); }

/* ── Vendors ───────────────────────────────────────────────────────────────────────────────── */

/* Two claims on one row: what it runs on, and what it works with. They stay separated by their
   own labels and a hairline rather than merging into one wall of logos. Below the desktop fold the
   master stacks them: caption on its own line, marks two to a row, the hairline across the column.
   The fold is the component's own width, not the viewport's: the same row sits in a full-bleed band
   and in one column of a pair, and a viewport query left an upright tick at the start of a wrapped
   line whenever the container was narrower than the window. */
.ds-vendors { display: flex; flex-direction: column; gap: 24px; container: ds-vendors / inline-size; }
.ds-vendors__row { display: flex; flex-direction: column; flex-wrap: nowrap; gap: 24px; }
.ds-vendors__group { display: flex; flex-direction: column; flex-wrap: nowrap; align-items: stretch; gap: 24px; }
.ds-vendors__caption { color: var(--ds-ink-muted); }
/* The divider is drawn in the caption's ink, as the master draws it — not the surface hairline. */
.ds-vendors__group + .ds-vendors__group::before { content: ''; flex: none; width: 100%; height: 1px; background: var(--ds-ink-muted); }
/* Two columns at 390, and still two at 320. */
.ds-vendors__list { display: grid; grid-template-columns: repeat(auto-fill, minmax(130px, 1fr)); gap: 24px 16px; margin: 0; padding: 0; list-style: none; }
/* An item aligns to its first line, not its middle: the mark takes the name's line box (the 23px line
   of the body-sm face) with the 20px glyph centred in it, so a one-line name reads exactly as before
   and a name that wraps in a narrow column keeps its mark beside the first line and its slot level
   with its neighbours'. */
.ds-vendors__item { display: inline-flex; align-items: flex-start; gap: 8px; min-width: 0; color: var(--ds-ink-body); }
.ds-vendors__mark { display: inline-flex; flex: none; align-items: center; height: 23px; }
.ds-vendors__mark > * { height: 20px; width: auto; }
.ds-vendors__note { margin: 0; color: var(--ds-ink-muted); }
/* The Storefront master has no divider: each claim is a caption over its own row of marks, and the
   two rows stack. The brand is the scope, so the shape follows it rather than a prop. */
.ds-product[data-brand='storefront'] .ds-vendors__group + .ds-vendors__group::before { content: none; }
/* One row once the component is 1024 wide — the master's row is 981 across at its 32px gaps, so
   six marks and two captions are sure of a single line; the divider turns upright and sits in the
   row's own gap, and the row centres in its band the way the master and the live site do. */
@container ds-vendors (min-width: 1024px) {
  .ds-vendors__row { flex-direction: row; flex-wrap: wrap; justify-content: center; align-items: center; gap: 24px 32px; }
  .ds-vendors__group { flex-direction: row; flex-wrap: wrap; align-items: center; gap: 16px 32px; }
  .ds-vendors__group + .ds-vendors__group::before { width: 1px; height: 20px; }
  .ds-vendors__list { display: flex; flex-wrap: wrap; align-items: center; gap: 16px 32px; }
  .ds-vendors__note { text-align: center; }
  /* Storefront keeps its stacked shape at every width and centres each line. */
  .ds-product[data-brand='storefront'] .ds-vendors__row { flex-direction: column; flex-wrap: nowrap; align-items: center; gap: 24px; }
  .ds-product[data-brand='storefront'] .ds-vendors__group { flex-direction: column; flex-wrap: nowrap; align-items: center; gap: 24px; }
  .ds-product[data-brand='storefront'] .ds-vendors__caption { text-align: center; }
  .ds-product[data-brand='storefront'] .ds-vendors__list { justify-content: center; }
}

/* ── Walkthrough ───────────────────────────────────────────────────────────────────────────── */

/*
 * The stage. Every step occupies the same cell of the deck; the current one sits flat to the reader,
 * hands over by receding along the z axis as it fades, and the state it hands over from stays on as
 * the parked card behind the live one.
 *
 * The parked card is drawn INSIDE the live step (`.ds-walk__park`), not beside it, because that is
 * the only box a stylesheet can clamp it to. Steps are different heights — the Storefront shop is
 * 622 tall and the basket it ends on is 259 — and a sibling parked in the same cell keeps its own
 * height, so a 562 blurred ghost stood around a 294 live card on every side and read as a broken
 * picture rather than as depth (owner ruling of 17 September 2026: the previous state
 * stays visibly present behind the live one, clamped to its box). Held inside the live step and
 * clipped to it, the parked card can never be larger than what it sits behind, whatever the two
 * states measure, and the only part of it that shows is the strip the master lets peek above.
 *
 * Its recession is the master's own parked card: scale 0.94, opacity 0.5, blur 3, and 20px of it
 * above the live card's top — the 611-tall front card of "Stage · 3D card stack" puts the 0.94 card
 * 39 higher than centre, which is 20.67 above the front card's own top edge.
 *
 * It is a fixed CSS timeline. The SETTLED first step is the default, so a browser without animation,
 * or a reader who asked for reduced motion, gets the finished result — the first state with the last
 * one parked behind it — rather than an empty frame.
 */
.ds-walk { display: grid; gap: var(--space-lg); min-width: 0; }
.ds-walk__copy { display: flex; flex-direction: column; gap: 16px; min-width: 0; }
.ds-walk__eyebrow, .ds-walk__worth-label { color: var(--ds-accent-ink, var(--ds-accent)); }
.ds-walk__title { margin: 0; max-width: 20ch; }
.ds-walk__body { margin: 0; max-width: 52ch; color: var(--ds-ink-body); }
.ds-walk__worth { display: flex; flex-wrap: wrap; gap: 8px; margin: 0; padding: 0; list-style: none; }
/* A worth chip is the master's: paper-white on the field, the hairline, the set's chip radius. */
.ds-walk__worth-item { display: inline-flex; align-items: center; gap: 6px; padding: 5px 10px; border: 1px solid var(--ds-rule); border-radius: var(--ds-radius-chip); background: var(--ds-surface); color: var(--ds-ink); white-space: nowrap; }
.ds-walk__worth-arrow { color: var(--ds-accent-ink, var(--ds-accent)); }

.ds-walk__stage { position: relative; min-width: 0; perspective: var(--ds-walk-perspective, 1600px); perspective-origin: 50% 40%; }
.ds-walk__deck { display: grid; transform-style: preserve-3d; }
/* Every step in one cell, centred, so a short card does not sit with all of the slack under it while
   a taller one fills the frame. */
.ds-walk__deck > * { grid-area: 1 / 1; align-self: center; justify-self: center; }
/* `isolation` is what keeps the parked card inside the step: a negative z-index child paints inside
   its nearest ANCESTOR stacking context, so without one here the parked card would sink behind the
   deck and out of sight. The live step's own box is also the positioning box the parked card is
   clamped to, hence `position: relative`. */
.ds-walk__step { position: relative; isolation: isolate; width: 100%; max-width: 420px; min-width: 0; opacity: 0; }
.ds-walk__step:first-child { opacity: 1; }
/* The state this step took over from, parked behind it. Absolute and clipped to the live step's own
   box grown 20px upwards, so the whole of it but that strip is behind the card in front, however
   tall the two states are; scaled about that top edge, so the strip is the master's 20 and nothing
   else moves it. The transform scales the clipping box with the card, which is what holds the
   parked bottom edge inside the live one. */
.ds-walk__park { position: absolute; inset: -20px 0 0 0; z-index: -1; overflow: clip; pointer-events: none; opacity: 0.5; filter: blur(3px); transform: scale(0.94); transform-origin: top center; }
/* The screen is a positioning box and nothing more: what a step shows is an `AppCard` — the product's
   own interface, rounded and shadowed, drawn by the app kit below — and the card carries its own frame.
   The sheet used to frame a picture here the same way, for a site that had only a capture; the owner's
   ruling makes every walkthrough state a live fragment of the kit and the slot takes no picture, so
   the second frame and the second copy of the corner token are gone with it. The corner is stated once, on the slot, and the state inside it inherits it.
   Stated rather than clipped, because clipping the slot would eat the app shadow the card casts
   outside its box. */
.ds-walk__screen { position: relative; border-radius: var(--ds-app-radius); }

/* The pointer lives inside the step it presses, placed on the control by the markup, so it can
   never drift as a card's contents change. Outside a stage it does not draw. The target takes the
   card's corner because it lights up under the pointer the way a real row does on hover. */
.ds-walk__target { position: relative; isolation: isolate; display: block; border-radius: var(--ds-app-radius); }
/* The tint the pointer's press leaves is a layer of its own, faded in and out, and never a colour
   inside a keyframe. A keyframe that names a token is resolved ONCE, when the animation starts:
   Chrome runs a background-color animation off the main thread with that value frozen into the
   composited frames, so a page opened dark and switched to light keeps painting the dark tint on a
   white card for as long as the loop runs while getComputedStyle reports the light one — the defect
   is invisible to every computed-style check and shows only in pixels. Opacity carries no colour, so
   the layer's own background re-resolves with the theme like any other paint.
   `isolation` is what keeps the layer where it belongs: a negative z-index child paints inside its
   nearest ANCESTOR stacking context, so without one on the target the tint would sink behind the
   app card it is meant to light. Isolated, it paints behind the row's own content and nothing else. */
.ds-walk__target::before { content: ''; position: absolute; inset: 0; z-index: -1; border-radius: inherit; background: var(--ds-app-hover); opacity: 0; pointer-events: none; }
/* `fit-content` and `align-self`, not `inline-block` alone: inside the card's flex column a target is a
   flex item, which blockifies the display and stretches it to the card's width regardless. */
.ds-walk__target--fit { display: inline-block; width: fit-content; max-width: 100%; align-self: flex-start; }
/* In a row the fit target keeps the row's own cross-axis alignment: flex-start pinned a targeted
   button above its neighbours in a centred row (owner, 29 Sep 2026, the Cuelearn paused-lesson card). */
:is(.ds-app-row, .ds-app-across, .ds-app-tabs) > .ds-walk__target--fit { align-self: auto; }
.ds-walk__cursor { display: none; }

@media (min-width: 1000px) {
  .ds-walk { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: var(--space-xl); align-items: center; }
  .ds-walk--right .ds-walk__copy { order: 2; }
  .ds-walk__stage { min-height: 380px; display: grid; align-items: center; }
}

@media (prefers-reduced-motion: no-preference) {
  /* Each step holds for one slice of the period, then hands over: the step just finished is shoved
     back into the stack while the next arrives over the top of it. Both are on screen through the
     middle of the move, which is what makes it read as one screen replacing another. The next step
     rises over one beat; the step it replaces starts receding at that same instant and takes two
     beats to sink into the background, so through the whole rise it is still there, fading and
     dropping back behind the card coming over it, the way the previous sites handed over. Going out
     as fast as coming in read as a switch, and windows merely adjacent left the deck down to the
     parked copy for a third of a second on every step, which read as a broken picture. The last
     step's window runs past 100% for the same reason: it is still sinking as the first arrives.
     A step that is not being done goes all the way OUT rather than resting at a dimmed 0.34: what
     stands behind the live card is that card's own parked copy, clamped to it, and a sibling left
     hanging at a third of its strength is the oversized ghost this stack was rebuilt to stop. The
     recession and the blur stay, so the hand-over still reads as one card going back and another
     coming over it; only the resting frame is empty. Going out takes exactly as long as coming in —
     a slower fade would leave a taller card standing translucent around a shorter one for most of a
     second, which is the same picture in motion. */
  .ds-walk__step { animation: ds-walk-3 var(--ds-walk-period, 13500ms) cubic-bezier(0.2, 0, 0, 1) infinite backwards; animation-delay: calc(var(--ds-walk-i, 0) * var(--ds-walk-period, 13500ms) / 3); }
  .ds-walk__stage[data-states='1'] .ds-walk__step { animation: none; opacity: 1; }
  .ds-walk__stage[data-states='2'] .ds-walk__step { animation-name: ds-walk-2; animation-delay: calc(var(--ds-walk-i, 0) * var(--ds-walk-period, 9000ms) / 2); }
  .ds-walk__stage[data-states='4'] .ds-walk__step { animation-name: ds-walk-4; animation-delay: calc(var(--ds-walk-i, 0) * var(--ds-walk-period, 18000ms) / 4); }
  .ds-walk__stage[data-states='5'] .ds-walk__step { animation-name: ds-walk-5; animation-delay: calc(var(--ds-walk-i, 0) * var(--ds-walk-period, 22500ms) / 5); }
  @keyframes ds-walk-2 { 0% { opacity: 0; transform: translate3d(0, -12px, -170px); filter: blur(3px); } 5%, 50% { opacity: 1; transform: none; filter: blur(0); } 60%, 100% { opacity: 0; transform: translate3d(0, -12px, -170px); filter: blur(3px); } }
  @keyframes ds-walk-3 { 0% { opacity: 0; transform: translate3d(0, -12px, -170px); filter: blur(3px); } 4%, 33.33% { opacity: 1; transform: none; filter: blur(0); } 41.33%, 100% { opacity: 0; transform: translate3d(0, -12px, -170px); filter: blur(3px); } }
  @keyframes ds-walk-4 { 0% { opacity: 0; transform: translate3d(0, -12px, -170px); filter: blur(3px); } 3%, 25% { opacity: 1; transform: none; filter: blur(0); } 31%, 100% { opacity: 0; transform: translate3d(0, -12px, -170px); filter: blur(3px); } }
  @keyframes ds-walk-5 { 0% { opacity: 0; transform: translate3d(0, -12px, -170px); filter: blur(3px); } 2.5%, 20% { opacity: 1; transform: none; filter: blur(0); } 25%, 100% { opacity: 0; transform: translate3d(0, -12px, -170px); filter: blur(3px); } }

  /* One beat per step, on the step's own timeline: the pointer appears, travels to the control,
     presses it as the step hands over, and is gone before the next arrives. Every beat here waits
     out a delay, and `backwards` holds it on its 0% frame while it waits: without it a waiting
     pointer and ring sit at their base opacity of 1, one per step, all at once. */
  .ds-walk__stage .ds-walk__cursor { display: block; position: absolute; width: 24px; height: 24px; pointer-events: none; z-index: 2; animation: ds-walk-cursor-3 var(--ds-walk-period, 13500ms) cubic-bezier(0.3, 0, 0.1, 1) infinite backwards; animation-delay: calc(var(--ds-walk-i, 0) * var(--ds-walk-period, 13500ms) / 3); }
  /* The ring is the kit's own pointer (`.ds-app-pointer__ring`); the stage gives it the beat. */
  .ds-walk__stage .ds-app-pointer__ring { animation: ds-walk-click-3 var(--ds-walk-period, 13500ms) ease-out infinite backwards; animation-delay: calc(var(--ds-walk-i, 0) * var(--ds-walk-period, 13500ms) / 3); }
  .ds-walk__stage[data-states='2'] .ds-walk__cursor { animation-name: ds-walk-cursor-2; animation-delay: calc(var(--ds-walk-i, 0) * var(--ds-walk-period, 9000ms) / 2); }
  .ds-walk__stage[data-states='2'] .ds-app-pointer__ring { animation-name: ds-walk-click-2; animation-delay: calc(var(--ds-walk-i, 0) * var(--ds-walk-period, 9000ms) / 2); }
  .ds-walk__stage[data-states='4'] .ds-walk__cursor { animation-name: ds-walk-cursor-4; animation-delay: calc(var(--ds-walk-i, 0) * var(--ds-walk-period, 18000ms) / 4); }
  .ds-walk__stage[data-states='4'] .ds-app-pointer__ring { animation-name: ds-walk-click-4; animation-delay: calc(var(--ds-walk-i, 0) * var(--ds-walk-period, 18000ms) / 4); }
  .ds-walk__stage[data-states='5'] .ds-walk__cursor { animation-name: ds-walk-cursor-5; animation-delay: calc(var(--ds-walk-i, 0) * var(--ds-walk-period, 22500ms) / 5); }
  .ds-walk__stage[data-states='5'] .ds-app-pointer__ring { animation-name: ds-walk-click-5; animation-delay: calc(var(--ds-walk-i, 0) * var(--ds-walk-period, 22500ms) / 5); }
  .ds-walk__stage[data-states='1'] .ds-walk__cursor { display: none; }
  /* A parked state is a still. The pointer, its ring and the tint it leaves belong to the step being
     done, and inside the parked copy they would run on the live step's clock — a second cursor
     pressing a control nobody is looking at. Stated here rather than beside the parked card itself,
     because these are the beats this query starts and a rule that stops an animation belongs with
     the rule that begins one. */
  .ds-walk__stage .ds-walk__park .ds-walk__cursor { display: none; }
  .ds-walk__stage .ds-walk__park .ds-walk__target::before { animation: none; opacity: 0; }
  .ds-walk__stage .ds-walk__park .ds-app-pointer__ring { animation: none; }
  /* The control acknowledges the pointer: the row lights on the app's hover tint as the pointer lands
     and lets go as the step hands over. Same clock as the pointer, so the two cannot drift apart.
     The beat is carried by the tint layer's opacity, never by a colour in a keyframe — see the
     account on the layer itself. */
  .ds-walk__stage .ds-walk__target::before { animation: ds-walk-lit-3 var(--ds-walk-period, 13500ms) ease-out infinite backwards; animation-delay: calc(var(--ds-walk-i, 0) * var(--ds-walk-period, 13500ms) / 3); }
  .ds-walk__stage[data-states='2'] .ds-walk__target::before { animation-name: ds-walk-lit-2; animation-delay: calc(var(--ds-walk-i, 0) * var(--ds-walk-period, 9000ms) / 2); }
  .ds-walk__stage[data-states='4'] .ds-walk__target::before { animation-name: ds-walk-lit-4; animation-delay: calc(var(--ds-walk-i, 0) * var(--ds-walk-period, 18000ms) / 4); }
  .ds-walk__stage[data-states='5'] .ds-walk__target::before { animation-name: ds-walk-lit-5; animation-delay: calc(var(--ds-walk-i, 0) * var(--ds-walk-period, 22500ms) / 5); }
  .ds-walk__stage[data-states='1'] .ds-walk__target::before { animation: none; }
  @keyframes ds-walk-lit-2 { 0%, 34% { opacity: 0; } 40%, 46% { opacity: 1; } 52%, 100% { opacity: 0; } }
  @keyframes ds-walk-lit-3 { 0%, 21% { opacity: 0; } 26%, 31% { opacity: 1; } 36%, 100% { opacity: 0; } }
  @keyframes ds-walk-lit-4 { 0%, 16% { opacity: 0; } 19.5%, 23% { opacity: 1; } 27%, 100% { opacity: 0; } }
  @keyframes ds-walk-lit-5 { 0%, 12.5% { opacity: 0; } 15.5%, 18.5% { opacity: 1; } 22%, 100% { opacity: 0; } }
  @keyframes ds-walk-cursor-2 { 0% { translate: 34px 26px; opacity: 0; } 7%, 18% { translate: 34px 26px; opacity: 1; } 38%, 44% { translate: 0 0; opacity: 1; } 50%, 100% { translate: 0 0; opacity: 0; } }
  @keyframes ds-walk-cursor-3 { 0% { translate: 34px 26px; opacity: 0; } 6%, 12% { translate: 34px 26px; opacity: 1; } 24%, 30% { translate: 0 0; opacity: 1; } 34%, 100% { translate: 0 0; opacity: 0; } }
  @keyframes ds-walk-cursor-4 { 0% { translate: 34px 26px; opacity: 0; } 4%, 9% { translate: 34px 26px; opacity: 1; } 18%, 22.5% { translate: 0 0; opacity: 1; } 25.5%, 100% { translate: 0 0; opacity: 0; } }
  @keyframes ds-walk-cursor-5 { 0% { translate: 34px 26px; opacity: 0; } 3.5%, 7% { translate: 34px 26px; opacity: 1; } 14.5%, 18% { translate: 0 0; opacity: 1; } 20.5%, 100% { translate: 0 0; opacity: 0; } }
  @keyframes ds-walk-click-2 { 0%, 42% { opacity: 0; scale: 0.35; } 45% { opacity: 0.5; scale: 1; } 50%, 100% { opacity: 0; scale: 1.15; } }
  @keyframes ds-walk-click-3 { 0%, 28% { opacity: 0; scale: 0.35; } 30% { opacity: 0.5; scale: 1; } 34%, 100% { opacity: 0; scale: 1.15; } }
  @keyframes ds-walk-click-4 { 0%, 21% { opacity: 0; scale: 0.35; } 22.5% { opacity: 0.5; scale: 1; } 25.5%, 100% { opacity: 0; scale: 1.15; } }
  @keyframes ds-walk-click-5 { 0%, 17% { opacity: 0; scale: 0.35; } 18% { opacity: 0.5; scale: 1; } 20.5%, 100% { opacity: 0; scale: 1.15; } }
}

/* ── Cutouts ───────────────────────────────────────────────────────────────────────────────── */

/* `min-width: 0` down the chain — figure, frame, card — because a built cutout holds a sentence that
   does not wrap, and a grid item or flex item left at its automatic minimum would grow to fit it and
   push the whole pair past a phone's edge instead of letting the sentence truncate. */
.ds-cutout { margin: 0; display: flex; flex-direction: column; gap: 10px; min-width: 0; }
/* A cutout is an object set down on the page, so the frame casts the card depth on top of its ring. */
.ds-cutout__frame { aspect-ratio: 574 / 360; overflow: hidden; background: var(--ds-cutout-surface, var(--cutout-card)); color: var(--ds-ink); border-radius: var(--ds-radius-card); box-shadow: inset 0 0 0 1px var(--ds-cutout-rule, var(--cutout-border)), var(--ds-shadow-card); }
.ds-cutout__frame > * { display: block; width: 100%; height: 100%; object-fit: contain; }
/* Raised is lifted, not outlined: a cutout of the app never rings itself in the page's strong
   hairline, so the depth does the lifting and the ring stays the app's own. */
.ds-cutout--raised .ds-cutout__frame { box-shadow: inset 0 0 0 1px var(--ds-cutout-rule, var(--cutout-border)), var(--ds-shadow-card); }
.ds-cutout__copy { display: flex; flex-direction: column; gap: 2px; }
.ds-cutout__caption { color: var(--ds-ink-muted); }
/* The heading grid the canvas puts over a pair: the h2 on the left, the intro on the right at a
   43-character measure. */
.ds-cutout-block { display: flex; flex-direction: column; gap: var(--ds-heading-gap); }
.ds-cutout-block__heading { display: grid; gap: 16px; }
@media (min-width: 900px) { .ds-cutout-block__heading { grid-template-columns: minmax(0, 560px) minmax(0, 420px); justify-content: space-between; align-items: start; } }
.ds-cutout-block__title { margin: 0; }
.ds-cutout-block__intro { margin: 0; max-width: 43ch; color: var(--ds-ink-body); }
/* Two cards of equal weight, level with each other, 32 apart. */
/* A built cutout — an `AppCard` from the kit — carries its own corner and shadow, so the frame steps
   aside: no aspect ratio, no hairline, no field. The frame becomes a flex box the card stretches to
   fill, and the pair's columns stretch rather than start-align, so two built cards holding different
   amounts sit one height, the way the canvas draws two 360-high cards. */
.ds-cutout--built .ds-cutout__frame { display: flex; flex: 1; aspect-ratio: auto; overflow: visible; background: transparent; box-shadow: none; }
.ds-cutout--built .ds-cutout__frame > * { height: auto; min-width: 0; object-fit: fill; }
.ds-cutout-pair { display: grid; grid-template-columns: minmax(0, 1fr); gap: 32px; }
/* Even columns are the canvas's pair. `lead` gives one card the wider column at 1.35:1, as the live
   product site sets an agent's basket beside a customer's sign-in: the denser state gets the room. */
@media (min-width: 700px) {
  .ds-cutout-pair { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .ds-cutout-pair--lead-back { grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr); }
  .ds-cutout-pair--lead-front { grid-template-columns: minmax(0, 1fr) minmax(0, 1.35fr); }
}

/* ── App kit ───────────────────────────────────────────────────────────────────────────────── */
/*
 * The product's own interface at interface scale, for the cutouts a walkthrough is made of. A cutout
 * is footage of the planned client UI, not a piece of website chrome, so this is the one family in
 * the library that is rounded, dense and shadowed: the product-radius card on a 1px hairline with the
 * two-layer shadow, status pills on the product chip corner, tabular mono figures, a 4px thumb. Every colour is an
 * app role token from the product theme, and the card restores the page's ink ladder — the app is in
 * dark mode when the page is, whatever field is behind it. The sizes are the live client's, recorded here.
 * Page 15 sets its interface styles to `auto`, so every leading here is the height the canvas
 * MEASURES for that face and size — a line of Inter 12 stands 15, of `ui/14` 17 — never one chosen
 * to sit above it; `scripts/app-kit-leading.test.mjs` reads those heights off the scenes and holds
 * the section to them (owner ruling, 17 Sep 2026).
 */
/* The padding and the gap are the master's own measurements, not a wide-or-narrow flag: page 15
   draws cards at 0, 12, 16 and 20 of padding and spaces their blocks by 0, 12 or 16, and the two
   vary independently — the add-ons alone take 12 of padding at three different gaps. The card
   states one only where its master differs from these. */
.ds-app-card { position: relative; display: flex; flex-direction: column; gap: var(--ds-app-gap, 12px); width: 100%; container: ds-app-card / inline-size; padding: var(--ds-app-pad, 16px); border-radius: var(--ds-app-radius); background: var(--ds-app-surface); color: var(--ds-ink); box-shadow: inset 0 0 0 1px var(--ds-app-border), var(--ds-app-shadow); font-family: var(--ds-app-face); font-size: 13px; line-height: 16px; font-weight: 500; text-align: start; }
/* A photo band that OPENS a cutout runs to the card's own edges: the canvas draws the catalogue's
   card at padding 0 with the band flush and the content inset under it, and a band held off the
   corner by 16px reads as a picture in a card rather than the head of a screen. It pulls back by
   whichever padding its card takes, which on a flush card is nothing to pull back by. */
.ds-app-card > .ds-app-band:first-child { margin: calc(-1 * var(--ds-app-pad, 16px)) calc(-1 * var(--ds-app-pad, 16px)) 0; }
/* A block the master leaves more or less room above than the card's own gap. The canvas spells the
   rhythm of a cutout as frames — an `mt-12` around a block, a `rule` frame padded twelve either side,
   an `intro` holding two blocks eight apart — and a record flattens those frames away, so a card
   that stated only its root frame's gap abutted every block in it. The block states the WHOLE space
   above it and this takes the card's gap back off, so the two never have to be read together. */
.ds-app-space { display: flex; flex-direction: column; min-width: 0; margin-block-start: calc(var(--ds-app-space) - var(--ds-app-gap, 12px)); }
.ds-app-card__head { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; }
.ds-app-card__title-row { display: flex; align-items: center; gap: 8px; }
.ds-app-card__title { margin: 0; font-size: 14px; font-weight: 600; line-height: 17px; color: var(--ds-ink); }
.ds-app-card__meta { margin: 2px 0 0; font-size: 12px; font-weight: 400; line-height: 15px; color: var(--ds-ink-muted); }
.ds-app-card__action { flex: none; font-size: 12px; font-weight: 500; line-height: 15px; color: var(--ds-accent-ink); }

/* Every figure is mono and tabular. JetBrains Mono has no naira sign, and the fallback face draws
   it about 30% too tall; Inter carries the glyph at the digits' height, so the sign is set in the
   app face at 1.018em — both numbers measured from the rendered faces, not chosen. */
.ds-app-figure { font-family: var(--ds-app-figure); font-variant-numeric: tabular-nums; letter-spacing: -0.01em; }
.ds-app-figure--strong { font-weight: 600; color: var(--ds-ink); }
.ds-app-naira { font-family: var(--ds-app-face); font-size: 1.018em; padding-inline-end: 0.06em; }

/* The chip's box is the master's: `UI kit / Status chip` pads 2 either side of a line, and that line
   is 13 in Inter and 15 in the mono, which is what `auto` gives each face — so the chip stands 17
   as a word and 19 as a figure. It took a leading of its own, and one leading cannot be both: the
   chip stood 20 wherever a cutout drew it and 18 wherever a page drew the same master through
   `StatusChip`, neither of them a height the canvas has. The two heights are stated here and on
   `.ds-kit-status` from the same pair of numbers, and `scripts/status-chip-box.test.mjs` reads
   both back off the master's own scene. */
.ds-app-pill { display: inline-flex; flex: none; align-items: center; gap: 4px; padding: 2px 8px; border-radius: var(--ds-radius-chip); background: var(--ds-app-sunken); color: var(--ds-ink-muted); font-size: 11px; font-weight: 500; line-height: 13px; white-space: nowrap; }
.ds-app-pill--figure { line-height: 15px; }
.ds-app-pill--ok { background: var(--ds-app-ok-field); color: var(--ds-app-ok); }
.ds-app-pill--warn { background: var(--ds-app-warn-field); color: var(--ds-app-warn); }
.ds-app-pill--bad { background: var(--ds-app-bad-field); color: var(--ds-app-bad); }
.ds-app-pill--brand { background: var(--ds-app-brand-field); color: var(--ds-app-brand-ink); }

/* The product's primary control at interface scale. It is a picture of a button: the stage is
   footage, hidden from assistive technology, so it renders as a span and never as a control that
   does nothing. */
.ds-app-button { display: inline-flex; align-items: center; justify-content: center; gap: 6px; padding: 8px 14px; border-radius: var(--ds-app-radius); background: var(--ds-accent); color: var(--ds-ink-on-accent-control); font-size: 13px; font-weight: 600; line-height: 16px; white-space: nowrap; }
.ds-app-button--quiet { background: var(--ds-app-surface); color: var(--ds-ink); box-shadow: inset 0 0 0 1px var(--ds-app-border); }

.ds-app-list { display: flex; flex-direction: column; margin: 0; padding: 0; list-style: none; }
.ds-app-row { display: flex; align-items: center; gap: 12px; padding: 10px 8px; border-radius: var(--ds-app-radius); }
.ds-app-row--lit { background: var(--ds-app-hover); }
.ds-app-row--flush { padding: 0; }
/* The canvas's `aa: MIN`: a row whose glyph sits level with the title rather than centred on the
   block under it. A 16px mark centred against a title and two lines of meta lands half a line below
   the words it belongs to, which is what the mission, the vision and the mailbox read like. */
.ds-app-row--top { align-items: flex-start; }
/* The mark such a row leads with sits ON the title's line, not at the top edge of it: the canvas
   draws the dot in a slot the height of one line and centres it there, which a flex-start row would
   hang six pixels high. A centred row reads the same either way, so the slot is the topped row's. */
.ds-app-row--top > .ds-app-dot { margin-block-start: calc((16px - 6px) / 2); }
.ds-app-row__main { display: flex; flex: 1; flex-direction: column; gap: 2px; min-width: 0; }
.ds-app-row__title { display: block; font-size: 13px; font-weight: 500; line-height: 16px; color: var(--ds-ink); overflow-wrap: anywhere; }
/* `UI kit / List row`'s own two lines: `product/row/13` over `product/meta/11`. A line the master sets
   in another role names it (`titleRole`, `metaRole`), and that run's rule, later in the file, wins. */
.ds-app-row__meta { display: block; font-size: 11px; font-weight: 400; line-height: 13px; color: var(--ds-ink-muted); }
.ds-app-row__end { display: inline-flex; flex: none; align-items: center; gap: 8px; }
/* A trailing the canvas stacks rather than lays across: a menu's price over whether it is serving,
   a room's rate over "per night", right-aligned, at the gap the master's own frame leaves. */
.ds-app-row__end--stack { flex-direction: column; align-items: flex-end; gap: var(--ds-app-row-stack, 0px); }
/* A meta that leads with a glyph: the room's guests, four pixels after the mark. */
.ds-app-row__meta-line { display: inline-flex; align-items: center; gap: 4px; }
/* The sunken panel a card sets a record down on, the hairline between rows, and the label–value list. */
.ds-app-panel { display: flex; flex-direction: column; gap: var(--ds-app-gap, 12px); padding: 12px; border-radius: var(--ds-app-radius); background: var(--ds-app-sunken); color: var(--ds-ink); }
/* The same frame drawn the two other ways the masters draw it: a hairline round it and no
   field at all — the menu's order, the checkout's address, an enquiry — and the one the
   interface has picked out, which takes the brand stroke over the hover field exactly as a
   selected option card does. */
.ds-app-panel--bordered { background: none; box-shadow: inset 0 0 0 1px var(--ds-app-border); }
.ds-app-panel--selected { background: var(--ds-app-hover); box-shadow: inset 0 0 0 1px var(--ds-accent); }
.ds-app-rule { height: 1px; background: var(--ds-app-border); }
.ds-app-facts { display: flex; flex-direction: column; gap: 6px; margin: 0; font-size: 12px; line-height: 16px; }
.ds-app-facts__row { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.ds-app-facts__label { color: var(--ds-ink-muted); }
.ds-app-facts__value { margin: 0; color: var(--ds-ink-body); }

.ds-app-line { display: inline-flex; align-items: center; gap: 6px; font-size: 11px; font-weight: 400; line-height: 13px; color: var(--ds-ink-muted); }
.ds-app-line--meta { font-size: 12px; line-height: 15px; }
.ds-app-line--ok { color: var(--ds-app-ok); }
.ds-app-line--title { font-size: 13px; font-weight: 600; line-height: 16px; color: var(--ds-ink); }
.ds-app-line__icon { display: inline-flex; flex: none; width: 14px; height: 14px; }
.ds-app-line__icon > svg { display: block; width: 100%; height: 100%; }
.ds-app-screen { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; }
.ds-app-screen__block { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.ds-app-screen__title { margin: 0; font-family: var(--ds-app-display); font-size: 21px; font-weight: 800; line-height: 22px; letter-spacing: -0.022em; color: var(--ds-ink); }
.ds-app-screen__meta { margin: 0; font-size: 13px; font-weight: 400; line-height: 16px; color: var(--ds-ink-muted); }
.ds-app-screen__period { display: inline-flex; flex: none; align-items: center; padding: 6px 12px; border-radius: var(--ds-app-radius); box-shadow: inset 0 0 0 1px var(--ds-app-border); font-size: 13px; line-height: 17px; color: var(--ds-ink-body); white-space: nowrap; }

/* The note the interface leaves beside a record, on the same tint a row lights with, and the fact
   one label makes with one figure. A strong fact drops its label to 12 and muted so the figure
   carries the line; a plain one sets both in the body ink at 13. The `Rule` axis of the canvas's
   key-value row is the kit's own hairline — `.ds-app-rule` between two rows, not a border on one. */
/* The callout is a grid rather than a column because the master's `Tone=Ok` puts its figure BESIDE
   the sentence: `text` fills the row and `trailing` hugs the right edge, 8 apart. Appended inside the
   body instead, the figure butted onto the last word and the pair read as one run of text. The title
   spans both tracks, so a note that opens with one is unchanged. */
.ds-app-callout { display: grid; grid-template-columns: minmax(0, 1fr); gap: 4px; padding: 12px; border-radius: var(--ds-app-radius); background: var(--ds-app-hover); }
.ds-app-callout__title { grid-column: 1 / -1; margin: 0; font-size: 12px; font-weight: 600; line-height: 15px; color: var(--ds-accent-ink); }
.ds-app-callout__body { min-width: 0; margin: 0; font-size: 12px; font-weight: 400; line-height: 15px; color: var(--ds-ink-muted); }
.ds-app-callout__body > * { margin: 0; }
.ds-app-callout--trailing { grid-template-columns: minmax(0, 1fr) auto; column-gap: 8px; align-items: center; }
.ds-app-callout__trailing { justify-self: end; font-family: var(--ds-app-figure); font-variant-numeric: tabular-nums; letter-spacing: -0.01em; font-size: 11px; font-weight: 500; line-height: 15px; color: var(--ds-ink); }
/* `Tone=Sunken` and `Tone=Total` are key-value ROWS, not notes: the master draws two hugging layers
   pushed to either edge, 14 in and 12 apart, and no title layer for either tone. The body is the key
   and `trailing` the figure. Sunken centres the pair on the sunken field; Total sits the label and
   its larger figure on one bottom line, on the hover tint. */
.ds-app-callout--sunken, .ds-app-callout--total { grid-template-columns: minmax(0, 1fr) auto; column-gap: 12px; padding: 14px; }
.ds-app-callout--sunken { align-items: center; background: var(--ds-app-sunken); }
.ds-app-callout--sunken .ds-app-callout__body { font-size: 13px; font-weight: 400; line-height: 16px; color: var(--ds-ink-body); }
.ds-app-callout--sunken .ds-app-callout__trailing { font-family: var(--ds-app-figure); font-size: 16px; font-weight: 600; line-height: 21px; color: var(--ds-ink); }
.ds-app-callout--total { align-items: end; }
.ds-app-callout--total .ds-app-callout__body { font-size: 14px; font-weight: 600; line-height: 17px; color: var(--ds-ink); }
.ds-app-callout--total .ds-app-callout__trailing { font-family: var(--ds-app-figure); font-size: 17px; font-weight: 600; line-height: 22px; color: var(--ds-accent-ink); }
/* A role the composition states for either side sets its own line box, not a run inside the tone's. */
.ds-app-callout__body > [class*='ds-app-run--'], .ds-app-callout__trailing > [class*='ds-app-run--'] { display: block; }
/* `Tone=Ok` is a line across, not a note: 10 above and below, 14 at the sides, the check 14 square
   8 before an 11px sentence and the figure after it. The check is in the line's own flow, so a line
   of 13px words still stands the check's 14 tall, as the master does. */
.ds-app-callout--ok { display: flex; align-items: center; gap: 8px; padding: 10px 14px; background: var(--ds-app-ok-field); }
.ds-app-callout--ok .ds-app-callout__body { flex: 1; font-size: 11px; line-height: 13px; color: var(--ds-app-ok); }
.ds-app-callout--ok .ds-app-callout__trailing { flex: none; color: var(--ds-app-ok); }
.ds-app-callout__mark { display: grid; flex: none; place-items: center; width: 14px; height: 14px; color: var(--ds-app-ok); }
.ds-app-callout__mark > svg { display: block; width: 100%; height: 100%; }
.ds-app-kv { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding-block: 8px; }
.ds-app-kv__key { font-size: 13px; font-weight: 400; line-height: 16px; color: var(--ds-ink-body); }
.ds-app-kv__value { font-size: 13px; line-height: 17px; color: var(--ds-ink-body); }
.ds-app-kv--strong :where(.ds-app-kv__key) { font-size: 12px; line-height: 15px; color: var(--ds-ink-muted); }
.ds-app-kv--strong :where(.ds-app-kv__value) { color: var(--ds-ink); }
/* `Emphasis=Sub` — the subtotal: `product/ui/13` against the figure, both in the heading ink, closed
   off by a hairline above as well as below. The band is the whole hierarchy, so a Sub row drawn as a
   Strong one reads as one more line of the list it is meant to total. */
.ds-app-kv--sub { box-shadow: inset 0 1px 0 var(--ds-app-border), inset 0 -1px 0 var(--ds-app-border); }
.ds-app-kv--sub :where(.ds-app-kv__key) { font-weight: 600; color: var(--ds-ink); }
.ds-app-kv--sub :where(.ds-app-kv__value) { color: var(--ds-ink); }
/* A rule is a stroke on the canvas, and a stroke takes no room in the frame it bounds: drawn as a
   border it stood every ruled row a pixel over its master. */
.ds-app-kv--ruled { box-shadow: inset 0 -1px 0 var(--ds-app-border); }
/* `Tone=Sunken` and `Tone=Total` set one fact across the callout at 14 all round: a label against a
   figure, pushed apart. The fact is the callout's own two layers, so the key-value row it is drawn
   with takes none of its own padding, and each side takes the tone's role — `meta/13` against
   `figure/16`, and the total's `ui/14` against `figure/17`, both settled on the figure's baseline.
   Each weighs one class and follows the row's own emphasis, so the tone wins over the row and a
   role the composition states for either side, set at the end of the section, wins over both. */
.ds-app-callout--sunken :where(.ds-app-kv), .ds-app-callout--total :where(.ds-app-kv) { padding-block: 0; }
.ds-app-callout--sunken :where(.ds-app-kv__key) { font-size: 13px; font-weight: 400; line-height: 16px; color: var(--ds-ink-body); }
.ds-app-callout--sunken :where(.ds-app-kv__value) { font-family: var(--ds-app-figure); font-size: 16px; font-weight: 700; line-height: 21px; color: var(--ds-ink); }
.ds-app-callout--total :where(.ds-app-kv) { align-items: flex-end; }
.ds-app-callout--total :where(.ds-app-kv__key) { font-size: 14px; font-weight: 600; line-height: 17px; color: var(--ds-ink); }
.ds-app-callout--total :where(.ds-app-kv__value) { font-family: var(--ds-app-figure); font-size: 17px; font-weight: 700; line-height: 22px; color: var(--ds-ink); }
/* A field of the interface — a quantity, a date, a search box — drawn settled, not editable. The
   canvas's `UI kit / Input` leads with a 14px glyph 12 from the edge and sets its placeholder 8 after
   it (measured on the Storefront shop cutout: icon at x=12, placeholder at x=34, field 360 wide by
   31 tall), so an icon moves the leading padding to 12 and a searched field fills its card. */
.ds-app-field { display: inline-flex; align-items: center; gap: 8px; padding: 6px 10px; border-radius: var(--ds-app-radius); background: var(--ds-app-surface); color: var(--ds-ink); box-shadow: inset 0 0 0 1px var(--ds-app-border); font-size: 12px; line-height: 16px; white-space: nowrap; }
.ds-app-field--fill { display: flex; width: 100%; }
.ds-app-field--lead { padding-block: 8px; padding-inline-start: 12px; }
.ds-app-field__icon { display: inline-flex; flex: none; width: 14px; height: 14px; color: var(--ds-ink-muted); }
.ds-app-field__icon > svg { display: block; width: 100%; height: 100%; }
.ds-app-field__label { min-width: 0; overflow: hidden; text-overflow: ellipsis; color: var(--ds-ink-muted); }
.ds-app-thumb { display: block; flex: none; width: var(--ds-app-thumb-width, var(--ds-app-thumb-size, 26px)); height: var(--ds-app-thumb-size, 26px); border-radius: var(--ds-app-radius-thumb); background: var(--ds-app-sunken); overflow: hidden; }
.ds-app-thumb > img { display: block; width: 100%; height: 100%; object-fit: cover; }
/* The three states off the master: a 1px OUTSIDE stroke in the brand — beyond the thumb's own box, as
   the canvas draws it, so the ring never eats a row of the picture — and two opacities that have to
   stay apart: a dimmed shot beside the chosen one still reads as available, where a sold-out line
   must read as gone. Whatever clips around a thumb has to leave it that pixel: rows and cards do, and
   the design-sync cell pads a master with an outside stroke by its width. */
.ds-app-thumb--selected { box-shadow: 0 0 0 1px var(--ds-accent); }
.ds-app-thumb--dim { opacity: .7; }
.ds-app-thumb--sold-out { opacity: .45; }
/* A step done: the check in its own tinted disc. One of the two round things the kit draws — this
   and the numbered disc of `UI kit / Badge` — while all 56 of its status chips are
   radius/product-sm. The two shapes had one token between them and the pill was carrying the
   disc's corner: the canvas's `UI kit / Check line` sets a 20px frame at radius 10 (six instances
   across the cutout masters) and a chip is never round at all. */
.ds-app-tick { display: inline-grid; flex: none; place-items: center; width: 20px; height: 20px; border-radius: 50%; background: var(--ds-app-ok-field); color: var(--ds-app-ok); }
.ds-app-tick > svg { display: block; width: 12px; height: 12px; }
/* `State=Pending`: the step of a journey that has not happened yet. The disc keeps its size so the
   column of checks stays aligned, and carries the hover field with the delivery glyph. */
.ds-app-tick--pending { background: var(--ds-app-hover); color: var(--ds-accent-ink); box-shadow: none; }
/* `Disc=Bare`: the mark with no disc under it, which is how the canvas lists what a piece includes —
   the check sits on the line's own leading rather than in a 20px well of its own. It keeps a frame:
   `Size=13` draws the mark in a 16px icon frame, which is what sets the line's text 4px inside the
   disc rows' beside it, and the mark is the plain glyph at stroke 2 where the disc's is the heavier
   3 — a box left to collapse around the glyph is a measurement the master never made. */
.ds-app-tick--bare { width: 16px; height: 16px; background: none; color: var(--ds-app-ok); }
.ds-app-tick--bare > svg { width: 16px; height: 16px; stroke-width: 2; }
/* `Size=11`: the line beside a list of what a piece includes, its bare check 12 square. */
.ds-app-tick--bare.ds-app-tick--11 { width: 12px; height: 12px; }
.ds-app-tick--bare.ds-app-tick--11 > svg { width: 12px; height: 12px; }
/* `UI kit / Check line` sets its words 10 after the mark, not a list row's 12. */
.ds-app-row.ds-app-check-line { gap: 10px; }

/* The dot a mail list leads an unread line with — `UI kit / List row` at `Leading=Dot`. The master
   binds it to `product/accent-ink`, which is the lift in the dark: the brand itself is 1.77:1 on the
   dark card. */
.ds-app-dot { display: block; flex: none; width: 6px; height: 6px; border-radius: 50%; background: var(--ds-accent-ink); }

/* A run of key-value rows, and a run of stat tiles: the frames the canvas stacks them in. A row of
   parts the canvas lays across the card — a control with the sentence beside it, a check with its
   figure — is `ds-app-across`, which wraps rather than squeezing a label at phone width. */
.ds-app-kvs { display: flex; flex-direction: column; }
.ds-app-stats { display: flex; align-items: stretch; gap: 10px; }
.ds-app-stats > * { flex: 1 1 0; min-width: 0; }
/* Across the row, the space its master frame leaves between its parts: twelve unless the record
   states its own, and a record never states the twelve the row already leaves. Down, when a row
   wraps at phone width, it is eight. */
.ds-app-across { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 8px var(--ds-app-across-gap, 12px); }
/* Some of those frames pack to the start instead of pushing apart — a line with its pill straight
   after the words, where the canvas lays them out at `MIN` rather than across the card. */
.ds-app-across--packed { justify-content: flex-start; }
/* `Primary full` means across whatever is left of the row: the catalogue's master gives Book a
   session 272 of its 360 and the icon squares beside it the rest. A pointer target wrapping the
   button is the flex item, and a target sized to its content resolves the button's own
   `width: 100%` against nothing — so a target that is not sized to fit stretches too, which is the
   same fact the renderer states when it passes `fit` for every button but this one. */
.ds-app-across > .ds-app-button--full,
.ds-app-across > .ds-walk__target:not(.ds-walk__target--fit) { flex: 1; min-width: 0; }
/* A row whose master grows one of its parts rather than pushing them apart. The canvas says which
   one: the part it sizes `FILL` while its neighbours hug — the browsing master gives Add to Cart
   272 of its 360 and the two icon squares the rest, the subscribe master 269 of its 360 to the
   email field, and the purchase order grows the SENTENCE beside its button rather than the button.
   So the record marks the item that fills and the row names its place, because the filling part is
   the second as often as the first. A button is inline-flex and would keep its label's width inside
   a slot that grew, so it takes the slot whole; the same fact the rule above states for a button
   that spans its card. */
.ds-app-across--fill-1 > :nth-child(1),
.ds-app-across--fill-2 > :nth-child(2),
.ds-app-across--fill-3 > :nth-child(3) { flex: 1; min-width: 0; }
.ds-app-across--fill-1 > :nth-child(1) > .ds-app-button,
.ds-app-across--fill-2 > :nth-child(2) > .ds-app-button,
.ds-app-across--fill-3 > :nth-child(3) > .ds-app-button { width: 100%; }

/* The frames the canvas draws around kit parts and gives no set of its own: a column with a gap, a
   product's picture over its thumbs, the label-and-figure cells a measurement sheet is laid out in,
   a breadcrumb, a review, and a run of loose pictures. They carry no colour: the parts inside them
   paint, these only place.
   These frames are the site-composed cutout contract. No library component emits them; the product
   sites' own cutout compositions do (storefront-website and cueprise-website, each in
   src/components/templates/cutouts/compositions.tsx), so the rules stay here for those sites. */
.ds-app-stack { display: flex; flex-direction: column; align-items: flex-start; gap: var(--ds-app-gap, 6px); min-width: 0; }
/* A chip or a button in a stack hugs its words — the column would otherwise stretch it to the full
   measure — while anything that holds a paragraph or takes typing keeps the whole width. */
.ds-app-stack > p,
.ds-app-stack > .ds-app-input,
.ds-app-stack > .ds-app-field,
.ds-app-stack > .ds-app-gallery,
.ds-app-stack > .ds-app-across { align-self: stretch; }
.ds-app-gallery { display: flex; flex-direction: column; gap: 8px; min-width: 0; }
.ds-app-gallery__main { display: block; overflow: hidden; aspect-ratio: var(--ds-gallery-ratio, 1); border-radius: var(--ds-app-radius); background: var(--ds-app-sunken); }
.ds-app-gallery__main > img { display: block; width: 100%; height: 100%; object-fit: cover; }
.ds-app-gallery__thumbs { display: flex; flex-wrap: wrap; gap: 6px; }
/* Thumbs the master sizes `FILL`: one strip, each an equal share of the gallery's own width. */
.ds-app-gallery__thumbs--fill { flex-wrap: nowrap; }
.ds-app-gallery__thumbs--fill > .ds-app-thumb { flex: 1 1 0; width: auto; min-width: 0; }
/* A gallery whose shot is a thumb hugs it, because the master's column is the picture's own width
   rather than a share of the card; the split beside it still takes the rest, and the pair wraps on a
   phone the way every other split does. */
.ds-app-split > .ds-app-gallery--shot { flex: 0 0 auto; }
.ds-app-cells { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px 12px; }
.ds-app-cell { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
/* A cell the canvas boxes — the measurements a garment is cut to — at the padding its master takes. */
.ds-app-cell--bordered { padding: 6px 10px; border-radius: var(--ds-app-radius); box-shadow: inset 0 0 0 1px var(--ds-app-border); }
.ds-app-cell--across { flex-direction: row; align-items: baseline; justify-content: space-between; gap: 8px; }
.ds-app-choices { display: flex; flex-wrap: wrap; gap: 6px; }
.ds-app-choices--fill { flex-wrap: nowrap; }
.ds-app-choices--fill > * { flex: 1 1 0; min-width: 0; }
.ds-app-choices--fill .ds-app-slot { width: 100%; }
/* A picture beside its words: the canvas sets the catalogue's gallery in a column of its own about a
   third of the card wide, with the piece's name, price and promise in the rest. It stacks on a phone
   rather than squeezing the column to a stamp. */
.ds-app-split { display: flex; flex-wrap: wrap; align-items: flex-start; gap: 12px; }
.ds-app-split > :first-child { flex: 1 1 110px; }
.ds-app-split > :last-child { flex: 3 1 180px; min-width: 0; }
.ds-app-split--fixed > :first-child { flex: 0 0 var(--ds-app-split-lead); }
.ds-app-beside { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; min-width: 0; }
/* The card-shaped choices a checkout offers stand side by side and share the width, which is how the
   canvas draws Delivery beside Pickup; the row-shaped ones stack, one line each.
   Equal TRACKS rather than `flex: 1 1 0`, because a zero basis is a border-box size: a card's own
   padding is a floor its share cannot fall below, so a padded card takes its 24px on top of the half
   it is given while a bare pointer target wrapping the identical card takes none — the storefront
   menu's two cards measured 164 and 188 in a 360 row against the master's equal 176s, and the skew
   stayed 24 at every width and with every label, because it is the padding and not the words. A
   `1fr` track is the same width whatever the item standing in it carries. */
.ds-app-options { display: grid; grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr); gap: 8px; }
.ds-app-options > * { min-width: 0; }
.ds-app-options-rows { display: flex; flex-direction: column; gap: 8px; }
.ds-app-crumbs { display: flex; flex-wrap: wrap; align-items: center; gap: 4px; }
.ds-app-review { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.ds-app-review__who { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 4px 8px; }
.ds-app-shots { display: flex; flex-wrap: wrap; gap: 6px; }
.ds-app-shots--fill { flex-wrap: nowrap; }
.ds-app-shots--fill > .ds-app-thumb { flex: 1 1 0; width: auto; min-width: 0; }
.ds-app-deep { background: var(--ds-surface-invert); color: var(--ds-ink); display: flex; flex-direction: column; align-items: flex-start; gap: var(--ds-app-gap, 8px); padding: 24px 20px; border-radius: var(--ds-app-radius); }

/* The shelf: the tile grid a shopfront lays its stock out in, two to a row at every width, because
   that is the shape the canvas draws and a shelf is read by comparing what is beside what. It stays
   two to a row on a phone as well: dropped to one column the four tiles stack four squares deep, and
   since the deck grid-stacks its states the whole walkthrough stage is then reserved at the tallest
   of them — 1609px against 191 and 268 for the states either side of it, measured at 390. Every
   measurement is the Storefront `The shop` cutout's own (2601:303500): the tile pads 10 and stacks at
   8, its picture is a square on the sunken field at the product corner, the name is product/ui/12 and
   the price row sets product/figure/11 against product/meta/10 with the two pushed apart. */
.ds-app-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
.ds-app-tile { display: flex; flex-direction: column; gap: 8px; min-width: 0; padding: 10px; border-radius: var(--ds-app-radius); background: var(--ds-app-surface); color: var(--ds-ink); box-shadow: inset 0 0 0 1px var(--ds-app-border); }
.ds-app-tile__media { position: relative; display: block; overflow: hidden; aspect-ratio: 1; border-radius: var(--ds-app-radius); background: var(--ds-app-sunken); }
.ds-app-tile__media > img { display: block; width: 100%; height: 100%; object-fit: cover; }
.ds-app-tile__meta { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.ds-app-tile__name { font-size: 12px; font-weight: 600; line-height: 15px; color: var(--ds-ink); overflow-wrap: anywhere; }
.ds-app-tile__price-row { display: flex; align-items: center; justify-content: space-between; gap: 8px; min-height: 15px; font-size: 11px; line-height: 15px; }
.ds-app-tile__price { color: var(--ds-ink-body); }
.ds-app-tile__stock { flex: none; font-size: 10px; line-height: 12px; color: var(--ds-ink-muted); }
/* `Kind=Piece`: the catalogue's tile. Its picture is the one the shelf's square is wrong about — the
   master draws a 158 x 92 landscape box (2557:90984), so a square runs 66px past it and pushes the
   colour and the size rows that much below the geometry the catalogue is laid out to. The picture
   carries the save glyph in its corner, and the colours and sizes each take a labelled row under the
   price, because a piece is ordered by choosing them rather than by reading a stock count. A swatch
   is the round dot the canvas draws a colour as, 14px across on the master with 4px between the four,
   so the row measures 68; a size takes the 3px corner the kit gives a chip. */
/* `Kind=Piece` draws its picture box FIXED at 92 tall, its name straight over its price, and its
   two choice labels in `micro/9`: a box cut to a ratio stood a fraction over its master at every
   width but one, and the kit's 2 between name and price and 10px labels stood the pair 4 over. */
.ds-app-tile--piece .ds-app-tile__media { aspect-ratio: auto; height: 92px; }
.ds-app-tile--piece .ds-app-tile__meta { gap: 0; }
.ds-app-tile--piece .ds-app-tile__choice-label { font-size: 9px; line-height: 11px; }
.ds-app-tile__wish { position: absolute; inset-block-start: 6px; inset-inline-end: 6px; }
.ds-app-tile__choice { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.ds-app-tile__choice-label { font-size: 10px; line-height: 12px; color: var(--ds-ink-muted); }
.ds-app-tile__swatches { display: flex; flex-wrap: wrap; gap: 4px; }
.ds-app-tile__swatch { display: block; width: 14px; height: 14px; border-radius: 50%; background: var(--ds-app-swatch, var(--ds-app-sunken)); box-shadow: inset 0 0 0 1px var(--ds-app-border); }
.ds-app-tile__sizes { display: flex; flex-wrap: wrap; gap: 4px; }
.ds-app-tile__size { display: inline-grid; place-items: center; min-width: 20px; height: 20px; padding-inline: 4px; border-radius: var(--ds-radius-chip); font-size: 10px; line-height: 12px; color: var(--ds-ink-body); box-shadow: inset 0 0 0 1px var(--ds-app-border); }
/* `Kind=Grid`: the set's default, the browsing tile of the Storefront shop (2557:90964). It has no card
   of its own: the tile is flush, and the hairline the shelf draws round the whole tile is drawn round
   the picture instead, over it, as the master strokes the image box. The name is product/link/12 with
   the rating beside it, the price row sets the pack in product/micro/9 where the shelf sets its stock,
   and the tile ends in its own control across it. `State=Sold out` (2557:90968) dims the photograph to
   45% and lays a 20px strip across its foot: black at 80% under white product/label/10 in BOTH themes,
   because the strip sits on a photograph, never on the theme's field. */
.ds-app-tile--grid { padding: 0; background: none; box-shadow: none; }
.ds-app-tile--grid .ds-app-tile__media::after { content: ''; position: absolute; inset: 0; border-radius: inherit; box-shadow: inset 0 0 0 1px var(--ds-app-border); pointer-events: none; }
.ds-app-tile--grid .ds-app-tile__name { font-weight: 500; }
.ds-app-tile__name-row { display: flex; align-items: center; justify-content: space-between; gap: 8px; min-width: 0; }
.ds-app-tile__rating { display: inline-flex; flex: none; align-items: center; gap: 2px; font-family: var(--ds-app-figure); font-size: 10px; font-weight: 500; line-height: 13px; color: var(--ds-ink-muted); }
.ds-app-tile__star { display: inline-flex; flex: none; width: 10px; height: 10px; color: var(--ds-app-warn); }
.ds-app-tile__star > svg { display: block; width: 100%; height: 100%; fill: currentColor; }
.ds-app-tile__pack { flex: none; font-size: 9px; line-height: 11px; color: var(--ds-ink-muted); }
.ds-app-tile__strip { position: absolute; inset: auto 0 0 0; display: block; margin: 0; padding-block: 4px; text-align: center; background: color-mix(in srgb, var(--brand-black) 80%, transparent); color: var(--brand-white); }
.ds-app-tile--sold-out .ds-app-tile__media > img { opacity: 0.45; }
.ds-app-tile__action { display: flex; min-width: 0; }
.ds-app-tile__action > .ds-app-button { flex: 1; min-width: 0; }
.ds-app-tile__action .ds-app-button__icon { width: 12px; height: 12px; }
/* Tiles in a shelf share the row's slots as subgrid rows: a name that wraps to two lines must not
   push its own price 3px below the price beside it. */
.ds-app-grid > .ds-app-tile { display: grid; grid-template-rows: subgrid; grid-row: span var(--ds-tile-rows, 3); align-content: start; }
.ds-app-grid > .ds-app-tile > .ds-app-tile__meta { display: grid; grid-template-rows: subgrid; grid-row: span 2; row-gap: 2px; }
/* The piece's name sits straight on its price, in the grid as out of it. */
.ds-app-grid > .ds-app-tile--piece > .ds-app-tile__meta { row-gap: 0; }
/* Narrow, the price and what the shelf says about it stop sitting side by side: at 150px of tile the
   two collide before the name does. A grid tile keeps its pack beside its price: the master draws the
   two on one row at 148. */
@container ds-app-card (max-width: 330px) {
  .ds-app-tile:not(.ds-app-tile--grid) .ds-app-tile__price-row { flex-direction: column; align-items: flex-start; gap: 2px; }
}

/* The basket's own pieces: a line set down on the sunken field, its quantity in a bordered square,
   the pill that only fits where the row is wide, and the total the panel adds up to. */
.ds-app-row--sunken { padding: 10px; background: var(--ds-app-sunken); }
/* The canvas's other two `Fill`/`Rule` answers for the same row: an object of its own, outlined and
   unfilled, and a row in a list read by its divisions. Neither paints a field, so neither needs an
   ink of its own — the hairline is the whole difference. */
.ds-app-row--bordered { padding: 10px; box-shadow: inset 0 0 0 1px var(--ds-app-border); }
/* Inside the row's own height, as the canvas strokes it. */
.ds-app-row--ruled { border-radius: 0; box-shadow: inset 0 -1px 0 var(--ds-app-border); }
.ds-app-row__detail { display: none; }
/* How far apart a list stands its rows — panels or plain lines alike: the number the composition's
   own master frame draws, carried in by the record, because the masters draw several — six between
   the FAQ's four panels and the About card's three steps, ten between a menu's dishes and a hotel's
   rooms, eight between a basket's lines. 8 is the kit's own, for a list drawn from no master. */
.ds-app-list--spaced { gap: var(--ds-app-list-gap, 8px); }
.ds-app-qty { display: inline-grid; flex: none; place-items: center; width: 24px; height: 24px; border-radius: var(--ds-app-radius-thumb); background: var(--ds-app-surface); color: var(--ds-ink); box-shadow: inset 0 0 0 1px var(--ds-app-border); font-size: 11px; font-weight: 500; }
.ds-app-total { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.ds-app-total__label,
.ds-app-facts__row--total .ds-app-facts__label { font-size: 13px; font-weight: 600; line-height: 16px; color: var(--ds-ink); }
.ds-app-total__value,
.ds-app-facts__row--total .ds-app-facts__value { font-size: 17px; line-height: 22px; color: var(--ds-ink); }
/* The `Size` axis is a figure size AND the line box the set records for it — 15/20, 16/21, 17/22,
   18/24 — so a size states both or the row stands taller than the canvas draws it. */
.ds-app-total--15 .ds-app-total__value { font-size: 15px; line-height: 20px; }
.ds-app-total--16 .ds-app-total__value { font-size: 16px; line-height: 21px; }
.ds-app-total--18 .ds-app-total__value { font-size: 18px; line-height: 24px; }

/* The prompt: the one field drawn with the brand's 2px border, the sentence in it and the small
   control at its end. The sentence is one line — cut short with an ellipsis rather than wrapped —
   because a faux input three lines deep is not what a shopper typed into. */
.ds-app-prompt { display: flex; align-items: center; gap: 12px; padding: 12px 14px; border-radius: var(--ds-app-radius); background: var(--ds-app-surface); color: var(--ds-ink); box-shadow: inset 0 0 0 2px var(--ds-accent); font-size: 14px; line-height: 17px; }
.ds-app-prompt__text { display: flex; flex: 1; min-width: 0; align-items: center; }
.ds-app-prompt__action { flex: none; }
/* `Kind=Compact`, `Outline` and `Sunken` set their label at 12 — `ui/12` and `link/12` — and a 12px
   label keeps its own 15px line, or the button stands a pixel over its master at 16. */
.ds-app-button--small { padding: 6px 12px; font-size: 12px; line-height: 15px; }
.ds-app-tags { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.ds-app-tags__icon { display: inline-flex; flex: none; width: 14px; height: 14px; color: var(--ds-accent-ink); }
.ds-app-tags__icon > svg { display: block; width: 100%; height: 100%; }
.ds-app-tags__label { font-size: 11px; line-height: 13px; color: var(--ds-ink-muted); }

/* The sign-in pieces: a code one sunken box per digit, the line the interface says about it, a link
   in the accent ink, and the small muted paragraph under a panel title. The cell master is 48 wide
   and every instance of it FILLs its row, so the cell carries the master's width and grows and
   shrinks from it: 60 each in the Cueprise card's 400, 53.3 in the storefront's 360, less again in a
   card a phone narrows, flush with the row every time. A ceiling ends the six short of it — 56 left
   24 dead at the right of the 400 — and a bare `flex: 1` collapses them onto the digit where a strip
   hugs instead of filling. */
.ds-app-code { display: flex; gap: 8px; }
.ds-app-code__box { display: grid; width: 48px; flex: 1 1 auto; min-width: 0; place-items: center; height: 44px; border-radius: var(--ds-app-radius); background: var(--ds-app-sunken); color: var(--ds-ink); box-shadow: inset 0 0 0 1px var(--ds-app-border); font-size: 17px; }
/* The kit cuts the cell to 40 where the card is narrower than the Cueprise screen's; the boxes still
   share the width, so only the height moves. */
.ds-app-code--40 .ds-app-code__box { height: 40px; }
.ds-app-status { margin: 0; font-size: 12px; font-weight: 500; line-height: 15px; color: var(--ds-ink-muted); }
.ds-app-status--ok { color: var(--ds-app-ok); }
.ds-app-status--warn { color: var(--ds-app-warn); }
.ds-app-status--bad { color: var(--ds-app-bad); }
.ds-app-status--brand { color: var(--ds-accent-ink); }
/* `Kind=Link` is bare: the label, 4 before a glyph, and no padding — the 6 by 10 a pressed link
   stands in is the pointer's own target frame, which the composition draws round it. Its ink weighs
   nothing, so a label the composition sets in a run's ink takes that one. */
.ds-app-link { display: inline-flex; align-items: center; gap: 4px; border-radius: var(--ds-app-radius); font-size: 12px; font-weight: 500; line-height: 15px; white-space: nowrap; }
:where(.ds-app-link) { color: var(--ds-accent-ink); }
.ds-app-text { margin: 0; font-size: 12px; font-weight: 400; line-height: 15px; color: var(--ds-ink-muted); }
.ds-app-text--small { font-size: 11px; line-height: 13px; }

/* The shell the interface is drawn in: the rail down the side, what it says at the top of it and
   what it navigates. The rail is 212 wide on the sunken field with a hairline down its trailing
   edge, and below 768 it is not drawn at all — a cutout of a desktop interface on a phone has to
   give the screen the width, or the figures in it are squeezed until they clip. Every size is the
   canvas's own: the nav pads 10 each side and 16 at the foot with 2 between items, an item is 7
   above and below with 10 between glyph and label, and the group heading sits 16 down and 6 clear
   of the item under it, indented to the item's 10 so the words line up with the labels rather than
   the icons. `State=Active` is the hover tint and the accent ink together, which is how the
   interface says where you are. */
.ds-app-side { display: none; }
@media (min-width: 768px) {
  .ds-app-side { display: flex; flex: none; flex-direction: column; width: 212px; border-inline-end: 1px solid var(--ds-app-border); background: var(--ds-app-sunken); color: var(--ds-ink); }
}
.ds-app-side__nav { display: flex; flex: 1; flex-direction: column; gap: 2px; min-height: 0; padding: 0 10px 16px; }
.ds-app-side__foot { display: flex; flex: none; align-items: center; gap: 10px; padding: 16px; border-block-start: 1px solid var(--ds-app-border); color: var(--ds-ink-body); }
.ds-app-tenant { display: flex; flex: none; align-items: center; gap: 10px; padding: 16px; }
.ds-app-tenant--flush { padding-inline: 0; }
.ds-app-tenant__mark { display: grid; flex: none; place-items: center; width: 32px; height: 32px; border-radius: var(--ds-app-radius); background: var(--ds-app-hover); color: var(--ds-accent-ink); }
.ds-app-tenant__names { display: flex; flex-direction: column; min-width: 0; }
.ds-app-tenant__name { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; color: var(--ds-ink); }
.ds-app-tenant__meta { color: var(--ds-ink-muted); }
.ds-app-group { display: block; padding: 16px 10px 6px; color: var(--ds-ink-muted); }
.ds-app-nav { display: flex; align-items: center; gap: 10px; padding: 7px 10px; border-radius: var(--ds-app-radius); color: var(--ds-ink-body); }
.ds-app-nav--active { background: var(--ds-app-hover); color: var(--ds-accent-ink); }
.ds-app-nav__icon { display: inline-flex; flex: none; width: 15px; height: 15px; }
.ds-app-nav__icon > svg { display: block; width: 100%; height: 100%; }
.ds-app-nav__label { flex: 1; min-width: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }

/* The table inside it. A table is read by its rules, so neither the head nor a row paints a field
   of its own — the card is the surface and the hairlines do the work; the canvas's `Fill=Sunken`
   head is the one exception and the last row of a table drops its rule. The head and the rows are
   handed the SAME column list, so a column cannot be 110 wide in one and 80 in the other: the
   width is a fixed basis on every column but the name, which takes the remainder. */
.ds-app-col { display: inline-block; white-space: nowrap; color: var(--ds-ink-muted); }
.ds-app-table { display: flex; flex-direction: column; }
.ds-app-thead { display: flex; align-items: center; min-height: 32px; border-block-end: 1px solid var(--ds-app-border); }
.ds-app-thead--sunken { background: var(--ds-app-sunken); color: var(--ds-ink); }
.ds-app-thead__cell { display: flex; flex: 1; min-width: 0; align-items: center; overflow: hidden; }
.ds-app-thead__cell--end { justify-content: flex-end; }
/* The rule under a table row is the master's stroke, inside the row's own 44 rather than a pixel
   under it: a border added one per row and stood the stock table 6px over its master. */
.ds-app-trow { display: flex; align-items: center; box-shadow: inset 0 -1px 0 var(--ds-app-border); }
.ds-app-trow--flush { box-shadow: none; }
/* Every cell but the name holds ONE token — a code, a branch, a figure, a chip — and the canvas
   hugs each of them on a single line. A wrapped token is how a fixed column grows its row: inside
   the 12 the cell is padded below, `AY-581` broke after `AY-` and stood every row of the Stock table
   57 tall against the master's 44. It measures 44 against the 40 of content the canvas fits it in,
   so unwrapped it runs 4 into the cell's own 12 of padding and stops well short of its neighbour. */
.ds-app-trow__cell { display: flex; flex: 1; min-width: 0; align-items: center; gap: 8px; padding-block: 10px; white-space: nowrap; }
/* The Product cell's name takes what the thumb leaves and breaks inside a word rather than widening
   the cell, because the name column is the only one that can grow and the figures beside it are
   fixed: a name that refuses to break pushes the price off the end of the card. */
.ds-app-trow__name { min-width: 0; color: var(--ds-ink); overflow-wrap: anywhere; white-space: normal; }
.ds-app-trow__cell--end { justify-content: flex-end; }
/* The column insets the master draws, and the only thing standing the figures off the card's own
   edge where the card pads nothing: 12 inside every cell, 16 at the two ends of the table. Every
   Stock header and row on the canvas pads `cell · Product` [10,12,10,16] and `cell · Status`
   [10,16,10,12], and the Receive card that carries the table pads 0 — so without these the thumb
   starts on the card's boundary and the status chip ends on it. The table whose card pads for it —
   the Branch kind the dashboard draws, flush on both edges — states none of them, which is why this
   is a modifier and not the paint of a cell. The 16 sits INSIDE the two end cells rather than on the
   row: taken off the row it comes out of the one column that can grow, and every fixed column then
   starts 4 short of the boundary the canvas fixes it at. */
.ds-app-thead--inset .ds-app-thead__cell,
.ds-app-trow--inset .ds-app-trow__cell { padding-inline: 12px; }
.ds-app-thead--inset .ds-app-thead__cell:first-child,
.ds-app-trow--inset .ds-app-trow__cell:first-child { padding-inline-start: 16px; }
/* The trailing end is NAMED, not `:last-child`. The columns a narrow card drops are still the row's
   last children, so `:last-child` puts the 16 on a cell that is not drawn and leaves the last cell
   drawn on 12. The head and the row work out which column ends the table at each rung and say so. */
.ds-app-thead--inset .ds-app-thead__cell--tail,
.ds-app-trow--inset .ds-app-trow__cell--tail { padding-inline-end: 16px; }
/* The columns a narrow card cannot hold are dropped, not squeezed: squeezed, the name wraps to a
   syllable a line and the last figure is clipped off the end. The card is what they answer to, as
   the row detail does — the same table sits in a 928 panel and in one of a pair at 458, and a
   viewport query gets both of those wrong. */
.ds-app-thead__cell--from-500,
.ds-app-trow__cell--from-500,
.ds-app-thead__cell--from-640,
.ds-app-trow__cell--from-640 { display: none; }
@container ds-app-card (min-width: 500px) {
  .ds-app-thead__cell--from-500,
  .ds-app-trow__cell--from-500 { display: flex; }
  .ds-app-thead--inset .ds-app-thead__cell--tail,
  .ds-app-trow--inset .ds-app-trow__cell--tail { padding-inline-end: 12px; }
  .ds-app-thead--inset .ds-app-thead__cell--tail-500,
  .ds-app-trow--inset .ds-app-trow__cell--tail-500 { padding-inline-end: 16px; }
}
@container ds-app-card (min-width: 640px) {
  .ds-app-thead__cell--from-640,
  .ds-app-trow__cell--from-640 { display: flex; }
  .ds-app-thead--inset .ds-app-thead__cell--tail-500,
  .ds-app-trow--inset .ds-app-trow__cell--tail-500 { padding-inline-end: 12px; }
  .ds-app-thead--inset .ds-app-thead__cell--tail-640,
  .ds-app-trow--inset .ds-app-trow__cell--tail-640 { padding-inline-end: 16px; }
}

/* The Stock Health bar a Branch row ends in. `UI kit / Table row` draws it 104 x 6 on the sunken
   field, the earned part in the ok ink and the share beside it in the 10px figure. The width is a
   maximum rather than a size, because the cell it sits in is narrower on a phone than in a panel,
   and both ends take the chip corner so a full bar is the same shape as an empty one. */
.ds-app-bar { display: flex; min-width: 0; align-items: center; gap: 8px; }
.ds-app-bar__track { width: 104px; max-width: 100%; height: 6px; overflow: hidden; border-radius: var(--ds-radius-chip); background: var(--ds-app-sunken); }
.ds-app-bar__fill { display: block; height: 100%; border-radius: var(--ds-radius-chip); background: var(--ds-app-ok); }
.ds-app-bar__fill--warn { background: var(--ds-app-warn); }
.ds-app-bar__figure { color: var(--ds-ink-muted); font-variant-numeric: tabular-nums; }

/* The alert list. The tone moves the 10px mark and nothing else: a list of alerts is one list on
   one field, not three coloured banners, and the mark sits 4 down so it lines up with the first
   line of type rather than the middle of the pair. */
.ds-app-alert { display: flex; align-items: flex-start; gap: 10px; padding: 10px; border-radius: var(--ds-app-radius); background: var(--ds-app-sunken); color: var(--ds-ink); }
.ds-app-alert__mark { flex: none; width: 10px; height: 10px; margin-block-start: 4px; border-radius: var(--ds-app-radius-thumb); background: var(--ds-ink-muted); }
.ds-app-alert__mark--warn { background: var(--ds-app-warn); }
.ds-app-alert__mark--bad { background: var(--ds-app-bad); }
.ds-app-alert__body { display: flex; flex: 1; flex-direction: column; min-width: 0; }
.ds-app-alert__title { color: var(--ds-ink); }
.ds-app-alert__detail { color: var(--ds-ink-muted); }
.ds-app-alert__when { flex: none; color: var(--ds-ink-muted); }

/* The one record the interface holds up. The only place in the kit the brand itself is drawn as a
   line: a 1px outline of `--ds-accent` round the hover tint, in both themes, with the head set in
   the figure voice in the accent ink and the lines under it in the interface's body. */
.ds-app-highlight { display: flex; flex-direction: column; gap: 8px; padding: 12px; border-radius: var(--ds-app-radius); background: var(--ds-app-hover); color: var(--ds-ink); box-shadow: inset 0 0 0 1px var(--ds-accent); }
.ds-app-highlight__head { display: flex; align-items: center; justify-content: space-between; gap: 12px; color: var(--ds-accent-ink); }
.ds-app-highlight__title { min-width: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
.ds-app-highlight__meta { display: inline-flex; flex: none; align-items: center; gap: 4px; }
.ds-app-highlight__icon { display: inline-flex; flex: none; width: 12px; height: 12px; }
.ds-app-highlight__icon > svg { display: block; width: 100%; height: 100%; }
.ds-app-highlight__lines { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.ds-app-highlight__line { color: var(--ds-ink); }

/* The controls and the fields, the pieces of the kit a person would reach for if the cutout were
   the running client. Every one of them is a PICTURE of a control — the stage is footage, hidden
   from assistive technology — so nothing here is focusable, nothing carries a ring, and the state a
   screen is in is drawn rather than held. Sizes are the canvas's own, on the twelve sets of the
   Product UI kit's controls group. */

/* The button's other kinds. `Outline` is the unfilled control whose label is set in the accent ink,
   `Sunken` the wide one on the app's own well that a vendor sign-in takes, and `Primary full` the
   one that spans its card — 348 in a 380 master, which is width 100% wherever it is dropped.
   `Compact` and `Grid 28` are both `--small`: twenty-seven and twenty-eight tall on the canvas, one
   pixel apart, and the second is the first with a glyph in front of it. */
.ds-app-button--outline { gap: 6px; padding: 6px 10px; background: none; color: var(--ds-accent-ink); box-shadow: inset 0 0 0 1px var(--ds-app-border); font-size: 12px; font-weight: 500; line-height: 15px; }
.ds-app-button--sunken { gap: 8px; padding: 10px 12px; background: var(--ds-app-sunken); color: var(--ds-ink); box-shadow: inset 0 0 0 1px var(--ds-app-border); font-size: 12px; font-weight: 500; line-height: 15px; }
.ds-app-button--full { display: flex; width: 100%; }
.ds-app-button__icon { display: inline-flex; flex: none; width: 14px; height: 14px; }
.ds-app-button__icon > svg { display: block; width: 100%; height: 100%; }

/* The field, drawn as the screen draws it rather than inline: the well a rung under the card, the
   hairline round it, 12 across and 8 down (6 down when filled with no lead: the master's
   `State=Filled, Leading=None` field is 28, its dial-code sibling 32), and the label — when the set's `Label=On` is what the
   screen shows — above it rather than inside. `AppField` is the inline twin and `AppPrompt` the
   `State=Active` one with the brand's 2px border; all three are one anatomy at three jobs. */
.ds-app-input { display: inline-flex; flex-direction: column; gap: 4px; min-width: 0; }
.ds-app-input__label { color: var(--ds-ink-muted); }
.ds-app-input__field { display: flex; align-items: center; gap: 8px; min-width: 0; padding: 8px 12px; border-radius: var(--ds-app-radius); background: var(--ds-app-sunken); box-shadow: inset 0 0 0 1px var(--ds-app-border); }
/* `State=Filled` with nothing leading it sets its value in six above and below, not eight: the
   filled field is 28 under its label where the resting and the dial-code ones are 29 and 32. */
.ds-app-input__field--filled { padding: 6px 12px; }
.ds-app-input__icon { display: inline-flex; flex: none; width: 14px; height: 14px; color: var(--ds-ink-muted); }
.ds-app-input__icon > svg { display: block; width: 100%; height: 100%; }
.ds-app-input__lead { flex: none; color: var(--ds-ink-muted); }
.ds-app-input__value { min-width: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; color: var(--ds-ink); }
.ds-app-input__placeholder { min-width: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; color: var(--ds-ink-muted); }

/* The chooser. Rest is a hairline and the body ink; chosen is the hover tint under the accent ink,
   and a chosen CONTROL adds the brand's own 1px ring, which is the one place outside the highlight
   card the kit draws the brand as a line. `Shape` is the corner and nothing else: a chip takes the
   2px product chip corner (the ruling of 6 September 2026, in place of the live pill) and a control
   the 6px one, because a size chooser and a tab chip are controls. */
.ds-app-choice { display: inline-flex; flex: none; align-items: center; padding: 4px 10px; color: var(--ds-ink-body); box-shadow: inset 0 0 0 1px var(--ds-app-border); white-space: nowrap; }
.ds-app-choice--chip { border-radius: var(--ds-radius-chip); }
.ds-app-choice--control { border-radius: var(--ds-app-radius); }
.ds-app-choice--selected { background: var(--ds-app-hover); color: var(--ds-accent-ink); box-shadow: none; }
.ds-app-choice--control.ds-app-choice--selected { box-shadow: inset 0 0 0 1px var(--ds-accent); }

/* The counters. The square is the basket's, and the same box a line's quantity is drawn in; the
   disc is the numbered step, the hover tint with its numeral in the accent ink. It is round, which
   in this kit only it and the check mark are. */
.ds-app-disc { display: inline-grid; flex: none; place-items: center; width: 20px; height: 20px; border-radius: 50%; background: var(--ds-app-hover); color: var(--ds-accent-ink); font-size: 10px; font-weight: 500; }

/* The tabs. Underline is the bar a screen's sections are read across — the active one in the
   heading ink over the brand's own 2px rule, the rest muted, 12 apart and 4 clear of the rule.
   Chip is the same choice as a row of controls, which is the chooser above at its control corner.
   The rule is an inset shadow and not a border because the master's tab is 19 tall WITH it: Figma
   aligns a frame's stroke inside, a border would add its 2 to every tab, and the row a composition
   places would sit two pixels lower than the drawing it is a picture of. The underline bar is a
   hugging row on the canvas and never wraps: narrower than its tabs, it scrolls. */
.ds-app-tabs { display: flex; align-items: flex-end; flex-wrap: wrap; }
.ds-app-tabs--underline { gap: 12px; flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; }
.ds-app-tabs--underline::-webkit-scrollbar { display: none; }
.ds-app-tabs--chip { align-items: center; gap: 8px; }
.ds-app-tabs__tab { padding-block-end: 4px; box-shadow: inset 0 -2px 0 transparent; color: var(--ds-ink-muted); white-space: nowrap; }
.ds-app-tabs__tab--active { box-shadow: inset 0 -2px 0 var(--ds-accent); color: var(--ds-ink); }

/* The shopfront's own three: the stepper, the sort control and the price facet. The first two are
   one box — a hairline at the control corner holding a muted word against a value — and the third
   is the label, the 4px well the brand fills to where the range was set, and the range under it. */
.ds-app-stepper { display: inline-flex; flex: none; align-items: center; gap: 8px; padding: 6px 10px; border-radius: var(--ds-app-radius); box-shadow: inset 0 0 0 1px var(--ds-app-border); white-space: nowrap; }
.ds-app-stepper__label { color: var(--ds-ink-muted); }
.ds-app-stepper__count { color: var(--ds-ink); }
.ds-app-sort { display: inline-flex; flex: none; align-items: center; gap: 6px; padding: 4px 10px; border-radius: var(--ds-app-radius); box-shadow: inset 0 0 0 1px var(--ds-app-border); white-space: nowrap; }
.ds-app-sort__label { color: var(--ds-ink-muted); }
.ds-app-sort__value { color: var(--ds-ink); }
.ds-app-chevron { display: inline-flex; flex: none; width: 12px; height: 12px; color: var(--ds-ink-muted); }
.ds-app-chevron--up { rotate: 180deg; }
.ds-app-chevron > svg { display: block; width: 100%; height: 100%; }
.ds-app-facet { display: flex; flex-direction: column; flex-wrap: nowrap; gap: 12px; }
.ds-app-facet__content { display: flex; flex-direction: column; flex-wrap: nowrap; gap: 8px; min-width: 0; }
.ds-app-facet__head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.ds-app-facet__label { min-width: 0; color: var(--ds-ink); }
.ds-app-facet__option { display: flex; align-items: center; gap: 8px; min-width: 0; }
.ds-app-facet__box { flex: none; width: 10px; height: 10px; border-radius: var(--ds-app-radius-thumb); box-shadow: inset 0 0 0 1px var(--ds-app-border); }
.ds-app-facet__option-label { min-width: 0; color: var(--ds-ink-body); }
.ds-app-slider { display: flex; flex-direction: column; flex-wrap: nowrap; gap: 8px; }
.ds-app-slider__label { color: var(--ds-ink); }
.ds-app-slider__track { display: block; overflow: hidden; height: 4px; border-radius: var(--ds-radius-chip); background: var(--ds-app-sunken); }
.ds-app-slider__fill { display: block; width: var(--ds-app-slider-at, 66%); height: 100%; border-radius: inherit; background: var(--ds-accent); }
.ds-app-slider__range { color: var(--ds-ink-muted); }

/* The option a screen offers, in the two shapes the canvas draws it: the 200-wide card of one
   label, and the payment row that fills its column with the aside at the far edge. Both are ONE
   line of type — the master's card is 31 tall on 8 and its row 36 on 10 — so the note is a third
   item on the row rather than a second line under the label. Rest is a hairline with the glyph
   muted; chosen is the brand ring over the hover tint with the glyph in the accent ink; and `--lit`
   is that tint under the plain hairline, which is the frame the pointer leaves under itself a beat
   before the option is taken. */
.ds-app-option { display: inline-flex; align-items: center; min-width: 0; border-radius: var(--ds-app-radius); color: var(--ds-ink); box-shadow: inset 0 0 0 1px var(--ds-app-border); text-align: start; }
.ds-app-option--card { gap: 8px; padding: 8px 12px; }
/* And the card fills the track it is given, the way the row-shaped option already takes the full
   width: the pointer target that presses one of them is a block, so the inline-flex card inside it
   keeps its content width and paints 96 of its 176, leaving the pair 76px apart where 8 is declared.
   Stated of the card rather than of the wrapper, so it holds however the target is drawn. */
.ds-app-options .ds-app-option--card { width: 100%; }
.ds-app-option--row { display: flex; gap: 12px; width: 100%; padding: 10px; }
.ds-app-option--lit { background: var(--ds-app-hover); }
.ds-app-option--selected { background: var(--ds-app-hover); box-shadow: inset 0 0 0 1px var(--ds-accent); }
.ds-app-option__icon { display: inline-flex; flex: none; width: 14px; height: 14px; color: var(--ds-ink-muted); }
.ds-app-option--row .ds-app-option__icon { width: 16px; height: 16px; }
.ds-app-option--selected .ds-app-option__icon { color: var(--ds-accent-ink); }
.ds-app-option__icon > svg { display: block; width: 100%; height: 100%; }
.ds-app-option__label { min-width: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; color: var(--ds-ink); }
.ds-app-option__note { flex: none; margin-inline-start: auto; padding-inline-start: 12px; color: var(--ds-ink-muted); }

/* The booking cell, wide for a time and square for a day. A cell that has gone is muted AND struck
   through, because a state told in colour alone is not told. The master's rule is as wide as the
   time and not as wide as the cell, which is what a line-through is: a drawn bar would have to guess
   the figure's width and would be wrong at every other one. */
.ds-app-slot { display: inline-grid; place-items: center; min-width: 0; border-radius: var(--ds-app-radius); box-shadow: inset 0 0 0 1px var(--ds-app-border); }
.ds-app-slot--slot { padding: 8px 10px; }
.ds-app-slot--day { width: 49px; height: 32px; border-radius: var(--ds-app-radius-thumb); }
.ds-app-slot--free { color: var(--ds-ink-body); }
.ds-app-slot--chosen { background: var(--ds-app-hover); color: var(--ds-ink); box-shadow: inset 0 0 0 1px var(--ds-accent); }
.ds-app-slot--day.ds-app-slot--chosen { background: var(--ds-accent); color: var(--ds-ink-on-accent-control); }
.ds-app-slot--taken { background: var(--ds-app-sunken); color: var(--ds-ink-muted); }
.ds-app-slot__value { white-space: nowrap; }
.ds-app-slot--taken .ds-app-slot__value { text-decoration: line-through; }

/* The typewriter, settled. The caret's `left` and the wipe's `clip-path` insets are both percentages
   and agree only against one box: `.ds-app-typed` is the caret's containing block and
   `.ds-app-typed__text` the clipped element, and the wrapper shrink-wraps the sentence while the
   sentence carries its own truncation. This is layout, not motion, so it sits outside the motion
   query; the caret is hidden here and shown only where it can blink and travel, because a caret that
   does neither is a stray mark. A step is simply present until a timed card says otherwise. */
.ds-app-typed { position: relative; display: inline-block; max-width: 100%; min-width: 0; vertical-align: bottom; }
.ds-app-typed__text { display: block; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ds-app-typed__caret { display: none; }
/* A step stacks what arrives together — a rule and the total under it — at the card's own 12px. */
.ds-app-step { display: flex; flex-direction: column; gap: 12px; min-width: 0; }

/* ── The pictures, the figures and the pointer ──────────────────────────────────────────────
 *
 * The rest of the kit: the seventeen sets a cutout is made of that are not surfaces, records or
 * controls. Everything here is measured off its master on page 15 — the sizes are the canvas's own
 * and are written out rather than derived, because a picture drawn at "about right" is the one thing
 * a cutout cannot survive. Every colour is an app role, a state role or the accent; nothing reaches
 * for a page token, so a fragment dropped inside a cutout paints the app's palette.
 */

/* A person, as a circle. The photo sizes are 26, 36 and 40; initials take the tint a row lights with
   and the accent ink, 28 in a circle. The tenant mark is the one avatar the canvas does NOT round:
   it is a logo slot, and a logo cropped to a circle loses its corners. */
.ds-app-avatar { display: grid; flex: none; place-items: center; overflow: hidden; width: var(--ds-app-avatar-size, 26px); height: var(--ds-app-avatar-size, 26px); border-radius: 50%; background: var(--ds-app-sunken); }
.ds-app-avatar > img { display: block; width: 100%; height: 100%; object-fit: cover; }
.ds-app-avatar--initials { background: var(--ds-app-hover); color: var(--ds-accent-ink); }
.ds-app-avatar--tenant { border-radius: var(--ds-app-radius); }

/* A strip of photograph with the interface's words over it. The scrim is FLAT, which is what the
   master draws: a gradient would leave the eyebrow — the line that sits highest — the least
   covered of the two, and the eyebrow is the one set at 9px.
   The kit draws the strip at three heights, and each one scrims by its own amount: the taller the
   band, the further the caption sits from the top of the picture and the less of it has to be held
   back. So the height and the scrim are one variant and are set together — a band given the height
   alone paints the 96's scrim over all three.
   The scrim is the one place the masters do NOT decide the value, and 62% is the floor on all three.
   The caption's inks ARE the masters': the title at white, the 9px eyebrow and the glyph at white 80%.
   Against the lightest ground a picture can reach, that eyebrow is 3.71:1 under the 96's own 55%, and
   lower still under the 104's 50% and the 120's 45%; 62% is the least scrim that holds it at 4.67:1,
   with the title at 6.2:1. A caption that cannot be read is not a variant, so the floor holds and the
   divergence is the canvas's to settle — the heights and the inks are the masters' and the scrim is
   the gate's. */
.ds-app-band { position: relative; display: block; overflow: hidden; height: 96px; border-radius: var(--ds-app-radius); background: var(--ds-app-sunken); }
.ds-app-band--cutout-104 { height: 104px; }
.ds-app-band--cutout-120 { height: 120px; }
.ds-app-band__media { position: absolute; inset: 0; display: block; }
.ds-app-band__media > img { display: block; width: 100%; height: 100%; object-fit: cover; }
/* The scrim carries the band's edge as well as its shade. A near-black strip laid on a near-black
   page has no boundary at all — the scrim takes the placeholder's white 8% lift BELOW the page
   it sits on (a strip darker than its 18,18,18 ground) — and every other picture-bearing object in
   the kit, the card and the category and shelf tiles, is bounded by this same hairline. It is
   declared on the scrim rather than on the band because the band's own background layer sits under
   the media and the scrim, both inset 0: a ring declared there is strained through both and reaches
   a light page as 97,98,101 where the canvas draws cutout/border at 228,231,236. `inherit` because
   the band clips at the product radius, which would cut the corners off a square ring. */
.ds-app-band__scrim { position: absolute; inset: 0; display: block; border-radius: inherit; background: rgb(0 0 0 / 0.62); box-shadow: inset 0 0 0 1px var(--ds-app-border); }
.ds-app-band__caption { position: absolute; inset: auto 16px 16px; display: flex; align-items: center; gap: 16px; color: var(--ds-ink-on-invert); }
.ds-app-band__text { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.ds-app-band__eyebrow { display: block; color: rgb(255 255 255 / 0.8); }
.ds-app-band__title { display: block; overflow-wrap: anywhere; }
.ds-app-band__icon { display: inline-flex; flex: none; align-items: center; width: 16px; height: 16px; margin-inline-start: auto; color: rgb(255 255 255 / 0.8); }
.ds-app-band__icon > svg { display: block; width: 100%; height: 100%; }
/* The promo band is the shopfront's own hero: the copy on the brand's deep field with the picture
   bleeding into the far corner. Below 640 the picture cannot bleed and keep the headline readable,
   so it comes back into the flow under the copy rather than shrinking to a stripe. */
/* The promo band paints a deep field in BOTH themes, so the empty picture well beside its copy takes
   the deep field's own placeholder ink rather than the page's. `--ds-app-placeholder` is mixed once,
   at the product root, out of that PAGE's ink: a custom property substitutes its var()s where it is
   declared, not where it is used, so on a light page the token reached this band already resolved to
   black at 6% — six per cent of black on a near-black field, which is a slot that reads as absent
   rather than as unfilled. The master draws white at 8%, which is what the dark ladder's own inks
   are written as. */
.ds-app-band--promo { height: auto; padding: 36px 32px; border-radius: 0; background: var(--ds-surface-invert); color: var(--ds-ink-on-invert); --ds-app-placeholder: rgb(255 255 255 / 0.08); }
.ds-app-band__copy { position: relative; z-index: 1; display: flex; flex-direction: column; align-items: flex-start; gap: 12px; min-width: 0; }
.ds-app-band__headline { display: block; overflow-wrap: anywhere; }
.ds-app-band--promo .ds-app-band__eyebrow { color: var(--ds-lift); }
.ds-app-band--promo .ds-app-band__media { position: relative; inset: auto; height: 120px; margin-block-start: 24px; border-radius: var(--ds-app-radius); overflow: hidden; }

/* One tile of a category shelf: 64 of photograph across the full width, then the name and its count.
   The picture is not inset the way a product tile's is — a category IS the photograph. */
.ds-app-cat { display: flex; flex-direction: column; min-width: 0; overflow: hidden; border-radius: var(--ds-app-radius); background: var(--ds-app-surface); color: var(--ds-ink); box-shadow: inset 0 0 0 1px var(--ds-app-border); }
.ds-app-cat__media { position: relative; display: block; overflow: hidden; aspect-ratio: 175 / 64; background: var(--ds-app-sunken); }
.ds-app-cat__media > img { position: absolute; inset: 0; display: block; width: 100%; height: 100%; object-fit: cover; }
.ds-app-cat__text { display: flex; flex-direction: column; min-width: 0; padding: 8px 10px; }
.ds-app-cat__name { font-size: 12px; font-weight: 500; line-height: 15px; color: var(--ds-ink); overflow-wrap: anywhere; }
.ds-app-cat__count { font-family: var(--ds-app-figure); font-variant-numeric: tabular-nums; letter-spacing: -0.01em; font-size: 10px; font-weight: 500; line-height: 13px; color: var(--ds-ink-muted); }

/* The glyph in its own box. The disc is the only one of the three that lands on a photograph, so it
   is the only one that carries a field of its own rather than borrowing the row's: the kit's card
   surface at 90%, which is white on a light page and the app's deep field on a dark one. The field
   and the ink move together — a fixed white disc under `--ds-ink-body` is a white heart on a white
   circle the moment the page goes dark, whatever picture is behind it. */
.ds-app-isq { display: inline-flex; flex: none; align-items: center; justify-content: center; width: 36px; height: 36px; border-radius: var(--ds-app-radius); }
.ds-app-isq > svg { display: block; width: 16px; height: 16px; }
.ds-app-isq--fill { background: var(--ds-app-hover); color: var(--ds-accent-ink); }
.ds-app-isq--outline { color: var(--ds-ink-muted); box-shadow: inset 0 0 0 1px var(--ds-app-border); }
.ds-app-isq--disc { width: 24px; height: 24px; border-radius: 50%; background: color-mix(in srgb, var(--ds-app-surface) 90%, transparent); color: var(--ds-ink-body); }
.ds-app-isq--disc > svg { width: 12px; height: 12px; }

/* A rating: five stars set solid against each other, an earned one in the warn ink and the rest
   outlined in the muted one. No gap — the master sets five 10px stars in 50px, and a gap between
   them would read as five marks rather than one score. */
.ds-app-stars { display: inline-flex; flex: none; align-items: center; color: var(--ds-ink-muted); }
.ds-app-stars__star { display: block; width: var(--ds-app-star-size, 10px); height: var(--ds-app-star-size, 10px); }
.ds-app-stars__star--on { fill: var(--ds-app-warn); color: var(--ds-app-warn); }

/* The trend behind a figure. Up and down are one shape in two inks: the canvas carries four trends,
   three of them up, and never a second geometry for the bad news. */
.ds-app-spark { display: block; flex: none; width: 64px; height: 24px; color: var(--ds-app-ok); }
.ds-app-spark--down { color: var(--ds-app-bad); }

/* What the whole is made of. The ring is 116 and the legend 16 away; below a 420 card the two stack,
   because a 116 ring and four legend rows cannot share the width of a phone. The slice geometry is
   `donutSlices`, shared with the dashboard cutout so one ring cannot drift from the other. */
.ds-app-donut { display: flex; flex-direction: column; align-items: flex-start; gap: 16px; min-width: 0; }
.ds-app-donut__ring { display: block; flex: none; width: 116px; height: 116px; }
.ds-app-donut__total { font-family: var(--ds-app-figure); font-size: 12px; font-weight: 500; letter-spacing: -0.01em; fill: var(--ds-ink); }
.ds-app-donut__caption { font-size: 9px; font-weight: 400; fill: var(--ds-ink-muted); }
.ds-app-donut__legend { display: flex; flex: 1; flex-direction: column; gap: 8px; min-width: 0; width: 100%; margin: 0; padding: 0; list-style: none; }
.ds-app-donut__row { display: flex; align-items: center; gap: 8px; min-width: 0; }
.ds-app-donut__dot { display: block; flex: none; width: 8px; height: 8px; border-radius: 50%; }
.ds-app-donut__name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12px; font-weight: 500; line-height: 15px; color: var(--ds-ink-body); }
.ds-app-donut__share { flex: none; font-family: var(--ds-app-figure); font-variant-numeric: tabular-nums; letter-spacing: -0.01em; font-size: 11px; font-weight: 500; line-height: 15px; color: var(--ds-ink-muted); }
.ds-app-donut__value { flex: none; font-family: var(--ds-app-figure); font-variant-numeric: tabular-nums; letter-spacing: -0.01em; font-size: 11px; font-weight: 500; line-height: 15px; text-align: end; color: var(--ds-ink-body); }

/* One figure off the dashboard with how it moved under it. The tile paints NO field: it is a
   hairline on the card it sits in, so a rail of four reads as one row of figures rather than four
   cards inside a card. The trend line goes to the far edge, where the master puts it. */
.ds-app-kpi { display: flex; flex-direction: column; gap: 6px; min-width: 0; padding: 12px; border-radius: var(--ds-app-radius); box-shadow: inset 0 0 0 1px var(--ds-app-border); }
.ds-app-kpi__label { font-size: 11px; font-weight: 500; line-height: 13px; color: var(--ds-ink-muted); }
.ds-app-kpi__value { font-size: 19px; font-weight: 700; line-height: 25px; letter-spacing: -0.025em; color: var(--ds-ink); }
.ds-app-kpi__foot { display: flex; align-items: center; gap: 8px; min-width: 0; padding-block-start: 2px; }
/* What the move is measured against goes when the CARD cannot hold it: on a phone the tile is about
   150 wide and the chip, the comparison and the trend line together are wider than that, so the line
   that says least is the one that drops — the same answer the dashboard cutout reached at 640. */
.ds-app-kpi__compare { display: none; flex: none; font-size: 10px; font-weight: 400; line-height: 12px; color: var(--ds-ink-muted); }
.ds-app-kpi__foot .ds-app-spark { margin-inline-start: auto; }

/* A counted thing, boxed, in the master's two shapes: the figure tile centres a name over a 17px
   figure over what it was before; the mini tile sets a 12px name over a line of state, left. The
   tone moves the mini tile's state line and nothing else — the canvas draws `Kind=Figure, Tone=Ok`
   and `Tone=Muted` identically, which is why the figure tile ignores it. */
.ds-app-stat { display: flex; flex-direction: column; min-width: 0; border-radius: var(--ds-app-radius); box-shadow: inset 0 0 0 1px var(--ds-app-border); }
.ds-app-stat--figure { align-items: center; gap: 4px; padding: 12px; text-align: center; }
.ds-app-stat--mini { gap: 2px; padding: 10px; }
.ds-app-stat__label { font-size: 11px; font-weight: 400; line-height: 13px; color: var(--ds-ink-muted); }
.ds-app-stat__value { font-size: 17px; font-weight: 700; line-height: 22px; color: var(--ds-ink); }
.ds-app-stat__was { font-size: 10px; font-weight: 400; line-height: 12px; color: var(--ds-ink-muted); }
.ds-app-stat__title { font-size: 12px; font-weight: 600; line-height: 15px; color: var(--ds-ink); overflow-wrap: anywhere; }
.ds-app-stat__state { font-size: 10px; font-weight: 400; line-height: 12px; color: var(--ds-ink-muted); }
.ds-app-stat__state--ok { color: var(--ds-app-ok); }

/* Three numbers side by side, divided by the kit's own hairline. The cells share the width evenly
   rather than hugging their figures, so the dividers stay where the eye expects them however long a
   number is — which is exactly what the master draws at 132.67 a cell. */
.ds-app-strip { display: flex; align-items: stretch; min-width: 0; }
.ds-app-strip__cell { display: flex; flex: 1; flex-direction: column; align-items: center; gap: 2px; min-width: 0; padding: 12px 8px; text-align: center; }
.ds-app-strip__cell + .ds-app-strip__cell { border-inline-start: 1px solid var(--ds-app-border); }
.ds-app-strip__value { font-size: 15px; font-weight: 700; line-height: 20px; color: var(--ds-ink); }
.ds-app-strip__label { font-size: 10px; font-weight: 400; line-height: 12px; color: var(--ds-ink-muted); }

/* One message in a conversation the interface is holding. No tail: the master draws none, and the
   side is said by the field and the alignment. An outgoing message hugs the far edge at 85%, which
   is the master's own proportion — 259 of a 305 row. */
.ds-app-bubble { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.ds-app-bubble__body { display: block; padding: 10px 12px; border-radius: var(--ds-app-radius); background: var(--ds-app-sunken); color: var(--ds-ink); font-size: 13px; font-weight: 400; line-height: 16px; overflow-wrap: anywhere; }
.ds-app-bubble__caption { display: block; font-size: 10px; font-weight: 400; line-height: 12px; color: var(--ds-ink-muted); }
.ds-app-bubble--out { align-items: flex-end; max-width: 85%; margin-inline-start: auto; }
.ds-app-bubble--out .ds-app-bubble__body { background: var(--ds-accent); color: var(--ds-ink-on-accent); }
.ds-app-bubble--out .ds-app-bubble__caption { text-align: end; }

/* The line drawn around what belongs to you: dashed rather than solid, because the boundary is a
   notion rather than a surface and the tour draws it around cards that already have a ring. The
   master's dash is 4 on 4; a 1px CSS dash is the engine's own rhythm and cannot be given a length,
   so the pattern is the browser's while the weight, the colour and the corner are the canvas's. The
   canvas strokes inside its box, so the dash is painted out of flow and stands in the 12 of padding. */
.ds-app-dashed { position: relative; display: flex; flex-direction: column; gap: 10px; min-width: 0; padding: 12px; border-radius: var(--ds-app-radius); }
.ds-app-dashed::after { content: ''; position: absolute; inset: 0; border: 1px dashed color-mix(in srgb, var(--ds-accent-ink) 45%, transparent); border-radius: inherit; pointer-events: none; }
.ds-app-dashed__eyebrow { display: inline-flex; align-items: center; gap: 6px; color: var(--ds-accent-ink); }
.ds-app-dashed__icon { display: inline-flex; flex: none; align-items: center; width: 12px; height: 12px; }
.ds-app-dashed__icon > svg { display: block; width: 100%; height: 100%; }

/* The pointer, landed on a control. The master is 36 square with the 24px cursor set 12 in from its
   top left, so the ring is one and a half times the arrow — and that is the ratio the walkthrough
   has always drawn, 36 against 24. The arrow's own outline is the one drop-shadow the library casts
   by hand: it has to read over arbitrary interface, and it carries the canvas's value. */
.ds-app-pointer { position: relative; display: block; width: 24px; height: 24px; }
.ds-app-pointer > svg { position: relative; display: block; filter: drop-shadow(0 1px 2px rgb(16 24 40 / 0.35)); }
.ds-app-pointer__ring { position: absolute; inset: -6px auto auto -6px; width: 36px; height: 36px; border-radius: 50%; border: 1px solid color-mix(in srgb, var(--ds-accent) 40%, transparent); background: color-mix(in srgb, var(--ds-accent) 15%, transparent); }

@container ds-app-card (min-width: 420px) {
  .ds-app-donut { flex-direction: row; align-items: center; }
  .ds-app-donut__legend { width: auto; }
  .ds-app-kpi__compare { display: inline; }
}

@media (min-width: 640px) {
  /* The master sets 620 of copy against a 543 picture in a 1180 band; as a proportion that is the
     copy stopping just before the picture starts, at 54%. */
  .ds-app-band--promo .ds-app-band__copy { max-width: 50%; }
  .ds-app-band--promo .ds-app-band__media { position: absolute; inset: 0 0 auto auto; width: 46%; height: 75%; margin-block-start: 0; border-radius: 0; }
}

/* The detail shows when the CARD is wide enough for it, not the viewport: the same row sits in a
   620px basket on a wide page, a 420px walkthrough step, and one column of a pair at 700px, and at
   the last of those a viewport query showed the pill and squeezed the name to a letter a line. The
   card is the size container the kit's rows answer to. */
@container ds-app-card (min-width: 500px) {
  .ds-app-row__detail { display: inline-flex; flex: none; align-items: center; }
}

@media (prefers-reduced-motion: no-preference) {
  /* A timed card runs its interface on the card's own clock. The sentence wipes in over the first
     17% of the loop in thirty steps — a wipe, not a width animation, so it still reads once a narrow
     card cuts it short — and the caret travels the same distance on the same step count, so it lands
     where the revealed text ends on every frame. It holds through the typing, blinks once, and is
     gone as the first step arrives. Each step fades up one stagger after the one before it and every
     step lets go together at the end, so the loop restarts on the empty prompt. Nothing here runs in
     a card without a period, and none of it runs for a reader who asked for reduced motion: they are
     left with the finished basket, which is the state that carries the claim. */
  .ds-app-card--timeline .ds-app-typed__text { animation: ds-app-type var(--ds-app-period) steps(30, end) infinite; }
  .ds-app-card--timeline .ds-app-typed__caret { display: block; position: absolute; top: 50%; left: 0; width: 2px; height: 1.05em; margin-left: 3px; translate: 0 -50%; background: var(--ds-accent-ink); animation: ds-app-caret var(--ds-app-period) steps(1, end) infinite, ds-app-caret-travel var(--ds-app-period) steps(30, end) infinite; }
  /* `backwards`, so a step waiting out its stagger on the first loop holds its 0% frame instead of
     flashing up settled before it has been typed for. */
  .ds-app-card--timeline .ds-app-step { animation: ds-app-appear var(--ds-app-period) cubic-bezier(0.2, 0, 0, 1) infinite backwards; animation-delay: calc(var(--ds-app-i, 0) * var(--ds-app-stagger)); }
  @keyframes ds-app-type { 0% { clip-path: inset(0 100% 0 0); } 17%, 100% { clip-path: inset(0 0 0 0); } }
  @keyframes ds-app-caret { 0%, 19% { opacity: 1; } 21% { opacity: 0; } 23% { opacity: 1; } 25%, 100% { opacity: 0; } }
  @keyframes ds-app-caret-travel { 0% { left: 0%; } 17%, 100% { left: 100%; } }
  @keyframes ds-app-appear { 0%, 19% { opacity: 0; transform: translateY(7px); } 25%, 93% { opacity: 1; transform: none; } 100% { opacity: 0; transform: translateY(7px); } }
}

/* ── Product UI kit ────────────────────────────────────────────────────────────────────────── */

/* The status chip: the one place the product set's state colours are spent. Each tone is an ink and
   its field from one token pair (theme-product.css), so a chip cannot put a light field under a
   dark-mode ink; Brand and Quiet sit on the kit's own surfaces rather than a state colour. It is a
   piece of the interface, not of the page — the product chip radius, never a pill — and the tone
   colours the words without replacing them. The fallbacks are the canvas variables' light values,
   for a chip that lands outside a product scope. */
/* The leading is the master's, as it is on `.ds-app-pill`, and it is stated here rather than left to
   the role the chip wears: `.ds-t-kit-chip-11` says `normal`, and a browser's `normal` draws Inter
   and the mono at one height where the canvas draws them 13 and 15. It overrides the role by
   source order — type.css is loaded before components.css by `styles.css`, which
   `scripts/build-package.mjs` writes — the two selectors weighing the same. */
.ds-kit-status { display: inline-flex; align-items: center; flex: none; padding: 2px 8px; border-radius: var(--ds-radius-chip); white-space: nowrap; background: var(--ds-cutout-sunken, var(--cutout-sunken)); color: var(--ds-ink-muted); line-height: 13px; }
.ds-kit-status--ok { background: var(--ds-ok-field, var(--state-ok-field)); color: var(--ds-ok, var(--state-ok)); }
.ds-kit-status--warn { background: var(--ds-warn-field, var(--state-warn-field)); color: var(--ds-warn, var(--state-warn)); }
.ds-kit-status--bad { background: var(--ds-bad-field, var(--state-bad-field)); color: var(--ds-bad, var(--state-bad)); }
.ds-kit-status--brand { background: var(--ds-cutout-hover, var(--cutout-hover)); color: var(--ds-accent-ink, var(--ds-accent)); }
/* A figure is tabular so a column of chips lines up digit for digit. */
.ds-kit-status--figure { font-variant-numeric: tabular-nums; line-height: 15px; }

/* The kit's text, in the four shapes the canvas draws it. `UI kit / Text` is one run at a named
   role — the family and the size on one axis of the set, the ink on the other, so the two are two
   modifiers here; `UI kit / Text line` is a row, because the canvas sets 6px between a glyph and the
   words; `UI kit / Text block` is the wrapped paragraph below; and `UI kit / Screen title` is the
   heading bar a screen opens with, the one place the kit sets Fustat. Every size is the product/*
   role the set is drawn in and every leading is the text node's own measured height.

   The runs are set near the end of the section on purpose. A composition letters a primitive's own
   words in another role where its master does — a list row's meta at `meta/11`, a link's label as a
   muted note, a key-value pair as two figures — and the part carries the role's classes beside its
   own. Both weigh one class, so the one written later wins, and a role written above the part it
   restyles would lose to the part's own size and leading every time. */
.ds-app-run { font-family: var(--ds-app-face); }
.ds-app-run--ui-12 { font-size: 12px; font-weight: 600; line-height: 15px; }
.ds-app-run--ui-13 { font-size: 13px; font-weight: 600; line-height: 16px; }
.ds-app-run--ui-15 { font-size: 15px; font-weight: 600; line-height: 18px; }
.ds-app-run--meta-11 { font-size: 11px; font-weight: 400; line-height: 13px; }
.ds-app-run--meta-12 { font-size: 12px; font-weight: 400; line-height: 15px; }
.ds-app-run--figure-11 { font-family: var(--ds-app-figure); font-variant-numeric: tabular-nums; letter-spacing: -0.01em; font-size: 11px; font-weight: 500; line-height: 15px; }
.ds-app-run--figure-12 { font-family: var(--ds-app-figure); font-variant-numeric: tabular-nums; letter-spacing: -0.01em; font-size: 12px; font-weight: 500; line-height: 16px; }
.ds-app-run--figure-20 { font-family: var(--ds-app-figure); font-variant-numeric: tabular-nums; letter-spacing: -0.01em; font-size: 20px; font-weight: 700; line-height: 26px; }
/* The rest of the same ladder, measured off `figma-scenes/text-styles.json` — the roles the
   storefront's catalogue, browsing and landing compositions set their words in. The canvas leaves
   these at the face's own leading, and that is NOT `line-height: normal`: the canvas rounds the
   face's ascender-to-descender once, a browser rounds the two apiece, and Inter 11 and 16 come out
   a pixel over the master. So each states the height the canvas draws; a figure keeps the mono
   face's tabular columns like every other figure role above. `mono-eyebrow/9` is not here: its
   capitals are part of the role, and case belongs to the generated type class rather than to a
   component, so `AppTextRun` reaches for `.ds-t-kit-mono-eyebrow-9` for that one. */
.ds-app-run--micro-9 { font-size: 9px; font-weight: 400; line-height: 11px; }
.ds-app-run--meta-10 { font-size: 10px; font-weight: 400; line-height: 12px; }
.ds-app-run--meta-13 { font-size: 13px; font-weight: 400; line-height: 16px; }
.ds-app-run--chip-11 { font-size: 11px; font-weight: 500; line-height: 13px; }
.ds-app-run--link-12 { font-size: 12px; font-weight: 500; line-height: 15px; }
.ds-app-run--ui-14 { font-size: 14px; font-weight: 600; line-height: 17px; }
.ds-app-run--ui-16 { font-size: 16px; font-weight: 600; line-height: 19px; }
.ds-app-run--row-13 { font-size: 13px; font-weight: 500; line-height: 16px; }
.ds-app-run--figure-13 { font-family: var(--ds-app-figure); font-variant-numeric: tabular-nums; letter-spacing: -0.01em; font-size: 13px; font-weight: 500; line-height: 17px; }
.ds-app-run--figure-15 { font-family: var(--ds-app-figure); font-variant-numeric: tabular-nums; letter-spacing: -0.01em; font-size: 15px; font-weight: 700; line-height: 20px; }
.ds-app-run--figure-16 { font-family: var(--ds-app-figure); font-variant-numeric: tabular-nums; letter-spacing: -0.01em; font-size: 16px; font-weight: 700; line-height: 21px; }
.ds-app-run--figure-10 { font-family: var(--ds-app-figure); font-variant-numeric: tabular-nums; letter-spacing: -0.01em; font-size: 10px; font-weight: 500; line-height: 13px; }
.ds-app-run--figure-17 { font-family: var(--ds-app-figure); font-variant-numeric: tabular-nums; letter-spacing: -0.01em; font-size: 17px; font-weight: 700; line-height: 22px; }
.ds-app-run--figure-28 { font-family: var(--ds-app-figure); font-variant-numeric: tabular-nums; letter-spacing: -0.01em; font-size: 28px; font-weight: 700; line-height: 1; }
.ds-app-run--heading { color: var(--ds-ink); }
.ds-app-run--body { color: var(--ds-ink-body); }
.ds-app-run--muted { color: var(--ds-ink-muted); }
.ds-app-run--ok { color: var(--ds-app-ok); }
.ds-app-run--accent { color: var(--ds-accent-ink); }

/* A slot with no picture in it is the canvas's `Image placeholder`, not a surface. The avatar on a
   staff row, the thumbnail on a booking, the shot above a catalogue strip, the band across the head
   of a screen, the tile on a category shelf and the tile on a product shelf all sit on the sunken
   well, which is the field a picture is laid ON — half a percent off the card's own white, so with
   no picture to lay it reads as nothing at all: five staff rows came out as five invisible circles,
   and both Bridal Collection tiles as bare squares, which is a slot that looks absent rather than
   one that looks unfilled. Each of those masters on page 15 nests an `Image placeholder` over the
   well — 2557:90982 nests `photo` inside the product tile's own 158×92 image box — so the six take
   that ink here. The seventh is the tile a shopfront lays its stock out in, which the library
   composes rather than extracts: the same square on the same well, so it follows the same rule.
   The primitive says it has nothing rather than the stylesheet reading `:empty`, because a slot
   handed an optimiser's own element has a child either way and only the primitive knows there is no
   picture behind it — and a product tile holds its wish glyph over the picture, so a slot with a
   child is not a slot with a photograph. The gallery shot is the exception: no library component
   draws it, the product sites' cutout compositions do (the contract stated above the frames), and
   they wear the modifier themselves. And this is the LAST word in the section on purpose: each
   of the seven paints the well from a rule of its own class weight, so a placeholder written above
   any of them loses the cascade and paints nothing at all — which is how the category tile and the
   band read as unfixed while their markup carried the modifier. */
.ds-app-avatar--placeholder,
.ds-app-thumb--placeholder,
.ds-app-gallery__main--placeholder,
.ds-app-band__media--placeholder,
.ds-app-cat__media--placeholder,
.ds-app-tile__media--placeholder,
.ds-shop__thumb--placeholder { background: var(--ds-app-placeholder); }

/* ── Product form and footer ───────────────────────────────────────────────────────────────── */

/* The card is the rung ABOVE the page, never the page: both Form / product · example masters fill it
   brand/white in the light and dark/raised in the dark, and put their fields a rung above THAT.
   `--ds-surface` is white in the light, so the light card was right by accident while the dark one
   came back on dark/base — the page itself — and the fields inside it a rung low with it. That pair,
   white over the page in the light and dark/raised over it in the dark, is `--ds-surface-control`. */
.ds-pform { gap: 24px; max-width: 560px; padding: 32px; background: var(--ds-surface-control); color: var(--ds-ink); border-radius: var(--ds-radius-card); box-shadow: inset 0 0 0 1px var(--ds-rule); }
.ds-pform .ds-form__fields { gap: 24px; }
/* The panel the product form becomes once it is sent. The Element=Success master centres everything
   under a 44px disc: the ok field as the disc, the ok ink as the check inside it, 20 down to the
   heading (the disc frame's 8 and the panel's 12) and 12 between the lines under it. The body and the
   note hold the master's 416 measure inside the 496 the panel gives them. */
.ds-pform--done { align-items: center; gap: 12px; text-align: center; }
/* The product panel is a card, and its own 32px already clears the ring: it keeps the card's box. */
.ds-pform.ds-form--done { box-sizing: border-box; margin: 0; }
.ds-pform__ok { display: grid; place-items: center; flex: none; width: 44px; height: 44px; margin-bottom: 8px; border-radius: 50%; background: var(--ds-ok-field, var(--state-ok-field)); color: var(--ds-ok, var(--state-ok)); }
.ds-pform__done { display: flex; flex-direction: column; align-items: center; gap: 12px; width: 100%; }
.ds-pform__title { margin: 0; width: 100%; color: var(--ds-ink); }
.ds-pform__body, .ds-pform__note { margin: 0; width: 100%; max-width: 416px; color: var(--ds-ink-muted); }
.ds-pform__note a { font-weight: 600; color: var(--ds-accent-ink); text-decoration: underline; }
/* The master strokes the check at 2.5 on a 20px box; lucide draws it at 2 on its own 24 grid. */
.ds-pform__ok .ds-icon svg { width: 20px; height: 20px; stroke-width: 2.5; }
/* A product panel floats a rung ABOVE the page: Element=Select State=Open fills it brand/white in
   the light and dark/raised in the dark, the same pair the example form cards take, while
   `--ds-surface` is the page — white in the light, dark/base in the dark. So the light panel was
   right by accident and the dark one came back on the page's own field, a rung under the master and
   all but level with the row it highlights. */
.ds-product .ds-select__panel { --ds-select-field: var(--ds-surface-control); }
/* An open product trigger takes the accent on its border, as the live product forms draw it — on
   the product set the accent is a legitimate ink; the corporate one takes the ink. The chosen row
   takes the accent at the medium weight; the corporate set keeps it in the ink at bold. */
.ds-product .ds-select__trigger[data-state='open'] { border-color: var(--ds-accent-ink); }
.ds-product .ds-select__option[data-state='checked'] { font-weight: 500; color: var(--ds-accent-ink); }
/* The product footer keeps its own deep field over the corporate footer's panel role and states the
   same master edge itself: every Footer / product variant carries the 1px inside top stroke, white
   at 12%, so it does not lean on `.ds-footer` for it. */
.ds-pfooter { background: var(--ds-surface-invert-panel); color: var(--ds-ink-on-invert); box-shadow: inset 0 1px 0 0 var(--ds-rule-on-invert); }
.ds-pfooter__legal { display: flex; flex-direction: column; gap: 2px; }
/* The product footer's column headings are the mono label voice at full ink in the brand's lift,
   which is what every Footer / product variant draws and what both live product sites set; the
   corporate footer's faded eyebrow would read as a third grey here. */
.ds-pfooter .ds-footer__heading { color: var(--ds-accent-ink); opacity: 1; }
/*
 * The utility row is the master's: the copyright at the start and the documents at the end, one
 * line at desktop and a stack below 1000px.
 *
 * It is a GRID rather than a flex column, and that is the whole of PRO-16's second failure. A flex
 * container that is both `column` and `wrap` sizes its width from its own height — the wrap decides
 * how many columns there are — and its height from the width it was given. Inside a frame whose
 * height a ResizeObserver reads back off the content, WebKit never settled that in one pass: the
 * observer looped rather than showing anything a reader would ever see. A grid track list has no
 * such feedback, so the row measures once.
 */
.ds-pfooter .ds-footer__utility { display: grid; grid-template-columns: minmax(0, 1fr); align-items: center; gap: 24px; }
.ds-pfooter .ds-footer__utility-links { gap: 12px 24px; }
/* The copyright leads the row, so the corporate footer's push to the end does not apply here. */
.ds-pfooter .ds-footer__copyright { margin-inline-start: 0; }
@media (min-width: 1000px) {
  .ds-pfooter .ds-footer__utility { grid-template-columns: minmax(0, auto) minmax(0, auto); justify-content: space-between; }
}

/* ── Chip row and card grid footnote ───────────────────────────────────────────────────────── */

.ds-chip-row { display: flex; gap: 10px; }
.ds-chip-row--standalone { flex-direction: column; }
.ds-chip-row--inline { flex-wrap: wrap; align-items: center; }
.ds-chip-row__label { color: var(--ds-ink-muted); }
.ds-chip-row__items { display: flex; flex-wrap: wrap; gap: 8px; }
/* A chip in a row is the hairline kind on the canvas, whatever tone it was given alone — except the
   filled ones: the trade chip row closes with a solid brand chip, and a filled chip stripped to a
   hairline here left its white label on the paper. */
.ds-chip-row__items > .ds-chip:not(.ds-chip--accent, .ds-chip--invert) { background: none; box-shadow: inset 0 0 0 1px color-mix(in srgb, currentColor 24%, transparent); }
/* 32 under the grid, not the heading gap the column would otherwise put there. */
.ds-card-grid__footnote { margin: calc(32px - var(--ds-heading-gap)) 0 0; color: var(--ds-ink-muted); }
.ds-button__icon { display: inline-flex; align-items: center; }
/* Layout=Two-column: the count line stands 16 over the rows and draws no rule of its own, and the
   rows are separate blocks, each over its own rule — 24 apart in the one column a phone gets, 32
   apart both ways in two. */
.ds-accordion--two-column .ds-accordion__list { gap: 16px; }
.ds-accordion--two-column .ds-accordion__count { padding-block-end: 0; border-block-end: 0; }
.ds-accordion__rows--two { display: grid; row-gap: 24px; }
@media (min-width: 900px) {
  /* Two columns start two runs of rows, so the first row of each draws its own top rule instead of
     the list — one rule between stacked rows, not two — unless the count line above drew it. */
  .ds-accordion__rows--two { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 32px; border-block-start: 0; }
  .ds-accordion__rows--two .ds-accordion__item:nth-child(-n + 2) { border-block-start: 1px solid var(--ds-rule); }
  .ds-accordion__count + .ds-accordion__rows--two .ds-accordion__item:nth-child(-n + 2) { border-block-start: 0; }
}
/* Kind=Footnote is a caption and nothing else: no fill, no stroke, no padding, so the gap above it
 * is the layout's. It paints no field, so like Kind=Outline it hands the whole ladder back rather
 * than keeping the light one `.ds-callout` restores: a caption under a blue band is in the band's ink. */
.ds-callout--footnote {
  padding: 24px 0;
  background: none;
  box-shadow: none;
  --ds-ink: inherit;
  --ds-ink-inverse: inherit;
  --ds-ink-body: inherit;
  --ds-ink-muted: inherit;
  --ds-ink-placeholder: inherit;
  --ds-rule: inherit;
  --ds-rule-strong: inherit;
  --ds-rule-outline: inherit;
  --ds-accent-ink: inherit;
  --ds-focus: inherit;
  --ds-surface-tint: inherit;
  --ds-surface-hover: inherit;
  --ds-surface-active: inherit;
}
.ds-callout--footnote .ds-callout__body { color: var(--ds-ink); }
.ds-callout--section { background: var(--ds-surface-paper); color: var(--ds-ink); box-shadow: none; }
/* Kind=Outline: the box the tier bands draw for WHAT YOU'LL SHIP, as a variant a page can name. No
 * fill, so it is not a ground: it hands the whole ink ladder — the hairlines, the accent ink and
 * the FOCUS RING with them — back to the field it sits on, rather than restoring the light one the
 * way a filled callout does. Its hairline is --ds-rule-outline and its label is the eyebrow in the
 * field's accent ink rather than a lime chip.
 *
 * The ring is the one this list kept forgetting: `.ds-callout` restores --ds-focus to the light
 * page's blue, so an action inside an outlined box on the brand band rang brand blue on brand blue
 * while the band's own lime ring never reached inside it. Six tokens came back and the seventh did
 * not; a token handed back has to be handed back whole. */
/* The outline master's statement is FILL in its 720 box, so it takes the box's measure, not a ch cap. */
.ds-callout--outline .ds-callout__statement { max-width: none; }
.ds-callout--outline {
  background: none;
  gap: 12px;
  padding: var(--space-sm);
  box-shadow: inset 0 0 0 1px var(--ds-rule-outline);
  --ds-ink: inherit;
  --ds-ink-inverse: inherit;
  --ds-ink-body: inherit;
  --ds-ink-muted: inherit;
  --ds-ink-placeholder: inherit;
  --ds-rule: inherit;
  --ds-rule-strong: inherit;
  --ds-rule-outline: inherit;
  --ds-accent-ink: inherit;
  --ds-focus: inherit;
  --ds-card-link-ink: inherit;
  /* The state pair travels with the ladder: the box paints no field, so its hover tint mixes against
     the BAND's field and its ink, exactly as the band's own blocks do. Restoring these instead would
     hover on the page's field inside a blue band. */
  --ds-surface-tint: inherit;
  --ds-surface-hover: inherit;
  --ds-surface-active: inherit;
}
.ds-products--brand { color: inherit; }

/* ── Ask Ace ───────────────────────────────────────────────────────────────────────────────── */

/* The masters stack chip, heading, intro and console 40 apart at both sizes: one rhythm, not the
   section heading's. */
.ds-ace { display: flex; flex-direction: column; gap: var(--space-lg); }
/* Centred, and the console is narrower than the band it sits in — a chat window the width of the
   page reads as an application, not as an invitation. */
.ds-ace__head { display: flex; flex-direction: column; align-items: center; gap: var(--space-lg); text-align: center; }
/* The master's chip is 24 tall, 10 either side of its label: a size up from a card's chip. */
.ds-ace__chip { align-self: center; padding: 6px 10px; }
.ds-ace__title { margin: 0; max-width: 40ch; }
/* FIXED 520 on Size=Desktop, FILL on Size=Mobile. */
.ds-ace__intro { margin: 0; max-width: 520px; color: var(--ds-ink-body); }

/* The console is an inverted panel on the page, like the footer: black in the light, one rung above
   the page in the dark. The Ask Ace masters draw no stroke on the card, so it takes no ring. */
.ds-ace__console { display: flex; flex-direction: column; width: 100%; max-width: 720px; margin-inline: auto; background: var(--ds-surface-invert-panel); color: var(--ds-ink-on-invert); }
/* 48 with its rule: the master's Header strokes inside its 14 of padding. */
.ds-ace__bar { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 14px 16px 13px; border-block-end: 1px solid color-mix(in srgb, currentColor 18%, transparent); }
/* The bar names the assistant beside its own face, as the canvas does. */
.ds-ace__brand { display: inline-flex; align-items: center; gap: 10px; min-width: 0; }
.ds-ace__who { opacity: 0.8; }
.ds-ace__live { display: inline-flex; align-items: center; gap: 6px; opacity: 0.8; }
/* The dot breathes: it is what says the answers are live and not a script. */
.ds-ace__live::before { content: ''; width: 6px; height: 6px; background: var(--ds-accent); }

/* The conversation hugs its lines on a phone (Size=Mobile Body is HUG) and is a fixed 300 window from
   the tablet up (Size=Desktop Body is FIXED 300), scrolling inside rather than growing the band. */
.ds-ace__log { display: flex; flex-direction: column; gap: 16px; padding: 16px; max-height: 24rem; overflow-y: auto; outline: none; }
@media (min-width: 768px) { .ds-ace__log { height: 300px; max-height: none; } }
/* The assistant's line runs the log's width beside its face, as the Greeting row does (FILL). */
.ds-ace__row { display: flex; gap: 10px; align-items: flex-start; }
/* The reader's own words sit on the right in a field of their own; the assistant's on the left,
   beside its face. The field is the brand's blue in the light and a lift off the console in the
   dark: the role, not a rung, because a rung of the dark ladder inside another rung is invisible. */
.ds-ace__row--user { align-self: flex-end; justify-content: flex-end; max-width: 85%; }
.ds-ace__avatar { flex: none; display: block; width: 20px; height: 20px; overflow: hidden; }
.ds-ace__avatar > img { display: block; width: 100%; height: 100%; object-fit: cover; }
.ds-ace__text { margin: 0; white-space: pre-line; }
.ds-ace__bubble { margin: 0; padding: 8px 14px; background: var(--ds-surface-on-invert); color: var(--ds-ink-on-invert); }
/* An 8×15 block of the accent: at the end of a line it is the line still arriving; alone on a line
   it is the wait for the next one, breathing. */
.ds-ace__caret { display: inline-block; width: 8px; height: 15px; margin-inline-start: 2px; vertical-align: -2px; background: var(--ds-accent); }
@media (prefers-reduced-motion: no-preference) {
  .ds-ace__console:not([data-status]) .ds-ace__live::before, .ds-ace__caret--pulse { animation: ds-ace-pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; }
}
@keyframes ds-ace-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } }
/* Answers not coming: the dot is still and in the panel's own ink, so nothing claims to be live. */
.ds-ace__console[data-status] .ds-ace__live::before { background: currentColor; opacity: 0.6; }

.ds-ace__suggestions { display: flex; flex-wrap: wrap; gap: 8px; padding: 0 16px 16px; }
/* 30 with its hairline: the master strokes inside 7 by 10 of padding. */
.ds-ace__suggestion { padding: 6px 9px; border: 1px solid color-mix(in srgb, currentColor 30%, transparent); background: none; color: inherit; cursor: pointer; transition: border-color var(--ds-fast, 140ms) var(--ds-ease, ease); }
.ds-ace__suggestion:hover:not(:disabled) { border-color: color-mix(in srgb, currentColor 70%, transparent); }
.ds-ace__suggestion:disabled { opacity: 0.5; cursor: default; }
.ds-ace__suggestion:focus-visible { outline: var(--ds-focus-width) solid var(--ds-focus); outline-offset: var(--ds-focus-offset); }

/* The Input row draws no rule: it sits 0 under the suggestions and 10 over the footer. */
.ds-ace__form { display: flex; align-items: center; gap: 10px; padding: 0 16px 10px; }
/* On a phone the master stacks everything full width: the suggestions, the field, the send control. */
@media (max-width: 479.98px) {
  .ds-ace__suggestions { flex-direction: column; flex-wrap: nowrap; gap: 16px; }
  .ds-ace__suggestion { text-align: start; }
  .ds-ace__form { flex-direction: column; align-items: stretch; gap: 12px; }
  .ds-ace__send { justify-content: center; }
}
/* The field is a hairline box that takes the accent on focus, as the live consoles draw it. */
/* 49 tall, the master's field: 13 by 14 of padding with the hairline inside it, set in web/body. */
.ds-ace__input { flex: 1 1 auto; min-width: 0; min-height: 44px; padding: 12px 13px; color: inherit; background: none; border: 1px solid color-mix(in srgb, currentColor 30%, transparent); transition: border-color var(--ds-fast, 140ms) var(--ds-ease, ease); }
.ds-ace__input::placeholder { color: currentColor; opacity: 0.55; }
.ds-ace__input:focus-visible { border-color: var(--ds-accent); outline: var(--ds-focus-width) solid var(--ds-focus); outline-offset: var(--ds-focus-offset); }
/* A quiet box until there is something to send, the accent the moment there is, white under the
   pointer. The dimmed label is the state, not a defect — WCAG exempts an inactive control. */
.ds-ace__send { display: inline-flex; align-items: center; justify-content: center; gap: 6px; min-width: 120px; min-height: 48px; padding-inline: 24px; border: 0; background: var(--ds-accent); color: var(--ds-ink-on-accent-control); cursor: pointer; transition: background-color var(--ds-fast, 140ms) var(--ds-ease, ease), color var(--ds-fast, 140ms) var(--ds-ease, ease); }
.ds-ace__send:hover:not(:disabled) { background: var(--brand-white); }
.ds-ace__send:disabled { background: color-mix(in srgb, var(--ds-ink) 15%, transparent); color: var(--ds-ink); cursor: default; }
.ds-ace__send:disabled .ds-ace__send-label { opacity: 0.55; }
.ds-ace__send:focus-visible { outline: var(--ds-focus-width) solid var(--ds-focus); outline-offset: var(--ds-focus-offset); }

/* The footnote and the way out to a person wrap onto their own lines rather than colliding. */
/* The footer draws no rule either; on a phone the note and the way out stack 16 apart. */
.ds-ace__foot { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 8px 16px; padding: 6px 16px 14px; }
@media (max-width: 479.98px) { .ds-ace__foot { flex-direction: column; flex-wrap: nowrap; align-items: flex-start; gap: 16px; } }
.ds-ace__footnote { opacity: 0.7; }

/* ── Sign in, settings, docs, apply ────────────────────────────────────────────────────────── */

/* The band's pattern is a positioned layer and paints over every in-flow box in its stacking
   context, so the panel is positioned and stacked above it; the wrapper isolates so the index
   reaches nothing outside the component. */
.ds-signin { display: flex; justify-content: center; isolation: isolate; }
.ds-signin__eyebrow { color: var(--ds-ink-muted); }
.ds-signin__panel { position: relative; z-index: 1; display: flex; flex-direction: column; gap: 16px; width: 100%; max-width: 26rem; padding: var(--space-md); background: var(--ds-surface); color: var(--ds-ink); box-shadow: inset 0 0 0 1px var(--ds-rule); }
.ds-signin__title, .ds-signin__intro { margin: 0; }
.ds-signin__intro { color: var(--ds-ink-body); }
.ds-signin__error { margin: 0; color: var(--ds-error); }
.ds-signin__methods { display: flex; flex-direction: column; gap: 10px; }
.ds-signin__helper, .ds-signin__notice { margin: 0; color: var(--ds-ink-muted); }

.ds-settings__inner { display: flex; flex-direction: column; gap: var(--space-lg); padding-block: var(--space-lg); }
.ds-settings__head { display: flex; flex-direction: column; gap: 8px; }
.ds-settings__title, .ds-settings__intro { margin: 0; }
.ds-settings__intro { color: var(--ds-ink-body); }
.ds-settings__layout { display: grid; gap: var(--space-lg); }
@media (min-width: 900px) { .ds-settings__layout { grid-template-columns: minmax(0, 220px) minmax(0, 1fr); gap: var(--space-xl); align-items: start; } }
.ds-settings-nav { display: flex; flex-direction: column; gap: 2px; }
/* Beside the panels the navigation stays in view as they scroll (owner, 28 Sep 2026), under the bar
   by the same clearance the article's side column and the doc rail use. */
@media (min-width: 900px) { .ds-settings-nav { position: sticky; top: calc(var(--ds-nav-pinned, 0px) + var(--space-md)); } }
.ds-settings-nav__item { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 10px 12px; color: var(--ds-ink-body); }
.ds-settings-nav__item[aria-current='page'] { background: var(--ds-surface-paper); color: var(--ds-ink); }
.ds-settings-nav__badge { color: var(--ds-ink-muted); }
.ds-settings__panels { display: flex; flex-direction: column; gap: var(--space-lg); min-width: 0; }
.ds-settings-group { display: flex; flex-direction: column; gap: 12px; }
.ds-settings-group__head { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: 8px 16px; }
.ds-settings-group__title, .ds-settings-group__desc { margin: 0; }
.ds-settings-group__desc { color: var(--ds-ink-muted); }
.ds-settings-group__body { border-block-start: 1px solid var(--ds-rule); }

/* ── Doc shell ─────────────────────────────────────────────────────────────────────────────── */

/* The shell paints its own ground so the bar, the document and the legal strip read as one page
   whatever field the host drops it on: the master sets all three on brand/white. */
.ds-doc { background: var(--ds-surface); color: var(--ds-ink); }
/* 260 for the contents, 64 between, and the rest for the document — the master's own measure, under
   64 of air and over 96. The third column, 200 wide, is this page's own headings; from 1280 it is
   always there, empty on a page with no headings, so every document keeps the measure a page with a
   rail has. Folded, the contents give up all but the 32 their button needs and the document takes
   the rest. */
.ds-doc__frame { container: ds-doc / inline-size; }
.ds-doc__inner { --ds-doc-rail: 260px; display: grid; gap: 64px; padding-block: 64px; }
@media (min-width: 900px) { .ds-doc__inner { grid-template-columns: minmax(0, var(--ds-doc-rail)) minmax(0, 1fr); gap: 64px; padding-block: 64px 96px; align-items: start; } }
/* Each part names its own cell, so the order the script gives them in the document for the keyboard
   moves nothing on the screen. */
@media (min-width: 1280px) {
  .ds-doc__inner { grid-template-columns: minmax(0, var(--ds-doc-rail)) minmax(0, 1fr) 200px; }
  .ds-doc__inner > .ds-doc__rail--contents { grid-column: 1; grid-row: 1; }
  .ds-doc__inner > .ds-doc__body { grid-column: 2; grid-row: 1; }
  .ds-doc__inner > .ds-doc__rail--here { grid-column: 3; grid-row: 1; }
}
.ds-doc__rail { display: flex; flex-direction: column; gap: 20px; }
/* Both rails ride the window, and each scrolls on its own when its list is taller than the window
   leaves: the rail stops 40 short of the foot as it starts 40 under the top (or the pinned bar), the
   list scrolls inside it and the fold button above the contents stays put. The scrolling box carries
   the room a focus ring needs, as padding for a link at either end and as scroll padding for a link
   the keyboard scrolls to in between, so the edge of the box never cuts the ring. */
@media (min-width: 900px) {
  .ds-doc__rail { position: sticky; top: calc(var(--ds-nav-pinned, 0px) + var(--space-lg)); max-height: calc(100vh - var(--ds-nav-pinned, 0px) - 2 * var(--space-lg)); }
  .ds-doc__contents, .ds-doc__rail--here > .ds-doc__group { --ds-doc-ring: calc(var(--ds-focus-width) + var(--ds-focus-offset)); min-height: 0; margin: calc(var(--ds-doc-ring) * -1); padding: var(--ds-doc-ring); scroll-padding-block: var(--ds-doc-ring); overflow-y: auto; }
}
/* Below the desktop scale the document comes first and its contents follow it, 64 under the updated
   line and behind a rule, as the master's Size=Mobile has it and the live documents keep it on a
   phone. Where each part sits on the screen is the stylesheet's; the order they sit in the document,
   which is the order the keyboard walks, is the script's, so it follows what is drawn. */
@media (max-width: 899.98px) { .ds-doc__rail--contents { order: 2; margin-block-start: 24px; padding-block-start: 28px; border-block-start: 1px solid var(--ds-rule); } }
/* Below the widest breakpoint there is no third column, so this page's own headings fold out above
   the document instead, in the box the master's Size=Mobile draws: the label and a chevron, 48.5 tall
   with its hairline, the list behind them, and 48 between the box and the breadcrumb (the grid's 64
   less 16). Without the script there is no button to open it, so the list stands open. */
@media (max-width: 1279.98px) {
  .ds-doc__rail--here { position: static; order: -1; gap: 0; max-height: none; margin-block-end: -16px; border: 1px solid var(--ds-rule); }
  .ds-doc__rail--here > .ds-doc__group { gap: 0; min-height: auto; margin: 0; padding: 0; overflow: visible; }
  .ds-doc__rail--here .ds-doc__group-heading { padding: 13px 16px 0; }
  .ds-doc__rail--here .ds-doc__items { margin: 12px 16px 16px; }
  .ds-doc[data-enhanced] .ds-doc__rail--here .ds-doc__group-heading { display: none; }
  .ds-doc[data-enhanced] .ds-doc__block-toggle { display: flex; }
  .ds-doc[data-enhanced] .ds-doc__rail--here:not([data-open]) > .ds-doc__group { display: none; }
  .ds-doc[data-enhanced] .ds-doc__rail--here[data-open] .ds-doc__items { margin-block-start: 0; }
}
/* With two columns the fold-out sits over the document, in its column. */
@media (min-width: 900px) and (max-width: 1279.98px) {
  .ds-doc__inner:has(.ds-doc__rail--here) > .ds-doc__rail--contents { grid-column: 1; grid-row: 1 / span 2; }
  .ds-doc__inner:has(.ds-doc__rail--here) > .ds-doc__rail--here { grid-column: 2; grid-row: 1; }
  .ds-doc__inner:has(.ds-doc__rail--here) > .ds-doc__body { grid-column: 2; grid-row: 2; }
}
.ds-doc__block-toggle { display: none; align-items: center; justify-content: space-between; gap: 12px; width: 100%; padding: 13px 16px 13.5px; border: 0; background: none; color: var(--ds-ink); text-align: start; cursor: pointer; }
.ds-doc__block-toggle[aria-expanded='true'] .ds-icon { transform: rotate(180deg); }
/* The fold. A real button at the top of the contents rail: the master's 32px hairline box with a
   chevron in it, and its words beside it while the contents show. Folded, only the box is left and
   the words stay for the reader who cannot see the box. The rail hides its list, not the button, so
   there is always a way back. Nothing is drawn until the script has set data-enhanced: a button that
   does nothing is worse than none, and a reader without the script gets the whole document open. */
.ds-doc__contents { display: flex; flex-direction: column; gap: 20px; }
.ds-doc__toggle { display: none; align-items: center; align-self: flex-start; gap: 12px; padding: 0; border: 0; background: none; color: var(--ds-ink-body); text-align: start; cursor: pointer; }
@media (min-width: 900px) { .ds-doc[data-enhanced] .ds-doc__toggle { display: inline-flex; } }
.ds-doc__toggle-box { display: inline-flex; flex: none; align-items: center; justify-content: center; width: 32px; height: 32px; border: 1px solid var(--ds-rule); color: var(--ds-ink); }
.ds-doc__toggle .ds-icon { transform: scaleX(-1); }
@media (hover: hover) { .ds-doc__toggle:hover { color: var(--ds-ink); } .ds-doc__toggle:hover .ds-doc__toggle-box { border-color: var(--ds-ink); } }
@media (prefers-reduced-motion: no-preference) { .ds-doc__toggle .ds-icon, .ds-doc__block-toggle .ds-icon { transition: transform var(--ds-fast, 140ms) var(--ds-ease, ease); } }
/* Folded because the reader said so, on every window wide enough for two columns. */
@media (min-width: 900px) {
  .ds-doc[data-contents='collapsed'] .ds-doc__inner { --ds-doc-rail: 32px; }
  .ds-doc[data-contents='collapsed'] .ds-doc__contents { display: none; }
  .ds-doc[data-contents='collapsed'] .ds-doc__toggle-label { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
  .ds-doc[data-contents='collapsed'] .ds-doc__toggle .ds-icon { transform: none; }
}
/* Folded because the document would otherwise be squeezed. The measure is sixty-five characters of
   the document's 15px face. Set in wrapped lines, that face runs 7.1 to 7.9px a character across the
   three document sites (a line loses the last word's worth of room, so the run of a whole paragraph,
   6.8, understates it), so the widest, 7.9, gives 514px: the frame's content box has to leave the
   document that much after the 260 of contents and the 64 between, and, from 1280, its 64 and 200.
   A reader's own choice sits on the shell as data-contents and outranks this. */
@media (min-width: 900px) {
  @container ds-doc (max-width: 838px) {
    .ds-doc[data-enhanced]:not([data-contents]) .ds-doc__inner { --ds-doc-rail: 32px; }
    .ds-doc[data-enhanced]:not([data-contents]) .ds-doc__contents { display: none; }
    .ds-doc[data-enhanced]:not([data-contents]) .ds-doc__toggle-label { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
    .ds-doc[data-enhanced]:not([data-contents]) .ds-doc__toggle .ds-icon { transform: none; }
  }
}
@media (min-width: 1280px) {
  @container ds-doc (max-width: 1102px) {
    .ds-doc[data-enhanced]:not([data-contents]) .ds-doc__inner { --ds-doc-rail: 32px; }
    .ds-doc[data-enhanced]:not([data-contents]) .ds-doc__contents { display: none; }
    .ds-doc[data-enhanced]:not([data-contents]) .ds-doc__toggle-label { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
    .ds-doc[data-enhanced]:not([data-contents]) .ds-doc__toggle .ds-icon { transform: none; }
  }
}
.ds-doc__group { display: flex; flex-direction: column; gap: 8px; }
.ds-doc__group-heading { margin: 0; color: var(--ds-ink-muted); }
/* One rail down the whole list, 16 in from it — the master draws it on the group, not per link. */
.ds-doc__items { display: flex; flex-direction: column; margin: 0; padding: 0 0 0 16px; list-style: none; border-inline-start: 1px solid var(--ds-rule); }
/* A 16px line on a 24px box. The canvas sets the items 22 apart; 24 is the smallest pitch at which
   each row is still a target (WCAG 2.5.8). */
.ds-doc__link { display: block; padding-block: 4px; color: var(--ds-ink-body); }
/* The page the reader is on: ink, bold, and a 3px accent bar laid over the rail at the same x as its
   neighbours. The master paints that bar brand/blue on the light variant and brand/lime on the dark
   one, which is the accent ink, not the lime accent. */
.ds-doc__link:is([aria-current='page'], [aria-current='location']) { margin-inline-start: -17px; padding-inline-start: 14px; border-inline-start: 3px solid var(--ds-accent-ink); color: var(--ds-ink); font-weight: 700; }
/* An h3 under its h2 sits 12 in from it, current or not. */
.ds-doc__item--sub > .ds-doc__link { padding-inline-start: 12px; }
.ds-doc__item--sub > .ds-doc__link[aria-current='location'] { padding-inline-start: 26px; }
/* The document's measure is the column the canvas gives it — 856 of the 1180 — not the 70ch a
   loose paragraph gets. The tables and the h2 rows in a policy need the full column. */
.ds-doc__body { min-width: 0; }
.ds-doc__body .ds-prose { max-width: none; }
/* Trail, title, lede, opening paragraph: 48 between each, the article's own rhythm on the master. */
.ds-doc__head { display: flex; flex-direction: column; gap: 48px; margin-block-end: 48px; }
.ds-doc__titling { display: flex; flex-direction: column; gap: 16px; }
.ds-doc__title, .ds-doc__effective { margin: 0; }
.ds-doc__lede { margin: 0; max-width: none; color: var(--ds-ink-body); }
/* The lede sits outside the prose, so its links would take the browser's blue; they take the accent ink the prose links do. */
.ds-doc__lede a { color: var(--ds-accent-ink); text-underline-offset: 3px; }
/* Trail and stamp are facts, not labels: the master sets both in web/caption at 60% ink, sentence
   case, and the live document sites set the same 13px line (COP-01). */
.ds-doc__trail { color: var(--ds-ink-muted); }
.ds-doc__crumbs { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 8px; margin: 0; padding: 0; list-style: none; }
.ds-doc__crumb { display: inline-flex; align-items: center; gap: 8px; }
/* The separator is a node of its own, the way the master draws it, and it is hidden from assistive
   technology in the markup rather than by a CSS trick that browsers read out anyway. */
.ds-doc__crumb-sep { color: var(--ds-ink-muted); }
/* A crumb that goes somewhere says so at rest: the master underlines the link and leaves the page
   itself plain, and hovering takes it to full ink rather than fading it. */
.ds-doc__crumb-link { display: inline-block; padding-block: 3px; margin-block: -3px; text-decoration: underline; text-underline-offset: 2px; }
.ds-doc__crumb-link:hover, .ds-doc__crumb-link:focus-visible { color: var(--ds-ink); opacity: 1; }
/* Each section of a document opens on a rule: 48 above it, 32 under it, 16 to the first line, at the
   h3 size the canvas sets. */
.ds-doc__body .ds-prose h2 { margin-block: 48px 16px; padding-block-start: 32px; border-block-start: 1px solid var(--ds-rule); }
@media (min-width: 768px) { .ds-doc__body .ds-prose h2 { font-size: 43px; line-height: 44px; } }
/* Links in a document are set in the accent ink — brand blue on the light master, lime on the dark. */
.ds-doc__body .ds-prose a { color: var(--ds-accent-ink); }
/* A table is the master's full grid: hairline cells, 12/16 inside each, the header row on the paper
   field, the key column 160 wide. Long words break rather than push the grid past the column —
   break-word, not anywhere, so the key column keeps its measure instead of shrinking to one letter. */
.ds-doc__body .ds-prose table { width: 100%; border-collapse: collapse; border: 1px solid var(--ds-rule); }
.ds-doc__body .ds-prose th, .ds-doc__body .ds-prose td { padding: 12px 16px; border: 1px solid var(--ds-rule); text-align: start; vertical-align: top; overflow-wrap: break-word; }
@media (min-width: 900px) { .ds-doc__body .ds-prose tr > :first-child { width: 160px; } }
.ds-doc__body .ds-prose th { background: var(--ds-surface-paper); color: var(--ds-ink); font-weight: 700; }
/* At the foot of the document, 48 under the last section, where the master puts it. */
.ds-doc__updated { display: block; margin-block-start: 48px; color: var(--ds-ink-muted); }

/* The document sites' own bar and legal strip: 72 and 70 tall on the canvas, both hairlined. The
   links never wrap; on a phone the bar drops to 64 and the labels a step, so three words and the
   toggle share one row with the lockup. */
.ds-doc-header { border-block-end: 1px solid var(--ds-rule); background: var(--ds-surface); color: var(--ds-ink); }
/* The master draws one row at both its sizes — 390 at 64 tall, 1440 at 72 — so the bar holds one row
   down to the width where a 26px lockup and three links stop fitting (381), and wraps below
   that rather than overflowing, which is what the live bar does at the same widths. */
.ds-doc-header__bar { display: flex; flex-wrap: nowrap; align-items: center; gap: 4px 12px; min-height: 64px; }
@media (min-width: 600px) { .ds-doc-header__bar { min-height: 72px; } }
.ds-doc-header__brand { display: inline-flex; align-items: center; margin-inline-end: auto; }
/* Size=Mobile draws the lockup at 26 so the row holds; Size=Desktop at 30. The height is inline on
   the lockup, so the bar caps it rather than setting it. The 10 above and below is what the live bar
   gives the wrapped rows, and it keeps the current rule inside the bar on the phones too narrow to
   hold one row; at 390 the bar still stands at its 64 minimum with the row centred in it. */
@media (max-width: 519.98px) {
  .ds-doc-header__bar { padding-block: 10px; }
  .ds-doc-header__brand .ds-lockup, .ds-doc-header__brand .ds-lockup__art { max-height: 26px; }
}
/* The master's own gaps: 28 between the links, before the hairline and after it on Size=Desktop, 12
   on Size=Mobile. The live bar changes at 520 and so does this. */
.ds-doc-header__group { display: flex; flex: none; align-items: center; gap: 12px; }
.ds-doc-header__links { display: flex; flex-wrap: nowrap; align-items: center; gap: 12px; }
@media (max-width: 519.98px) { .ds-doc-header .ds-nav__tools--divided { padding-inline-start: 13px; } }
@media (min-width: 520px) { .ds-doc-header__group, .ds-doc-header__links { gap: 28px; } }
.ds-doc-header__link { position: relative; white-space: nowrap; color: var(--ds-ink-muted); }
/* A link on this bar darkens on hover rather than fading, the way the live document bars do; the
   library's default link fade would take a muted link further away from the reader. */
.ds-doc-header__link:hover { color: var(--ds-ink); opacity: 1; }
/* The page the reader is on: full ink under the master's 2px rule in the accent ink — brand blue on
   a light field, lime on a dark one, because the corporate set draws no lime on white. The rule is
   its own box under the link, not a text decoration, so the focus ring passes between the link and
   the rule instead of painting over it: 6 down where the master draws 4, which is the 2 the ring
   needs to clear it, the corporate ring being the 3px at offset 2 the whole library draws. */
.ds-doc-header__link[aria-current='page'] { color: var(--ds-ink); }
.ds-doc-header__link[aria-current='page']::after { content: ''; position: absolute; inset-inline: 0; inset-block-end: -8px; height: 2px; background: var(--ds-accent-ink); }
/* On the narrowest phones the lockup, three labels and the control cannot share 272px: the labels
   take a row of their own under the lockup rather than running past the edge. Above 360 the bar
   holds one row, which is what the master draws at both its sizes. */
@media (max-width: 359.98px) {
  .ds-doc-header__bar { flex-wrap: wrap; row-gap: 8px; padding-block: 12px; }
  .ds-doc-header__links { flex-basis: 100%; }
}
.ds-doc-legal { border-block-start: 1px solid var(--ds-rule); background: var(--ds-surface); color: var(--ds-ink); }
.ds-doc-legal__bar { display: flex; flex-wrap: wrap; align-items: center; gap: 12px 24px; padding-block: var(--space-sm); }
.ds-doc-legal__copyright { color: var(--ds-ink-muted); }
.ds-doc-legal__links { display: flex; flex-wrap: wrap; gap: 12px 24px; margin-inline-start: auto; }
/* On a phone the legal row is one left column (rule 35), not a list hung on the right. */
@media (max-width: 599.98px) { .ds-doc-legal__links { margin-inline-start: 0; } }
.ds-doc-legal__link { color: var(--ds-ink-muted); }

/* The head stacks over the band's ground, as the hero's copy does: the ground is positioned, and an
   unpositioned head is painted under it. The back link and the meta line are the master's full
   white; dimmed, they met the field plus the ground's 8% at 4.28:1 and 3.97:1. */
.ds-apply__head { position: relative; display: flex; flex-direction: column; gap: 12px; }
.ds-apply__title, .ds-apply__intro { margin: 0; }
.ds-apply__back { align-self: flex-start; }
.ds-apply__meta { margin: 0; }
.ds-apply__title { max-width: 20ch; }
.ds-apply__intro { max-width: 56ch; }
.ds-apply__inner { display: grid; gap: var(--space-lg); }
@media (min-width: 1000px) { .ds-apply--split .ds-apply__inner { grid-template-columns: minmax(0, 720px) minmax(0, 1fr); gap: var(--space-xl); align-items: start; } }
.ds-apply__form { max-width: 720px; width: 100%; }
.ds-apply--full .ds-apply__form { margin-inline: auto; }
.ds-apply__aside { min-width: 0; }

/* ── Consent banner ───────────────────────────────────────────────────────────────────────────── */

/* Pinned to the foot of the window over the page, on the raised surface, under a hairline. */
.ds-consent { position: fixed; inset-inline: 0; inset-block-end: 0; z-index: 50; background: var(--ds-surface-chrome); color: var(--ds-ink); border-block-start: 1px solid var(--ds-rule); }
.ds-consent__inner { display: flex; flex-direction: column; gap: 12px; padding-block: 16px; }
.ds-consent__message { margin: 0; max-width: 640px; color: var(--ds-ink-body); }
/* The link is underlined, as both sets' masters draw it: in the sentence's ink it is 2.08:1 from the
   text on the light bar and 1.57:1 on the dark one, so colour alone does not mark it (WCAG 1.4.1).
   The rule outranks `.ds-link`, which a site's anchor usually carries and which takes the line off. */
.ds-consent__message a { color: var(--ds-ink); text-decoration-line: underline; text-underline-offset: 2px; }
/* Both sets' Size=Mobile masters start the two answers where the sentence starts — the actions frame
   sits at the gutter (ja MIN) — so the row is start-aligned until the bar becomes one line. */
.ds-consent__actions { display: flex; align-items: center; justify-content: flex-start; gap: 16px; }
/* Reopened from the footer, the region itself holds focus until the reader moves on. */
.ds-consent:focus-visible { outline: var(--ds-focus-width) solid var(--ds-focus); outline-offset: var(--ds-focus-offset); }
@media (min-width: 768px) {
  .ds-consent__inner { flex-direction: row; align-items: center; gap: 32px; }
  .ds-consent__actions { margin-inline-start: auto; flex: none; justify-content: flex-end; }
}
/* The product bar sits on the page rather than a raised field: the page at 95% over a 24px blur, so
   what scrolls under it shows through, as the live product sites draw it. The answers keep the set's
   CTA voice and its control radius; only the quiet one is muted until hovered. On a phone the two
   answers start where the sentence does. */
:where(.ds-product) .ds-consent { background: color-mix(in srgb, var(--ds-surface-chrome) 95%, transparent); -webkit-backdrop-filter: blur(24px); backdrop-filter: blur(24px); }
:where(.ds-product) .ds-consent__actions { justify-content: flex-start; }
:where(.ds-product) .ds-consent .ds-button--quiet { color: var(--ds-ink-muted); }
:where(.ds-product) .ds-consent .ds-button--quiet:hover { color: var(--ds-ink); }

/* ── Back to top ──────────────────────────────────────────────────────────────────────────────── */

/* A 48 square in the lower right — square, like everything else in the corporate set — inverted so
   it reads on either theme, with a 25% hairline so it still has an edge on a dark page. Hidden until
   wanted. It rides above anything docked to the foot of the window (see --ds-dock in theme.css). */
.ds-btt { position: fixed; inset-block-end: calc(var(--ds-dock-inset) + var(--ds-dock, 0px)); inset-inline-end: var(--ds-dock-inset); z-index: 40; display: inline-flex; align-items: center; justify-content: center; width: var(--ds-dock-control); height: var(--ds-dock-control); padding: 0; border: 1px solid color-mix(in srgb, var(--ds-ink-on-invert) 25%, transparent); border-radius: var(--ds-radius-control); background: var(--ds-surface-invert); color: var(--ds-ink-on-invert); font-size: 20px; line-height: 1; cursor: pointer; opacity: 0; transform: translateY(12px); pointer-events: none; box-shadow: var(--ds-shadow-raised); transition: opacity var(--ds-fast, 140ms) var(--ds-ease, ease), transform var(--ds-fast, 140ms) var(--ds-ease, ease), background-color var(--ds-fast, 140ms) var(--ds-ease, ease), color var(--ds-fast, 140ms) var(--ds-ease, ease), inset-block-end var(--ds-fast, 140ms) var(--ds-ease, ease); }
.ds-btt[data-visible='true'] { opacity: 1; transform: none; pointer-events: auto; }
.ds-btt:hover { background: var(--ds-accent); color: var(--ds-ink-on-accent-control); border-color: transparent; }
.ds-btt:active { background: var(--ds-accent); color: var(--ds-ink-on-accent-control); border-color: transparent; transform: translateY(1px); }
.ds-btt:focus-visible { outline: var(--ds-focus-width) solid var(--ds-focus); outline-offset: var(--ds-focus-offset); }
/* Below 900 the gutter is narrower than the control's lane (inset + control + inset), so a page that
   carries the control reserves that lane under its last line; from 900 up the gutter itself clears it. */
@media (max-width: 899.98px) {
  :where(:root, .ds-root):has(.ds-btt) { --ds-dock-lane: calc(var(--ds-dock-inset) * 2 + var(--ds-dock-control)); }
}

/* Two default glyphs, one shown: the text arrow on the corporate square, the line arrow on the
   product disc. The theme picks, so a site never has to. */
.ds-btt__icon { display: none; width: 18px; height: 18px; }
/* The product control is a 44 disc on the raised surface under a hairline, with the raised shadow the
   live product sites give it; the arrow is body ink and takes the accent ink on hover, the field
   stays. */
:where(.ds-product) .ds-btt { width: var(--ds-dock-control); height: var(--ds-dock-control); border: 1px solid var(--ds-rule); border-radius: 50%; background: var(--ds-surface-chrome); color: var(--ds-ink-body); box-shadow: var(--ds-shadow-raised); }
:where(.ds-product) .ds-btt:hover { background: var(--ds-surface-chrome); color: var(--ds-accent-ink); border-color: var(--ds-rule); }
:where(.ds-product) .ds-btt__glyph { display: none; }
:where(.ds-product) .ds-btt__icon { display: block; }

/* ── Skip link ────────────────────────────────────────────────────────────────────────────────── */

/* First in the tab order, parked 96 above the window (the master's offset, so the ring clears it too)
   until keyboard focus arrives, then the accent block at 16/16 over every other layer. The accent pair
   is the primary button's, so it is lime with black ink on both corporate themes and the brand colour
   with white ink inside a product page. Corners follow the masters: the chip's on the corporate set
   (radius/chip), the control's on the product set (radius/product-md). The 3px ring sits 2 out. */
.ds-skip { position: fixed; inset-block-start: 16px; inset-inline-start: 16px; z-index: 100; display: inline-block; padding: 12px 16px; border-radius: var(--ds-radius-chip); background: var(--ds-accent); color: var(--ds-ink-on-accent-control); text-decoration: none; white-space: nowrap; transform: translateY(-96px); transition: transform var(--ds-fast, 140ms) var(--ds-ease, ease); }
.ds-skip:focus { transform: none; }
.ds-skip:focus-visible { outline: var(--ds-focus-width) solid var(--ds-focus); outline-offset: var(--ds-focus-offset); }
:where(.ds-product) .ds-skip { border-radius: var(--ds-radius-control); }

/*
 * The product set.
 *
 * Cueprise and Storefront are standalone brands: their own colour, their own faces, their own
 * backgrounds. They share the corporate library's structure and none of its palette, so a product
 * page wraps itself in `.ds-product` and the role tokens point somewhere else from there down.
 *
 * `data-brand` picks which product. Nothing outside this file knows either brand's hex.
 */

:where(.ds-product) {
  color-scheme: light;
  --ds-color-scheme-base: light;
  --ds-surface: var(--brand-white);
  --ds-surface-control: var(--brand-white);
  /* A chosen card in the product set does NOT invert: `Element=Radio card selected` keeps the
     card's own field in both themes and says so with a 2px rule and the chip. An inverted panel in
     the middle of a light product form reads as a different surface, not as an answer. */
  --ds-selected-surface: var(--ds-surface-control);
  --ds-selected-ink: var(--ds-ink);
  --ds-selected-rule: var(--brand-black);
  --ds-surface-paper: var(--cueprise-lilac);
  --ds-surface-raised: #ffffff;
  /* The blueprint 404's ground. `Not found / product` (2861:345042) draws the band on brand/white
     like `Section / product Field=Paper`, and in the DARK on dark/raised where that master takes
     dark/base — one rung apart, and the master is right: the two Not found masters are one 404
     blueprint, so the product field is the corporate twin's field (DARK-01). No other product band
     wants that pair, so it is a role of its own rather than a Field the canvas never draws. */
  --ds-surface-blueprint: var(--ds-surface);
  /* Chrome that floats over a product page: white in the light on both brands, as Consent banner /
     product and Back to top / product draw it, and the brand's raised tone in the dark. */
  --ds-surface-chrome: var(--brand-white);
  --ds-surface-invert: var(--cueprise-deep);
  /* The product footer is an inverted PANEL, not an inverted band: in the light both are the deep
     field, and in the dark the Footer / product masters put the panel on dark/base while a deep BAND
     (Section / product Field=Deep, the hero) goes to dark/hero. One role each, as the corporate set
     has had since the footer shipped four rungs from its page. */
  --ds-surface-invert-panel: var(--cueprise-deep);
  --ds-surface-invert-panel-on-paper: var(--cueprise-deep);
  --ds-surface-brand: var(--cueprise-brand);
  /* A product keeps its own backgrounds in both themes, so its hero field is its brand field and
     never the corporate ladder the root theme would otherwise hand it in the dark. */
  --ds-surface-hero: var(--ds-surface-brand);
  --ds-surface-accent: var(--cueprise-lilac);
  --ds-surface-sand: var(--storefront-sand);
  /* A product field is not the page it sits on: every Form / product master fills the input, the
     textarea, the select trigger and the highlighted option with neutral/sunken, a rung under the
     white panel around them. The corporate set has no such rung and leaves this the surface. */
  --ds-surface-sunken: var(--neutral-sunken);
  /* The Select Open master fills the highlighted row with the trigger's own well, not with the brand
     lilac --ds-surface-paper holds here — so the row follows the well in both themes. */
  --ds-surface-row-hover: var(--ds-surface-sunken);
  --ds-accent: var(--cueprise-brand);
  --ds-ink-on-accent-control: var(--brand-white);
  /* Unlike the corporate lime, a product accent is a legitimate ink on a light ground — the brand
     purple measures 10:1 on white and the brand blue 6.4:1 — and the canvas sets its eyebrows,
     mono labels and closing lines in it. The corporate rule that the accent is never ink does not
     travel here, so the token is re-pointed rather than inherited. */
  --ds-accent-ink: var(--ds-accent);
  /* The product footer's own top edge: every Footer / product variant carries a 1px inside top
     stroke of white at 12%, both themes, over the deep field. Declared here so the product set does
     not lean on the corporate root block for it. */
  --ds-rule-on-invert: rgb(255 255 255 / 0.12);

  /* A row outside a band inherits the scope's ink, never the page's. */
  color: var(--ds-ink);
  --ds-ink: var(--neutral-ink-heading);
  --ds-ink-inverse: var(--brand-white);
  --ds-ink-body: var(--neutral-ink-body);
  --ds-ink-muted: var(--neutral-ink-muted);
  --ds-ink-placeholder: var(--ds-ink-muted);
  --ds-ink-on-brand: var(--brand-white);
  /* The canvas sets every `product-web/body-17` on a deep field at 80% white — ten of them across
     page 15, and the live product heroes run lower still at 76%. At 90% the lede sat almost as
     bright as the headline and the band lost its order. Product scope only; corporate keeps 92%. */
  --ds-ink-body-on-brand: rgb(255 255 255 / 0.8);
  --ds-ink-on-accent: var(--brand-white);
  --ds-ink-on-invert: var(--brand-white);

  --ds-rule: var(--neutral-hairline);
  --ds-rule-strong: rgb(16 24 40 / 0.28);
  /* A text field's edge: input, textarea and the select trigger. Both live product sites draw every
     field in their card border (#e4e7ec light, white at 10% dark, both brands), not the strong rule. */
  --ds-rule-field: var(--cutout-border);
  --ds-focus: var(--state-focus-product);
  --ds-lift: var(--cueprise-lift);
  /* The product's hover field is the canvas's own `cutout/hover` — #F9F5FF for Cueprise, #EFF4FD for
     Storefront, and the two deep tones in the dark — which is exactly the `--card-hover` the live
     product sites paint on a card and on a trade chip (cueprise globals.css `.chip:hover`). It is
     opaque already, so it takes no mix and no approximation; it follows brand and theme because
     `--ds-cutout-hover` is re-pointed in each of the four blocks below, all of which match the same
     element as this one. Pressed goes one step further in the brand, which the canvas has no
     variable for. */
  --ds-surface-tint: var(--ds-accent);
  --ds-surface-mix: 5%;
  --ds-surface-mix-strong: 8%;
  --ds-surface-hover: var(--ds-cutout-hover);
  --ds-surface-active: color-mix(in srgb, var(--ds-surface-tint) var(--ds-surface-mix), var(--ds-surface-hover));
  --ds-surface-hover-base: var(--ds-surface-hover);
  --ds-surface-active-base: var(--ds-surface-active);
  /* The product sites reveal a touch further and slower than the corporate ones, siblings 90ms apart,
     and settle on the plain bezier — Cueprise and Storefront carry no spring. */
  --ds-reveal-rise: 14px;
  --ds-reveal-duration: 620ms;
  --ds-reveal-stagger: 90ms;
  --ds-reveal-ease: var(--ds-ease);

  /* The two depths a product site casts, and the only two: a cutout of the interface or a card that
     lifts under the pointer takes `card`; chrome that floats over the page — an open listbox, the
     back-to-top control — takes `raised`, and never borrows the cutout's depth. The corporate theme
     draws nothing for either token. */
  --ds-shadow-card: 0 16px 40px -8px rgb(16 24 40 / 0.1), 0 4px 12px -2px rgb(16 24 40 / 0.06);
  --ds-shadow-raised: 0 8px 24px -8px rgb(16 24 40 / 0.16);
  /* Product chips take the product radius; the corporate 3px chip stays the corporate set's own. */
  --ds-radius-chip: var(--radius-product-sm);
  /* Corners. A product is rounded like the live product sites: radius/product-md on every control,
     card, input and cutout, radius/product-thumb on a thumbnail or a small square, radius/product-sm
     on a chip. No pills. The corporate set leaves the control radius undefined and the shared rules
     fall back to square; the app kit's own corners are below. */
  --ds-radius-control: var(--radius-product-md);
  --ds-radius-card: var(--radius-product-md);
  --ds-radius-thumb: var(--radius-product-thumb);

  --ds-ink-base: var(--ds-ink);
  --ds-ink-body-base: var(--ds-ink-body);
  --ds-ink-muted-base: var(--ds-ink-muted);
  --ds-ink-placeholder-base: var(--ds-ink-placeholder);
  --ds-rule-base: var(--ds-rule);
  --ds-rule-strong-base: var(--ds-rule-strong);
  --ds-accent-ink-base: var(--ds-accent-ink);
  --ds-focus-base: var(--ds-focus);

  /* Status. The only hues outside the brand, and they earn it: a stock state that cannot be told
     apart at a glance is not a stock state. Each tone is an ink and the field that ink sits on, from
     one Figma variable pair, so a chip cannot mix a light field with a dark-mode ink. The corporate
     set has none of these — red is its only state colour.
     These six are the whole status palette: the chip, the kit's status pill and the app pill all read
     them, and the set that used to sit above under `--ds-state-*` was a hand-copied hex duplicate of
     the same six values, so a canvas change moved two of the three and left the chip behind. */
  --ds-ok: var(--state-ok);
  --ds-ok-field: var(--state-ok-field);
  --ds-warn: var(--state-warn);
  --ds-warn-field: var(--state-warn-field);
  --ds-bad: var(--state-bad);
  --ds-bad-field: var(--state-bad-field);
  /* The chart ramp, brand first and lightening: a donut's four slices, a bar's four series. One ramp
     on the canvas, the same in both themes. */
  --ds-chart-1: var(--chart-1);
  --ds-chart-2: var(--chart-2);
  --ds-chart-3: var(--chart-3);
  --ds-chart-4: var(--chart-4);
  /* The planned interface's own surfaces, for the UI kit that draws it: the card, the sunken well
     inside it, the hover tint and the hairline. The kit is the app, not the page, so these stay
     white on every band in light mode and take the app's own dark theme in the dark. */
  --ds-cutout-surface: var(--cutout-card);
  --ds-cutout-sunken: var(--cutout-sunken);
  --ds-cutout-hover: var(--cutout-hover);
  --ds-cutout-rule: var(--cutout-border);
  /* The app kit: the planned client interface, as a walkthrough cutout draws it. A cutout is footage
     of the product, not website chrome, so these are the product UI's own values — the card, the
     sunken row, the hairline, the status hues and a two-layer shadow — and they follow the page
     theme, never the band the cutout sits on. Every colour is one of the canvas roles above
     (cutout/* and state/*, Light and Dark, with their Storefront twins), so the kit and the status
     chip can never disagree; the radius, shadow, faces and clock are stated here. */
  --ds-app-surface: var(--ds-cutout-surface);
  --ds-app-sunken: var(--ds-cutout-sunken);
  --ds-app-border: var(--ds-cutout-rule);
  --ds-app-hover: var(--ds-cutout-hover);
  /* The kit's own ink ladder. Every UI kit and cutout master on page 15 binds neutral/ink-body and
     neutral/ink-muted, whose Dark mode is white at 0.76 and 0.62; a product SECTION draws its dark ink
     unbound at 0.8 and 0.6, so the two ladders differ in the dark and only there. The kit's roots
     (components.css) re-point the page's body and muted ink at these. */
  --ds-app-ink-body: var(--neutral-ink-body);
  --ds-app-ink-muted: var(--neutral-ink-muted);
  /* What a picture slot paints while it has no picture. Every `Image placeholder` the canvas nests
     inside a cutout — the avatar on a staff row, the thumbnail on a booking, the shot above a
     catalogue strip — is the file's own placeholder ink, black at 6% on light and white at 8% on
     dark, which is `--ds-placeholder-alpha` of the page's ink. The sunken well is a surface and not a
     placeholder: at #f9fafb on the kit's white card it reads as nothing, and five staff rows came out
     as five invisible circles. The mix happens HERE, where the property is declared, and what reaches
     a slot is the finished colour — so a block that paints a field the page's ink cannot be laid on
     states its own (the promo band, components.css) rather than expecting this to follow it. */
  --ds-app-placeholder: color-mix(in srgb, var(--ds-ink) var(--ds-placeholder-alpha), transparent);
  --ds-app-ok: var(--ds-ok);
  --ds-app-ok-field: var(--ds-ok-field);
  --ds-app-warn: var(--ds-warn);
  --ds-app-warn-field: var(--ds-warn-field);
  --ds-app-bad: var(--ds-bad);
  --ds-app-bad-field: var(--ds-bad-field);
  /* The brand-toned pill: accent ink on the hover tint in the light, where it measures 10:1. */
  --ds-app-brand-ink: var(--ds-accent-ink);
  --ds-app-brand-field: var(--ds-app-hover);
  /* The cutout depth, not a second recipe: the kit's card IS a cutout of the interface. */
  --ds-app-shadow: var(--ds-shadow-card);
  --ds-app-radius: var(--radius-product-md);
  --ds-app-radius-thumb: var(--radius-product-thumb);
  /* The product's back-to-top disc is 44, so the dock lane the footer reserves is measured from it. */
  --ds-dock-control: 44px;
  /* The kit's two faces, by reference to the generated face table (type.css, from the canvas's own
     text styles) rather than a second copy of the same stacks: the interface is set in Inter and its
     figures in JetBrains Mono, and a capture that moves either face moves this with it. */
  --ds-app-face: var(--ds-face-inter);
  --ds-app-figure: var(--ds-face-jetbrains-mono);
  /* And the third, spent once: `product/screen/21` sets a screen's own title in Fustat, the only
     display face anywhere in the kit. */
  --ds-app-display: var(--ds-face-fustat);
  /* The kit's clock: one loop of a timed card, and the beat between things arriving in it. A card
     sets its own period; the stagger is the interface's, measured from the live scene. */
  --ds-app-period: 15000ms;
  --ds-app-stagger: 550ms;
}

@media (min-width: 768px) {
  /* Rhythm. A product band holds its content 56 under its heading, not the corporate 72: every
     page-15 band master (Section / product, the five card grids, Cutout pair, Site showcase, Tour,
     Coming soon) lays its heading out at gap 56, and the canvas names the value
     space/heading-gap-product. Declared here rather than per component, so a corporate block used
     inside a product page takes the product rhythm too. Mobile is 48 in both sets, which is what
     the root already hands down, so only the wide end moves. */
  :where(.ds-product) { --ds-heading-gap: var(--space-heading-gap-product); }
}

:where(.ds-product[data-brand='storefront']) {
  --ds-surface-sand: var(--storefront-sand);
  --ds-surface-paper: var(--storefront-mist);
  --ds-surface-invert: var(--storefront-deep);
  --ds-surface-invert-panel: var(--storefront-deep);
  --ds-surface-invert-panel-on-paper: var(--storefront-deep);
  --ds-surface-brand: var(--brand-blue);
  --ds-surface-accent: var(--storefront-mist);
  --ds-accent: var(--brand-blue);
  --ds-ink-on-accent-control: var(--brand-white);
  --ds-focus: var(--brand-blue);
  --ds-focus-base: var(--ds-focus);
  --ds-lift: var(--storefront-lift);
  /* Storefront's interface surfaces: the hover tint is blue-cast; the well and the card stay white. */
  --ds-cutout-surface: var(--cutout-card-storefront);
  --ds-cutout-sunken: var(--cutout-sunken-storefront);
  --ds-cutout-hover: var(--cutout-hover-storefront);
}

/*
 * The product set in the dark.
 *
 * A product SITE field takes the corporate ladder here — dark/hero, dark/base, dark/raised,
 * dark/panel and nothing else — because that is what every page-15 Theme=Dark master draws
 * (`scripts/dark-rungs.test.mjs` holds all of them to it) and what rule 21 says: no coloured fields
 * in the dark. The brand survives in the dark as the accent, the lift and the ink, never as a field.
 *
 * Two things are NOT site fields and keep the brand's own deep tones, because their masters do:
 *   - the app kit and the cutouts below (--ds-cutout-*, --ds-app-*). A cutout is footage of the
 *     planned product interface, in the app's own dark theme; the page it sits on is not its ground;
 *   - chrome that floats OVER the page — the consent bar, the back-to-top disc — which the Consent
 *     banner / product and Back to top / product masters fill with cueprise/raised and
 *     storefront/raised in the dark. That is --ds-surface-chrome.
 *
 * Before this, the product theme kept #1a1026, #241634, #120b1c and #0a1220 in the dark and the
 * product hero drew the brand purple itself, so a Cueprise page in dark mode was a purple page.
 */
:where(.dark .ds-product, [data-theme='dark'] .ds-product) {
  color-scheme: dark;
  --ds-color-scheme-base: dark;
  --ds-surface: var(--dark-base);
  /* A control is one rung up from the page, as the corporate dark block also has it. COR-11 wrote
     this role against the brand's own dark; a full-width choice card is wide enough for the dark
     field probe, so the rung is what it takes here. */
  --ds-surface-control: var(--dark-raised);
  --ds-selected-rule: rgb(255 255 255 / 0.15);
  /* The tinted alternate band, stated ONCE for both brands: every page-15 tinted master paints
     dark/raised in the dark, Cueprise's lilac twins as Storefront's mist ones do, so the band sits
     one rung up from the page beside it (rule 31). `dark-fields.test.mjs` reads that class
     off the scenes and holds this line to it. */
  --ds-surface-paper: var(--dark-raised);
  --ds-surface-raised: var(--dark-panel);
  /* The 404 blueprint follows its corporate twin rather than the product page: dark/raised, which is
     what `Section field="paper"` resolves to in the dark and what 2861:345042 paints. */
  --ds-surface-blueprint: var(--dark-raised);
  /* An inverted BAND — the hero, Section / product Field=Deep — is the floor. */
  --ds-surface-invert: var(--dark-hero);
  /* The inverted PANEL — the footer — is the page rung, as Footer / product Theme=Dark draws it. */
  --ds-surface-invert-panel: var(--dark-base);
  --ds-surface-invert-panel-on-paper: var(--dark-base);
  /* A brand field becomes the panel and the hero the floor, exactly as the corporate blue bands do.
     Both roles have to be stated: the light block above points the hero at the brand field, and an
     unstated role here would leave a product hero painting #5300B8 on a dark page. */
  --ds-surface-brand: var(--dark-panel);
  --ds-surface-hero: var(--dark-hero);
  /* Chrome that floats keeps the brand: the masters draw it that way. */
  --ds-surface-chrome: var(--cueprise-raised);
  --ds-ink: var(--brand-white);
  --ds-ink-inverse: var(--brand-black);
  /* The page ladder, as every non-kit page-15 Theme=Dark master draws it: unbound white at 0.8 and
     0.6. The kit reads its own, --ds-app-ink-body and --ds-app-ink-muted below. */
  --ds-ink-body: rgb(255 255 255 / 0.8);
  --ds-ink-muted: rgb(255 255 255 / 0.6);
  --ds-ink-placeholder: var(--ds-ink-muted);
  --ds-ink-on-invert: var(--brand-white);
  --ds-rule: rgb(255 255 255 / 0.14);
  --ds-rule-strong: rgb(255 255 255 / 0.4);
  --ds-rule-field: var(--cutout-border-dark);
  --ds-surface-accent: var(--dark-raised);
  --ds-surface-sand: var(--dark-raised);
  /* In the dark the well steps UP rather than down: every Form / product Theme=Dark master — Input,
     Textarea, the Select trigger and the highlighted option — fills the field dark/panel over a
     dark/base page, and both example forms put that field inside a dark/raised card. dark/panel is
     the raised rung, `--ds-surface-raised`; this read `--ds-surface-paper` and so painted the whole
     product form a rung low in the dark, fields and highlighted row alike. */
  --ds-surface-sunken: var(--ds-surface-raised);
  --ds-ink-on-accent: var(--brand-white);
  /* The brand purple measures 2.7:1 on the dark ground. In the dark the accent is its lift, which
     is what the ladder has for exactly this. */
  --ds-accent-ink: var(--ds-lift);
  /* The same two depths, cast in black: a grey shadow on a deep field reads as a smear. */
  --ds-shadow-card: 0 16px 40px -8px rgb(0 0 0 / 0.5), 0 4px 12px -2px rgb(0 0 0 / 0.4);
  --ds-shadow-raised: 0 8px 24px -8px rgb(0 0 0 / 0.55);
  /* The live product sites ring in the lift on a dark ground (`--ring: var(--brand-lift)`), for the
     same reason the accent ink moves there: the brand itself measures 2.7:1 against it. The canvas
     names it `state/focus-product-on-dark` = cueprise/lift; read through --ds-lift so Storefront's
     block above gives its own lift without a second rule here. */
  --ds-focus: var(--ds-lift);
  --ds-focus-base: var(--ds-focus);

  --ds-ink-base: var(--ds-ink);
  --ds-ink-body-base: var(--ds-ink-body);
  --ds-ink-muted-base: var(--ds-ink-muted);
  --ds-ink-placeholder-base: var(--ds-ink-placeholder);
  --ds-rule-base: var(--ds-rule);
  --ds-rule-strong-base: var(--ds-rule-strong);
  --ds-accent-ink-base: var(--ds-accent-ink);

  /* The state colours lift in the dark and their fields become a 12% wash of the same ink, as the
     canvas's Dark mode has them; the chart ramp does not change. */
  --ds-ok: var(--state-ok-dark);
  --ds-ok-field: var(--state-ok-field-dark);
  --ds-warn: var(--state-warn-dark);
  --ds-warn-field: var(--state-warn-field-dark);
  --ds-bad: var(--state-bad-dark);
  --ds-bad-field: var(--state-bad-field-dark);
  --ds-cutout-surface: var(--cutout-card-dark);
  --ds-cutout-sunken: var(--cutout-sunken-dark);
  --ds-cutout-hover: var(--cutout-hover-dark);
  --ds-cutout-rule: var(--cutout-border-dark);
  --ds-app-ink-body: var(--neutral-ink-body-dark);
  --ds-app-ink-muted: var(--neutral-ink-muted-dark);
  /* The app in dark mode, because the page is: the cutout roles above carry the canvas's Dark values.
     In the dark the lift on the hover tint measures 4.28:1 at the pill's 11px, under AA. White ink
     on a field of the brand itself reads, and still says brand. */
  --ds-app-brand-ink: var(--brand-white);
  --ds-app-brand-field: color-mix(in srgb, var(--ds-accent) 45%, transparent);
  --ds-app-shadow: var(--ds-shadow-card);
  /* The hover field itself needs no restating: it is `var(--ds-cutout-hover)`, and the line above
     re-points that at the canvas's dark hex on this same element. Only the pressed step and the ink
     it mixes change — white, not the brand purple, which disappears into a deep field. */
  --ds-surface-tint: var(--ds-ink);
  --ds-surface-mix: 6%;
  --ds-surface-mix-strong: 10%;
  --ds-surface-active: color-mix(in srgb, var(--ds-surface-tint) var(--ds-surface-mix), var(--ds-surface-hover));
  --ds-surface-hover-base: var(--ds-surface-hover);
  --ds-surface-active-base: var(--ds-surface-active);
}

:where(.dark .ds-product[data-brand='storefront'], [data-theme='dark'] .ds-product[data-brand='storefront']) {
  --ds-surface-chrome: var(--storefront-raised);
  --ds-ink-on-invert: var(--brand-white);
  --ds-accent-ink: var(--ds-lift);
  --ds-cutout-surface: var(--cutout-card-storefront-dark);
  --ds-cutout-sunken: var(--cutout-sunken-storefront-dark);
  --ds-cutout-hover: var(--cutout-hover-storefront-dark);
}

/* The product faces live in type.css, generated from the canvas: `:where(.ds-product) .ds-t-*`
   re-points every shared role at its product counterpart. The block that used to sit here named
   `.ds-body`, `.ds-lede`, `.ds-cta` and `.ds-eyebrow` — class names this library has never emitted,
   since a type role is `.ds-t-body` — so it matched nothing and the shared primitives were set in
   Fustat inside a product page. */

/* GENERATED by scripts/build-library-patterns.mjs from the Figma pattern tiles — do not edit. */

/*
 * A ground: the decorative field behind a corporate hero, and behind every band of a product page.
 * It masks to the ink of whatever it sits on, so the same tile works on the blue field, on paper and
 * in the dark without a second copy of it existing anywhere.
 *
 * The tile is a MASK over currentColor, not a picture. As a background-image it could never take
 * the field's ink whatever the tile said: an SVG loaded as an image is its own document, and
 * currentColor there resolves against that document's initial colour — black — never against the
 * element the image is painted on. So the ground drew whatever hue Figma happened to export. On the
 * corporate hero that split the mudcloth in two, the diamonds and zigzags white (Figma exported them
 * as stroke="white") and the bars, plus signs and dots black; the canvas draws every mark in one
 * ink. On the Storefront deep field the lattice was black on #101b2e and could not be seen at all.
 * Masking is the mechanism the comment above always described: every drawn shape becomes ink, the
 * ink is the field's, and the opacity is set where the ground is used.
 */
.ds-pattern {
  position: absolute;
  inset: 0;
  pointer-events: none;
  color: inherit;
  opacity: var(--ds-pattern-opacity, 0.08);
  background-color: currentColor;
  -webkit-mask-image: var(--ds-pattern-image);
  mask-image: var(--ds-pattern-image);
  -webkit-mask-repeat: repeat;
  mask-repeat: repeat;
  -webkit-mask-size: var(--ds-pattern-size);
  mask-size: var(--ds-pattern-size);
}

/*
 * A ground reads one rung stronger in the dark: every product master's Theme=Dark twin lifts the
 * 4.5% field ground to 7%, and both live product sites say the same thing in one class,
 * opacity 0.045 with a dark variant of 0.07. The lift is read from a second custom property rather
 * than from a dark copy of the map, because the opacity arrives inline from the component and an
 * inline value cannot be overridden by a rule. A ground with no lift of its own keeps its own.
 */
:where(.dark, [data-theme='dark']) .ds-pattern {
  opacity: var(--ds-pattern-opacity-dark, var(--ds-pattern-opacity, 0.08));
}

/*
 * The fade both product sites draw under a light field's ground: the tile is full strength through
 * the middle of the band and gone by its edges, which is why the kente over the sand never reads as
 * a hard stripe beside the add-on cards. The gradient is the live one, read off cueprise.cuesoft.io
 * and storefront.cuesoft.io.
 *
 * Two masks, one box each, rather than one box carrying both: mask-composite is the only way to
 * intersect two masks on one element and it is dropped by the Safaris the estate still serves, and a
 * dropped composite UNIONS the layers — the fade would then paint a solid block of ink over the
 * band. A child that carries the tile cannot fail that way.
 */
.ds-pattern--fade {
  --ds-pattern-fade: radial-gradient(120% 90% at 50% 50%, #000 35%, transparent 78%);
  background-color: transparent;
  -webkit-mask-image: var(--ds-pattern-fade);
  mask-image: var(--ds-pattern-fade);
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-size: 100% 100%;
  mask-size: 100% 100%;
}

.ds-pattern--fade::before {
  content: '';
  position: absolute;
  inset: 0;
  background-color: currentColor;
  -webkit-mask-image: var(--ds-pattern-image);
  mask-image: var(--ds-pattern-image);
  -webkit-mask-repeat: repeat;
  mask-repeat: repeat;
  -webkit-mask-size: var(--ds-pattern-size);
  mask-size: var(--ds-pattern-size);
}

.ds-pattern--mudcloth {
  --ds-pattern-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22112%22%20height%3D%22112%22%20viewBox%3D%220%200%20112%20112%22%3E%3Csvg%20x%3D%2213%22%20y%3D%2211%22%20width%3D%2230%22%20height%3D%2234%22%20viewBox%3D%220%200%2030%2034%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%0A%3Cpath%20d%3D%22M29%2017L15%201L1%2017L15%2033L29%2017Z%22%20stroke%3D%22white%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%2F%3E%0A%3C%2Fsvg%3E%0A%3Csvg%20x%3D%2226%22%20y%3D%2226%22%20width%3D%225%22%20height%3D%225%22%20viewBox%3D%220%200%205%205%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%0A%3Ccircle%20cx%3D%222.5%22%20cy%3D%222.5%22%20r%3D%222.5%22%20fill%3D%22white%22%2F%3E%0A%3C%2Fsvg%3E%0A%3Crect%20x%3D%2267%22%20y%3D%2216%22%20width%3D%223%22%20height%3D%2220%22%20rx%3D%221.25%22%20fill%3D%22currentColor%22%2F%3E%3Crect%20x%3D%2279%22%20y%3D%2216%22%20width%3D%223%22%20height%3D%2220%22%20rx%3D%221.25%22%20fill%3D%22currentColor%22%2F%3E%3Crect%20x%3D%2291%22%20y%3D%2216%22%20width%3D%223%22%20height%3D%2220%22%20rx%3D%221.25%22%20fill%3D%22currentColor%22%2F%3E%3Csvg%20x%3D%229%22%20y%3D%2263%22%20width%3D%2238%22%20height%3D%2214%22%20viewBox%3D%220%200%2038%2014%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%0A%3Cpath%20d%3D%22M1%2013L13%201L25%2013L37%201%22%20stroke%3D%22white%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%2F%3E%0A%3C%2Fsvg%3E%0A%3Crect%20x%3D%2279%22%20y%3D%2268%22%20width%3D%223%22%20height%3D%2216%22%20rx%3D%221.25%22%20fill%3D%22currentColor%22%2F%3E%3Crect%20x%3D%2272%22%20y%3D%2275%22%20width%3D%2216%22%20height%3D%223%22%20rx%3D%221.25%22%20fill%3D%22currentColor%22%2F%3E%3Csvg%20x%3D%2212%22%20y%3D%2298%22%20width%3D%224%22%20height%3D%224%22%20viewBox%3D%220%200%204%204%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%0A%3Ccircle%20cx%3D%222%22%20cy%3D%222%22%20r%3D%222%22%20fill%3D%22white%22%2F%3E%0A%3C%2Fsvg%3E%0A%3Csvg%20x%3D%2226%22%20y%3D%2298%22%20width%3D%224%22%20height%3D%224%22%20viewBox%3D%220%200%204%204%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%0A%3Ccircle%20cx%3D%222%22%20cy%3D%222%22%20r%3D%222%22%20fill%3D%22white%22%2F%3E%0A%3C%2Fsvg%3E%0A%3Csvg%20x%3D%2240%22%20y%3D%2298%22%20width%3D%224%22%20height%3D%224%22%20viewBox%3D%220%200%204%204%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%0A%3Ccircle%20cx%3D%222%22%20cy%3D%222%22%20r%3D%222%22%20fill%3D%22white%22%2F%3E%0A%3C%2Fsvg%3E%0A%3Csvg%20x%3D%22102%22%20y%3D%2246%22%20width%3D%224%22%20height%3D%224%22%20viewBox%3D%220%200%204%204%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%0A%3Ccircle%20cx%3D%222%22%20cy%3D%222%22%20r%3D%222%22%20fill%3D%22white%22%2F%3E%0A%3C%2Fsvg%3E%0A%3C%2Fsvg%3E");
  --ds-pattern-size: 112px 112px;
}

.ds-pattern--kente {
  --ds-pattern-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2264%22%20height%3D%2264%22%20viewBox%3D%220%200%2064%2064%22%3E%3Crect%20x%3D%220%22%20y%3D%220%22%20width%3D%226%22%20height%3D%2264%22%20rx%3D%220%22%20fill%3D%22currentColor%22%2F%3E%3Crect%20x%3D%2210%22%20y%3D%220%22%20width%3D%222%22%20height%3D%2264%22%20rx%3D%220%22%20fill%3D%22currentColor%22%2F%3E%3Crect%20x%3D%2216%22%20y%3D%220%22%20width%3D%226%22%20height%3D%2264%22%20rx%3D%220%22%20fill%3D%22currentColor%22%2F%3E%3Crect%20x%3D%2238%22%20y%3D%220%22%20width%3D%226%22%20height%3D%2264%22%20rx%3D%220%22%20fill%3D%22currentColor%22%2F%3E%3Crect%20x%3D%2248%22%20y%3D%220%22%20width%3D%222%22%20height%3D%2264%22%20rx%3D%220%22%20fill%3D%22currentColor%22%2F%3E%3Crect%20x%3D%2254%22%20y%3D%220%22%20width%3D%226%22%20height%3D%2264%22%20rx%3D%220%22%20fill%3D%22currentColor%22%2F%3E%3Crect%20x%3D%2224%22%20y%3D%2214%22%20width%3D%2212%22%20height%3D%224%22%20rx%3D%220%22%20fill%3D%22currentColor%22%2F%3E%3Crect%20x%3D%2224%22%20y%3D%2250%22%20width%3D%2212%22%20height%3D%224%22%20rx%3D%220%22%20fill%3D%22currentColor%22%2F%3E%3C%2Fsvg%3E");
  --ds-pattern-size: 64px 64px;
}

.ds-pattern--lattice {
  --ds-pattern-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2248%22%20height%3D%2248%22%20viewBox%3D%220%200%2048%2048%22%3E%3Csvg%20x%3D%220%22%20y%3D%220%22%20width%3D%2248%22%20height%3D%2248%22%20viewBox%3D%220%200%2048%2048%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M0%200L48%2048%22%20stroke%3D%22black%22%2F%3E%3C%2Fsvg%3E%3Csvg%20x%3D%220%22%20y%3D%220%22%20width%3D%2248%22%20height%3D%2248%22%20viewBox%3D%220%200%2048%2048%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M48%200L0%2048%22%20stroke%3D%22black%22%2F%3E%3C%2Fsvg%3E%3C%2Fsvg%3E");
  --ds-pattern-size: 48px 48px;
}

/*
 * One curve, one duration scale, and nothing that moves without being asked.
 *
 * The system allows two kinds of motion: reveal-on-scroll, and the state changes a control makes
 * when you touch it. Everything here is off under `prefers-reduced-motion`, and every hover rule is
 * behind `hover: hover` so a touch device never gets a state it cannot leave.
 */

:where(:root, .ds-root) {
  --ds-ease: cubic-bezier(0.2, 0, 0, 1);
  --ds-fast: 140ms;
  /* A change the page makes as you scroll, not one your hand made: slower than a press, quicker
     than a reveal. */
  --ds-medium: 300ms;
  --ds-slow: 520ms;
  /* Reveal on scroll: how far a band rises, how long it takes, how far apart siblings start, and the
     curve it settles on. The product set re-points all four. */
  --ds-reveal-rise: 12px;
  --ds-reveal-duration: 600ms;
  --ds-reveal-stagger: 70ms;
  --ds-reveal-ease: var(--ds-ease);
}

/* The corporate reveal settles on the spring the four division sites ship — heavily damped, ~3%
   overshoot, encoded as linear(). A browser without linear() keeps the bezier declared above; a
   custom property holding a value the browser cannot parse would fall to `ease`, not to it. */
@supports (transition-timing-function: linear(0, 1)) {
  :where(:root, .ds-root) {
    --ds-reveal-ease: linear(0, 0.402 7.4%, 0.711 14.9%, 0.929 23.4%, 1.008 31.2%, 1.031 38.3%, 1.024 48%, 1.004 68.5%, 1);
  }
}

/* ── Controls ──────────────────────────────────────────────────────────────────────────────── */

.ds-button { transition: background-color var(--ds-fast) var(--ds-ease), color var(--ds-fast) var(--ds-ease), box-shadow var(--ds-fast) var(--ds-ease), transform var(--ds-fast) var(--ds-ease); }
.ds-button:active { transform: translateY(1px); }

/* The arrow moves, not the words: a label that shifts under the pointer is harder to click. */
.ds-link--arrow::after { display: inline-block; transition: transform var(--ds-fast) var(--ds-ease); }
@media (hover: hover) {
  .ds-link--arrow:hover::after { transform: translateX(3px); }
}

.ds-input, .ds-check__box, .ds-switch__track { transition: border-color var(--ds-fast) var(--ds-ease), background-color var(--ds-fast) var(--ds-ease); }

/* The product bar's pin: field, hairline and ink cross-fade together as the band passes under it. */
.ds-nav--product, .ds-nav--over { transition: background-color var(--ds-medium) var(--ds-ease), border-color var(--ds-medium) var(--ds-ease), color var(--ds-medium) var(--ds-ease), backdrop-filter var(--ds-medium) var(--ds-ease); }
.ds-nav--product::after { transition: opacity var(--ds-medium) var(--ds-ease), background-color var(--ds-medium) var(--ds-ease); }
.ds-nav--product .ds-nav__link, .ds-nav--product .ds-nav__label, .ds-nav--product .ds-nav__secondary { transition: color var(--ds-medium) var(--ds-ease); }
/* A change of theme is not a band passing under the bar: for the frames it takes to paint, the bar and
   everything in it change at once (useThemeSwitch). WebKit leaves a cross-fade on a backdrop-filter
   bar half painted until the next scroll. After the rules above so it wins at equal weight. */
.ds-nav[data-theme-switch],
.ds-nav[data-theme-switch]::before,
.ds-nav[data-theme-switch]::after,
.ds-nav[data-theme-switch] *,
.ds-nav[data-theme-switch] *::before,
.ds-nav[data-theme-switch] *::after { transition: none; }
.ds-switch__thumb { transition: inset-inline-start var(--ds-fast) var(--ds-ease), background-color var(--ds-fast) var(--ds-ease); }

/* A footer glyph lifts 2px and brightens to the full ink, the way every live footer's does. */
.ds-footer__social-link { transition: color var(--ds-fast) var(--ds-ease), transform var(--ds-fast) var(--ds-ease); }
@media (hover: hover) {
  .ds-footer__social-link:hover { color: var(--ds-ink); transform: translateY(-2px); }
}

/* A trade chip warms under the pointer — the accent hairline, the accent icon, and behind it the
   state pair's hover field, which on a product page is the canvas's own `cutout/hover` (#F9F5FF for
   Cueprise, #EFF4FD for Storefront, and the two deep tones in the dark) — exactly the `--card-hover`
   live cueprise paints in `.chip:hover`. It reads as "and yours" rather than being skimmed. The filled
   chip is already the answer and holds. The accountability chips on the partnership card do the same
   on the deep field they sit on, where the tint is 7% of their own ink (the live band's card-hover),
   not the light lilac, which would put white ink on a pale field. Neither is a control, so neither
   moves. */
.ds-trade-chip, .ds-trade-chip .ds-chip__icon, .ds-partnership__chips .ds-chip { transition: box-shadow var(--ds-fast) var(--ds-ease), color var(--ds-fast) var(--ds-ease), background-color var(--ds-fast) var(--ds-ease); }
@media (hover: hover) {
  :where(.ds-product) .ds-trade-chip:not(.ds-chip--accent):hover { box-shadow: inset 0 0 0 1px var(--ds-accent-ink, var(--ds-accent)); color: var(--ds-ink); background-color: var(--ds-surface-hover); }
  :where(.ds-product) .ds-trade-chip:not(.ds-chip--accent):hover .ds-chip__icon { color: var(--ds-accent-ink, var(--ds-accent)); }
  :where(.ds-product) .ds-partnership__chips .ds-chip:hover { box-shadow: inset 0 0 0 1px var(--ds-accent-ink, var(--ds-accent)); color: var(--ds-ink); background-color: color-mix(in srgb, currentColor 7%, transparent); }
}

/* ── Cards ─────────────────────────────────────────────────────────────────────────────────── */

/* The corporate set casts no shadow, so a card that responds to the pointer does it with its own
   rule and a pixel of lift. A product site card does the same rise and, inside a product scope,
   casts `--ds-shadow-card` while it is up (components.css); the token draws nothing anywhere else.
   The showcase card is not in this list: PRO-15 gives it a rule of its own, because only a card with
   a way in lifts. */
.ds-card, .ds-products__item, .ds-case, .ds-media-card, .ds-contributors__item { transition: background-color var(--ds-fast) var(--ds-ease), box-shadow var(--ds-fast) var(--ds-ease), transform var(--ds-fast) var(--ds-ease); }
@media (hover: hover) {
  .ds-card--link:hover, .ds-products__item:hover { transform: translateY(-2px); }
  .ds-case:hover .ds-case__media img, .ds-media-card:hover .ds-media-card__cover img { transform: scale(1.03); }
  /* A framed screen that is a way into the product lifts its picture, not its frame; the frame clips
     the growth, so the hairline stays exactly where it is. */
  a.ds-media--screen:hover img { transform: scale(1.02); }
}
.ds-media img { transition: transform var(--ds-slow) var(--ds-ease); }

/* ── Reveal on scroll ──────────────────────────────────────────────────────────────────────── */

/*
 * Hidden only behind the pre-paint mark. `html.js` is set by the script `revealPrepaintScript()`
 * emits before the first paint and removed by its own four-second fallback unless a `Reveal` cancels
 * it, so with no JavaScript — or with a chunk that never loads — nothing stays hidden. One shot:
 * opacity and a small rise; the delay is the sibling's step times the set's stagger plus any offset
 * it was handed, nothing else moves. The transition belongs to the reveal only while it is hidden or
 * moving: `Reveal` marks the element `done` when the opacity transition ends, and a done element
 * carries no reveal transition, so a component that renders a painted, hoverable element as the
 * `Reveal` itself keeps its own hover and focus transitions. Under a rule that matched `.ds-reveal`
 * for good, a product card's 140ms lift ran on this 600ms spring and overshot.
 */
@media (prefers-reduced-motion: no-preference) {
  .js .ds-reveal[data-state='hidden'] { opacity: 0; transform: translate3d(0, var(--ds-reveal-rise), 0); }
  .ds-reveal[data-state='hidden'], .ds-reveal[data-state='shown'] {
    transition-property: opacity, transform;
    transition-duration: var(--ds-reveal-duration);
    transition-timing-function: var(--ds-reveal-ease);
    transition-delay: calc(var(--ds-reveal-delay, 0ms) + var(--ds-reveal-step, 0) * var(--ds-reveal-stagger));
  }
}

/* ── Hero, ambient ─────────────────────────────────────────────────────────────────────────── */

/*
 * The one thing in the system that moves without being touched, and it still has to be asked for.
 * Three grounds a hero can opt into through `motion`, all of them off by default, all of them inside
 * `.ds-hero` and inside a motion preference. They are the live corporate hero's own grammar —
 * cuesoft-website `globals.css` `grid-breath`, `packet-run`, `hero-sheet-drift` — read back onto the
 * system's tile. Nothing here asks the main thread for a frame: opacity, an offset along a path, and
 * a transform the scroller scrubs.
 */
@media (prefers-reduced-motion: no-preference) {
  /* Breath. The tile is drawn twice, in the layer's own pseudo-elements, each masked to one diagonal
     half of the band and each carrying the opposite half of the pulse — so the ground never blinks
     as a whole, the brightness travels across it. The whole mechanism lives in this query: with a
     reduced-motion preference the layer keeps its single static background and there are no halves.
     The pulse rides inside the layer's own 8% opacity, so the ground moves between about 4% and 8%
     of the field's ink and no further. */
  @keyframes ds-hero-breath { 0%, 100% { opacity: 0.55; } 50% { opacity: 1; } }
  .ds-hero .ds-hero__ground--breath .ds-pattern { background-image: none; }
  .ds-hero .ds-hero__ground--breath .ds-pattern::before,
  .ds-hero .ds-hero__ground--breath .ds-pattern::after {
    content: '';
    position: absolute;
    inset: 0;
    background-image: var(--ds-pattern-image);
    background-repeat: repeat;
    background-size: var(--ds-pattern-size);
    animation: ds-hero-breath 8s ease-in-out infinite;
  }
  .ds-hero .ds-hero__ground--breath .ds-pattern::before { -webkit-mask-image: linear-gradient(115deg, #000, transparent); mask-image: linear-gradient(115deg, #000, transparent); }
  .ds-hero .ds-hero__ground--breath .ds-pattern::after { -webkit-mask-image: linear-gradient(115deg, transparent, #000); mask-image: linear-gradient(115deg, transparent, #000); animation-delay: -4s; }

  /* Packets. A mark travels its rail over the first half of its period and the rail is quiet for the
     second, so the band reads as occasional traffic rather than a loop running. Only where
     offset-path resolves; anywhere else the marks stay at the opacity components.css gives them,
     which is none. */
  @supports (offset-path: path('M0 0')) {
    @keyframes ds-hero-packet {
      0% { offset-distance: 0%; opacity: 0; }
      5% { opacity: 1; }
      45% { opacity: 1; }
      50%, 100% { offset-distance: 100%; opacity: 0; }
    }
    .ds-hero .ds-hero__packet { animation: ds-hero-packet var(--ds-packet-period, 13s) linear var(--ds-packet-delay, 0s) infinite backwards; }
  }

  /* Drift. Scrubbed by the document's own scroll over the first screen, so nothing moves while the
     reader reads — the sheet simply leaves a little slower than the type. The layer is oversized by
     more than the rise so the drift never exposes an edge, and the hero clips what hangs out of it.
     A browser without scroll timelines keeps the sheet exactly where it is. */
  @supports (animation-timeline: scroll()) {
    @keyframes ds-hero-drift { to { transform: translateY(-44px); } }
    .ds-hero .ds-hero__ground--drift {
      inset-block: -64px;
      animation: ds-hero-drift linear both;
      animation-timeline: scroll(root block);
      animation-range: 0 100vh;
    }
  }
}


/* ── Product ───────────────────────────────────────────────────────────────────────────────── */

/*
 * The product sites move a little more than the corporate ones, and only where the movement means
 * something: the brand rule under an eyebrow draws itself in as its section arrives, a card that
 * leads somewhere rises under the pointer, a chip warms up, a button acknowledges the hand over it.
 * Everything here is scoped to the product set, so the corporate pages stay as still as their canvas.
 */

/* The rule is drawn from its left end, the way a hand draws one, and only once the section it marks
   has been revealed — the same Reveal a site already wraps the section in, so there is nothing new
   to add. A page that wraps no Reveal round it gets the rule simply there, at rest. It waits out the
   reveal's own delay plus a beat, so it arrives as the copy settles rather than with it. */
.ds-peyebrow--ruled::after, .ds-industry__eyebrow::after { transform-origin: left center; }
@media (prefers-reduced-motion: no-preference) {
  .ds-reveal[data-state='hidden'] .ds-peyebrow--ruled::after,
  .ds-reveal[data-state='hidden'] .ds-industry__eyebrow::after { transform: scaleX(0); }
  .ds-reveal[data-state='shown'] .ds-peyebrow--ruled::after,
  .ds-reveal[data-state='shown'] .ds-industry__eyebrow::after { transition: transform var(--ds-slow) var(--ds-ease) calc(var(--ds-reveal-delay, 0ms) + 120ms); }
}

/* A showcase card rises two pixels and takes the set's shadow, the way the cutouts wear it at rest.
   Only a card with a way in does: a lift on a card that leads nowhere promises a click that does
   nothing. */
.ds-showcase__item { transition: box-shadow var(--ds-fast) var(--ds-ease), transform var(--ds-fast) var(--ds-ease); }
@media (hover: hover) {
  .ds-showcase__item:has(.ds-showcase__link):hover { transform: translateY(-2px); box-shadow: inset 0 0 0 1px var(--ds-rule), var(--ds-shadow-card); }
}

/* A product button rises a pixel under the pointer and settles back on the press, so the press is
   felt as the return rather than as a second move down. */
@media (hover: hover) {
  .ds-product .ds-button:hover { transform: translateY(-1px); }
}
.ds-product .ds-button:active { transform: none; }

@media (prefers-reduced-motion: reduce) {
  .ds-button:active { transform: none; }
  /* The bar still changes look when the band passes under it; it changes without a fade. */
  .ds-nav--product, .ds-nav--over, .ds-nav--product::after, .ds-nav--product .ds-nav__link, .ds-nav--product .ds-nav__label, .ds-nav--product .ds-nav__secondary { transition: none; }
  .ds-card--link:hover, .ds-products__item:hover { transform: none; }
  .ds-footer__social-link:hover { transform: none; }
  .ds-media img { transition: none; }
  a.ds-media--screen:hover img { transform: none; }
  .ds-skip { transition: none; }
  .ds-switch__thumb { transition: none; }
  .ds-accordion__toggle { transition: none; }
  .ds-showcase__item:has(.ds-showcase__link):hover, .ds-product .ds-button:hover { transform: none; }
}

