/* ============================================================================
   DESIGN LANGUAGE — Wavefront
   ----------------------------------------------------------------------------
   This file is the only place the visual language is defined. Pages and the
   shared script reference generic, language-agnostic names (.button, .card,
   .field, .toast ...) so this file can be swapped wholesale for another design
   language without editing any markup.

   THE LANGUAGE IN ONE PARAGRAPH
     Flat surfaces separated by FILL, not by outlines. One accent, spent only
     on the primary action and on "this one is active" — never as decoration.
     Hover and focus are answered by a shift in background, never by a border
     appearing, a shadow growing or an element changing size. Shadow is
     reserved for things that genuinely float above the page: the drawer, a
     menu, a dialog, a toast. Hairlines exist but are rare. No gradients, no
     glow, no grow-on-hover, no ink ripples. Radii are moderate, and the type
     is small, dense and untracked.

     The practical test for any new rule: if it adds a border, a shadow or a
     transform where a change of background would do, it is wrong.

   SECTIONS
     01  Tokens — dark (the reference theme)
     02  Tokens — light
     03  Reset and base
     04  Typography
     05  Layout: container, grid, stack, utilities
     06  Surfaces: card, panel, sheet, divider, elevation
     07  Buttons and button groups
     08  Fields and inputs
     09  Selection controls
     10  Navigation
     11  Lists and tables
     12  Feedback: dialog, toast, tooltip, progress, banner, skeleton
     13  Data display: chip, badge, avatar, stat, timeline, tree, meter
     14  Media
     15  Content and long-form
     16  Overlays and pickers: popover, combobox, calendar, rating, sortable
     17  Application patterns: settings, dashboard, chat, auth, errors
     18  Marketing blocks: hero, features, pricing, testimonials, CTA
     19  Showcase chrome (the demo pages themselves)
     20  Print, reduced motion, forced colors
     21  Right-to-left
   ========================================================================= */

/* --- Self-hosted faces ---------------------------------------------------- */
/* Three files, variable weight axes, swap display. Self-hosted rather than
   fetched so the kit renders identically offline and nothing blocks on a
   third-party origin. */
@font-face {
  font-family: 'Sora';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('assets/fonts/Sora.woff2') format('woff2');
}
@font-face {
  font-family: 'Roboto';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('assets/fonts/Roboto.woff2') format('woff2');
}
@font-face {
  font-family: 'Roboto Mono';
  font-style: normal;
  font-weight: 400 500;
  font-display: swap;
  src: url('assets/fonts/RobotoMono.woff2') format('woff2');
}
/* Material Icons is a placeholder glyph set, self-hosted like the text faces
   so that the kit never reaches for a third-party host. See ICONS.txt. */
@font-face {
  font-family: 'Material Icons';
  font-style: normal;
  font-weight: 400;
  font-display: block;
  src: url('assets/fonts/MaterialIcons.woff2') format('woff2');
}

/* ======================================================== 01 Tokens — dark */
/* Dark is the reference theme: these are the values the language was designed
   against, and the light block below is derived from them rather than the
   other way round. It is declared on :root so dark is what a page gets before
   any preference is read; 02 overrides it for an explicit light choice or a
   light OS preference. */
:root {
  /* Accent ramp ----------------------------------------------------------- */
  /* One hue, five steps. -50 is the accent proper; -40 is the lighter step a
     filled control moves to on hover, and the step accent TEXT is set in (the
     -50 is a shade too dim against a dark ground to read comfortably); -70 is
     the pressed step, and the one fill quiet enough to sit behind a letter
     avatar; -10 is the tint that marks an item as active. */
  --accent-10: #0f2a3f;
  --accent-40: #54aef6;
  --accent-50: #2196f3;
  --accent-55: #0b76cb;
  --accent-60: #1b7ecc;
  --accent-70: #1565a5;

  /* Neutral ramp ---------------------------------------------------------- */
  /* Cool grays. These carry avatars, scrollbar thumbs and anything that has
     to be a distinct block of color without borrowing the accent. */
  --neutral-40: #737b82;
  --neutral-50: #535c65;
  --neutral-60: #454d54;
  --neutral-70: #32373d;

  /* Brand ----------------------------------------------------------------- */
  --color-primary:          var(--accent-50);
  --color-primary-hover:    var(--accent-40);
  --color-primary-strong:   var(--accent-70);
  --color-primary-soft:     var(--accent-10);
  --color-primary-text:     var(--accent-40);
  --color-on-primary:       #ffffff;

  /* The accent splits in two, and the reason is contrast.
     --color-primary is the identity: the exact blue, spent on everything that
     carries no text — a focus ring, a switch that is on, a progress fill, a
     tab indicator, a slider thumb. Non-text UI needs 3:1 and it clears that
     comfortably.
     --color-primary-ground is for the places text sits ON the accent: the
     filled button, the floating action button, a badge, the selected day in a
     calendar. White on the identity blue is 3.1:1, which fails AA for body
     text, so those use the same hue two rungs down, where white reaches
     4.7:1. The two read as the same color at a glance, which is the point:
     the brand survives and the label is legible. Set both to --accent-50 to
     get the reference exactly, and accept the failure knowingly. */
  --color-primary-ground:   var(--accent-55);

  /* "Secondary" is deliberately not a second hue. The language spends one
     accent and no more, so the secondary voice is the neutral ramp: a quiet
     gray control that reads as subordinate without introducing a color that
     would compete with the accent for meaning. */
  --color-secondary:        #b8bcc0;
  --color-secondary-strong: #dfe1e5;
  --color-secondary-hover:  #d2d5d9;
  --color-secondary-soft:   #2b2f31;
  --color-on-secondary:     #181a1b;

  /* The "neutral" filled button paints itself in the foreground color, so it
     is the one fill that cannot be derived from a hue ramp. Both of its
     states are stated outright: a dark theme's near-white fill goes to pure
     white on hover, and the press shade below darkens it from there. */
  --color-neutral-hover:    #ffffff;

  /* Status ---------------------------------------------------------------- */
  /* Four hues beyond the accent, and they appear as TINTS — a 13% wash of the
     hue, carrying the hue itself as text — not as saturated fills. A solid
     status block would outshout the one accent. */
  --color-danger:       #e57373;
  /* Near-black, not white. The dark theme states its status hues light so
     they read as TEXT on a dark ground, which is how they are spent almost
     everywhere — but that same lightness means white on top of one is 3:1.
     Where the hue becomes a fill, the label has to go the other way. */
  --color-on-danger:    #1a1011;
  --color-danger-soft:  rgba(229, 115, 115, .13);
  /* The brighter step a filled status control moves to on hover. These exist
     for the same reason --accent-40 does: a filled control answers hover by
     moving along its own ramp to a clean color, not by muddying itself with a
     wash. Without them the filled intents had no hover of their own and were
     left washing white over a saturated hue, which at 8% is not a state
     anyone can see. */
  --color-danger-hover:  #f09494;
  --color-warning:      #ffb74d;
  --color-on-warning:   #181a1b;
  --color-warning-soft: rgba(255, 183, 77, .13);
  --color-warning-hover: #ffcc80;
  /* Text on the soft fill. In dark, the hue clears 4.5:1 on its own 13% wash,
     so unlike a light theme this needs no separate darkened step — but it
     stays a token because the light block does need one. */
  --color-on-warning-soft: #ffb74d;
  --color-success:      #81c784;
  --color-on-success:   #101611;
  --color-success-soft: rgba(129, 199, 132, .13);
  --color-success-hover: #a3dba6;
  --color-info:         var(--accent-40);
  --color-on-info:      #ffffff;
  --color-info-soft:    var(--accent-10);

  /* Neutrals — the elevation ladder --------------------------------------- */
  /* Read these as a ladder of FILLS, because fill is how this language
     separates one plane from another. The ordering is deliberate and is the
     thing most likely to look like a mistake: a card is DARKER than the page
     it sits on, not lighter. Depth here is distance from the viewer rather
     than distance from a light source, and the page is the near plane.

       background      the page
       surface         a card, a panel, a list — one step back from the page
       surface-sunken  a field, a menu, anything raised back off a card
       surface-hover   the same, one step brighter: hover, focus, selection

     "Sunken" is a name inherited from the class contract; in this language it
     is the raised step. The value is what matters: it is where an input sits,
     and surface-hover is where that input goes when you focus it. */
  --color-background:       #1e2021;

  /* The four rungs, as fixed values that are never reassigned. The named
     tokens below point at them, and it is the NAMES that get re-based inside
     a container (see "the relative ladder" at the end of this block) — which
     is only possible because the rungs themselves stay put. */
  --step-1: #181a1b;
  --step-2: #232628;
  --step-3: #2b2f31;
  --step-4: #363b3e;

  --color-surface:          var(--step-1);
  --color-surface-sunken:   var(--step-2);
  --color-surface-hover:    var(--step-3);
  --color-surface-raised:   var(--step-2);

  /* Foreground ------------------------------------------------------------ */
  /* Solid values, not alphas: a translucent foreground picks up whichever of
     the four fills happens to be behind it, so one "muted" label came out as
     four different grays across a single page. */
  --color-on-background:    #dfe1e5;
  --color-on-surface:       #dfe1e5;
  --color-on-surface-muted: #b8bcc0;
  /* Lifted from the reference #93999f so that it still clears 4.5:1 on the
     TOP rung of the ladder, not just on the page. Tertiary text ends up on
     step 4 more often than it looks — any hovered row inside a card. */
  --color-on-surface-faint: #a0a6ac;

  /* Hairlines — rare: dividers, a grouping box, a focus ring --------------- */
  --color-divider:       #2a2d30;
  --color-border:        #2a2d30;
  --color-border-strong: #32373d;
  --color-border-loud:   #424a51;
  --color-scrim:         rgba(0, 0, 0, .60);

  /* Channel forms, for composing translucent state layers ----------------- */
  --rgb-on-surface: 223, 225, 229;
  --rgb-primary:    33, 150, 243;
  --rgb-surface:    24, 26, 27;

  /* Interaction state opacities ------------------------------------------- */
  /* Composed against --rgb-on-surface, so .08 / .14 here are exactly the
     white overlays the language uses for hover and press on a dark ground. */
  --state-hover:            .08;
  --state-focus:            .12;
  --state-pressed:          .14;
  /* Press is a SHADE, not a wash of the foreground, and it is the one state
     whose direction never changes with the theme: every pressed control in
     this language gets darker. See the note above .button:active::before for
     why that is a hard rule rather than a per-variant judgement. On a dark
     ground the surrounding washes are lifting the control, so the shade has
     to be strong enough to unmistakably reverse that. */
  --state-press-shade:      .26;
  --state-selected:         .14;
  --state-disabled-content: .45;
  --state-disabled-surface: .10;
  /* A disabled control fades rather than recoloring: against these fills a
     "disabled gray" is not distinguishable from an enabled one. */
  --opacity-disabled: .55;

  /* Typography ------------------------------------------------------------ */
  /* Two faces doing two jobs. Sora sets anything that NAMES a thing — page
     titles, card titles, section headings, a brand mark, the figure that is
     the point of its tile. Roboto sets everything that is read rather than
     scanned. Mixing them inside one line is the thing to avoid. */
  --font-sans:    'Roboto', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Helvetica, Arial, sans-serif;
  --font-display: 'Sora', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Helvetica, Arial, sans-serif;
  --font-mono:    'Roboto Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  --font-brand:   var(--font-display);

  --weight-light:   400;
  --weight-regular: 400;
  --weight-medium:  500;
  --weight-semi:    600;
  --weight-bold:    700;

  /* The scale is small and dense: 14px is the body, and the step from one
     size to the next is often a single pixel. Hierarchy is carried by weight,
     and by which FACE a line is set in, far more than by size — 16px Sora
     semibold over 14px Roboto is the commonest pairing in the language, and
     the size difference between them is almost nothing.

     Tracking is zero everywhere but the overline. Letter-spaced body copy is
     a Material signature, and reading it back at 13px is most of what made
     the previous language unmistakable. */
  --type-display-1-size: 4rem;      --type-display-1-line: 1;    --type-display-1-track: -.02em;  --type-display-1-weight: 700;
  --type-display-2-size: 2.75rem;   --type-display-2-line: 1.1;  --type-display-2-track: -.02em;  --type-display-2-weight: 700;
  --type-display-3-size: 2rem;      --type-display-3-line: 1.18; --type-display-3-track: -.015em; --type-display-3-weight: 600;
  --type-heading-1-size: 1.5rem;    --type-heading-1-line: 1.2;  --type-heading-1-track: -.01em;  --type-heading-1-weight: 600;
  --type-heading-2-size: 1.25rem;   --type-heading-2-line: 1.3;  --type-heading-2-track: -.005em; --type-heading-2-weight: 600;
  --type-heading-3-size: 1rem;      --type-heading-3-line: 1.35; --type-heading-3-track: 0;       --type-heading-3-weight: 600;
  --type-subtitle-size:  .9375rem;  --type-subtitle-line:  1.45; --type-subtitle-track:  0;       --type-subtitle-weight: 500;
  --type-subtitle-2-size: .875rem;  --type-subtitle-2-line: 1.4; --type-subtitle-2-track: 0;      --type-subtitle-2-weight: 500;
  --type-body-size:      .875rem;   --type-body-line:      1.5;  --type-body-track:      0;       --type-body-weight: 400;
  --type-body-2-size:    .8125rem;  --type-body-2-line:    1.5;  --type-body-2-track:    0;       --type-body-2-weight: 400;
  --type-label-size:     .8125rem;  --type-label-line:     1;    --type-label-track:     0;       --type-label-weight: 600;
  --type-caption-size:   .75rem;    --type-caption-line:   1.5;  --type-caption-track:   0;       --type-caption-weight: 400;
  --type-overline-size:  .6875rem;  --type-overline-line:  1;    --type-overline-track:  .12em;   --type-overline-weight: 600;

  /* Button labels are sentence case. Uppercasing costs the word shape a
     reader matches on, and at 13px it costs legibility too. The overline
     keeps its caps: it labels a block rather than being read as words. */
  --type-label-transform:    none;
  --type-overline-transform: uppercase;

  /* Shape ------------------------------------------------------------------ */
  /* ONE radius, and it is 6px. The scale used to climb 4 → 16, which meant the
     corner an element got depended on how big the element was: a card was
     visibly rounder than the button sitting inside it, a chip visibly sharper
     than the field beside it. Nobody asked for a size-dependent corner, and
     it is the first thing the eye catches when two of them meet. Every
     rectangle in the language now gets the same corner at every size.

     The whole xs/sm/md/lg/xl ramp is kept so the token contract still
     resolves, the same way the elevation ramp collapses onto three shadows:
     there is no second roundness for the in-between steps to describe. Two
     exceptions survive, both because the shape is the point rather than a
     finish — the capsule, for the two things that genuinely are capsules (a
     switch track and a scrollbar thumb), and the circle, for avatars and
     dots. */
  --radius-none:   0;
  --radius-xs:     6px;
  --radius-sm:     6px;
  --radius-md:     6px;
  --radius-lg:     6px;
  --radius-xl:     6px;
  --radius-pill:   999px;
  --radius-circle: 50%;

  --border-width:        1px;
  --border-width-strong: 2px;

  /* Elevation --------------------------------------------------------------- */
  /* Two real shadows and a deep one, spent only on things that float clear of
     the page: the drawer, a menu, a dialog, a toast, a sheet. A resting card
     gets none — that is what the fill ladder is for. The full
     0/1/2/3/4/6/8/12/16/24 ramp is kept so the class contract still resolves,
     but it collapses onto those three: there is nothing between "sitting on
     the page" and "floating above it" for the in-between steps to describe. */
  --shadow-0:  none;
  --shadow-1:  0 1px 2px rgba(0,0,0,.5), 0 2px 8px rgba(0,0,0,.35);
  --shadow-2:  var(--shadow-1);
  --shadow-3:  var(--shadow-1);
  --shadow-4:  0 2px 8px rgba(0,0,0,.55), 0 4px 16px rgba(0,0,0,.4);
  --shadow-6:  var(--shadow-4);
  --shadow-8:  var(--shadow-4);
  --shadow-12: 0 4px 16px rgba(0,0,0,.6), 0 12px 36px rgba(0,0,0,.5);
  --shadow-16: var(--shadow-12);
  --shadow-24: var(--shadow-12);

  /* Spacing — 8pt grid ------------------------------------------------------ */
  --space-0:  0;
  --space-1:  4px;
  --space-2:  8px;
  --space-3:  12px;
  --space-4:  16px;
  --space-5:  24px;
  --space-6:  32px;
  --space-7:  40px;
  --space-8:  48px;
  --space-9:  64px;
  --space-10: 80px;
  --space-11: 96px;

  /* Sizing ------------------------------------------------------------------ */
  /* 36px is the control height the whole language is built around: buttons,
     chips, selects and the segmented group all land on it, so a row of mixed
     controls lines up without anyone measuring. 42px is the one taller box,
     for a field that is the point of its row. */
  --size-control:          36px;
  --size-control-sm:       28px;
  --size-control-lg:       42px;
  --size-touch:            40px;
  --size-icon:             22px;
  --size-icon-sm:          18px;
  --size-icon-lg:          36px;
  --size-topbar:           56px;
  --size-topbar-compact:   48px;
  --size-drawer:           264px;
  --size-rail:             80px;
  --size-container:        1240px;
  --size-container-narrow: 720px;
  --size-container-wide:   1640px;
  --size-readable:         68ch;
  --size-gutter:           20px;

  /* Motion ------------------------------------------------------------------ */
  /* One curve does nearly all of it, and it is fast. Motion here confirms
     that something happened; it is never the thing you look at. */
  --ease-standard:   cubic-bezier(.2, .8, .2, 1);
  --ease-decelerate: cubic-bezier(0, 0, .2, 1);
  --ease-accelerate: cubic-bezier(.4, 0, 1, 1);
  --ease-sharp:      cubic-bezier(.4, 0, .6, 1);

  --duration-instant: 80ms;
  --duration-fast:    120ms;
  --duration-base:    160ms;
  --duration-slow:    240ms;
  --duration-slower:  320ms;

  /* Stacking ----------------------------------------------------------------- */
  --layer-base:    0;
  --layer-raised:  1;
  --layer-sticky:  100;
  --layer-drawer:  200;
  --layer-topbar:  300;
  --layer-overlay: 400;
  --layer-dialog:  500;
  --layer-toast:   600;
  /* A row held by the pointer clears every surface it could have been
     dragged out of, a dialog included, because it is the one thing on screen
     the pointer is literally holding. */
  --layer-drag:    650;
  --layer-tooltip: 700;
  /* The scrollbar is drawn rather than native, so it has to be placed in this
     scale by hand. --layer-scrollbar is a scroller's own bar: above a sticky
     table header, which is the one thing inside a scroller that would
     otherwise paint over it. The page's bar clears the topbar as well, but
     deliberately stays under the scrim — once a dialog has locked the
     document there is nothing left for that bar to scroll, and a bar left
     bright over a dimmed page reads as a control that still works. */
  --layer-scrollbar:      150;
  --layer-scrollbar-page: 350;

  /* Scrollbars -------------------------------------------------------------- */
  /* --scrollbar-size is the gutter reserved for the bar, and is exactly the
     width the native bar used to take, so that replacing it reflows nothing.
     The thumb is drawn narrower than that gutter on purpose: one that fills
     it edge to edge reads as a painted border down the side of the scroller
     rather than as something that moves. --scrollbar-min-thumb keeps it
     grabbable in a very long document, where its true proportional length
     would come out a few pixels tall. The behavior reads all three. */
  --scrollbar-size:        10px;
  --scrollbar-thumb-size:  6px;
  --scrollbar-min-thumb:   28px;
  /* Neutral, not accented: a scrollbar reports position, and the accent is
     reserved for things that carry state. Hover is the one value that cannot
     be shared between the themes — going lighter is what reads as "raised"
     on a dark ground and as "fading" on a light one — so the light blocks
     below take it the other way. */
  --scrollbar-thumb:       var(--neutral-50);
  --scrollbar-thumb-hover: var(--neutral-40);

  /* Focus --------------------------------------------------------------------- */
  --focus-ring-width:  2px;
  --focus-ring-offset: 2px;
  --focus-ring-color:  var(--color-primary);

  color-scheme: dark;
}

/* ======================================================= 02 Tokens — light */
/* Derived from the dark block rather than invented beside it. Every
   relationship is preserved and only the direction flips: the card is still a
   step away from the page, the field is still a step away from the card,
   focus is still a step further, hairlines are still rare.

   Two things genuinely change rather than invert. The accent drops to -60,
   because #2196F3 on white is 3.1:1 and fails as text. And the status hues
   darken, for the same reason — #E57373 and #81C784 are dark-theme tints and
   are unreadable on a pale wash of themselves. */
:root[data-theme="light"],
:root[data-theme="light"] ::backdrop {
  --color-primary:          var(--accent-60);
  --color-primary-hover:    var(--accent-70);
  --color-primary-strong:   var(--accent-70);
  --color-primary-soft:     #e3f1fc;
  --color-primary-ground:   #0f6099;
  --color-primary-text:     #0f6099;
  --color-on-primary:       #ffffff;

  --color-secondary:        #535c65;
  --color-secondary-strong: #32373d;
  /* Lighter, like every other filled hover — the -strong step goes the other
     way and is kept for text, where a light theme needs the darker value. */
  --color-secondary-hover:  #697480;
  --color-secondary-soft:   #e7eaec;
  --color-on-secondary:     #ffffff;

  /* A light theme's foreground fill is near-black, which a shade cannot
     darken any further in a way the eye picks up. Its hover therefore lifts
     and its press bottoms out at true black, so the three steps stay
     distinct in the one place the shared shade cannot do the work. */
  --color-neutral-hover:    #363c42;

  --color-danger:       #bd2424;
  --color-on-danger:    #ffffff;
  --color-danger-soft:  #fdeaea;
  --color-danger-hover: #d93434;
  --color-warning:      #985c00;
  --color-on-warning:   #ffffff;
  --color-warning-soft: #fdf2e0;
  --color-on-warning-soft: #8a5200;
  --color-warning-hover: #b87000;
  --color-success:      #256a29;
  --color-on-success:   #ffffff;
  --color-success-soft: #e7f3e8;
  --color-success-hover: #2f8534;
  --color-info:         #0f6099;
  --color-on-info:      #ffffff;
  --color-info-soft:    #e3f1fc;

  /* The same ladder, reflected. White is the card; the page is the gray it
     sits on; a field steps back off the card; focus steps back once more. */
  --color-background:       #edeff1;

  --step-1: #ffffff;
  --step-2: #eceff1;
  --step-3: #e1e5e9;
  --step-4: #d3d9de;

  --color-surface:          var(--step-1);
  --color-surface-sunken:   var(--step-2);
  --color-surface-hover:    var(--step-3);
  --color-surface-raised:   var(--step-1);

  --color-on-background:    #1a1d1f;
  --color-on-surface:       #1a1d1f;
  --color-on-surface-muted: #474d52;
  /* Darkened from the straight inversion for the same reason as its dark
     counterpart: it has to clear 4.5:1 on the top rung, not only on white. */
  --color-on-surface-faint: #575f66;

  --color-divider:       #e0e4e7;
  --color-border:        #e0e4e7;
  --color-border-strong: #ccd2d7;
  --color-border-loud:   #aeb6bd;
  --color-scrim:         rgba(16, 24, 32, .45);

  --rgb-on-surface: 26, 29, 31;
  --rgb-primary:    27, 126, 204;
  --rgb-surface:    255, 255, 255;

  /* A dark overlay on a light ground reads far stronger than a light overlay
     on a dark one at the same alpha, so these are roughly halved. */
  --state-hover:    .045;
  --state-focus:    .09;
  --state-pressed:  .085;
  /* A shade on a light ground reads far stronger than on a dark one, and
     hover is already darkening here, so press only has to go further. */
  --state-press-shade:  .16;
  --state-selected: .09;

  /* Hover darkens here, where on the dark theme it lightens. Both directions
     mean the same thing — more contrast against the ground under it. */
  --scrollbar-thumb-hover: var(--neutral-60);

  /* Shadows have to be re-stated, not inherited: the dark values are 35–60%
     black, which on a white card reads as soot rather than as elevation. */
  --shadow-1:  0 1px 2px rgba(16,24,32,.08), 0 2px 8px rgba(16,24,32,.06);
  --shadow-2:  var(--shadow-1);
  --shadow-3:  var(--shadow-1);
  --shadow-4:  0 2px 8px rgba(16,24,32,.10), 0 4px 16px rgba(16,24,32,.08);
  --shadow-6:  var(--shadow-4);
  --shadow-8:  var(--shadow-4);
  --shadow-12: 0 4px 16px rgba(16,24,32,.12), 0 12px 36px rgba(16,24,32,.12);
  --shadow-16: var(--shadow-12);
  --shadow-24: var(--shadow-12);

  color-scheme: light;
}

@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) {
    --color-primary:          var(--accent-60);
    --color-primary-hover:    var(--accent-70);
    --color-primary-strong:   var(--accent-70);
    --color-primary-soft:     #e3f1fc;
    --color-primary-ground:   #0f6099;
    --color-primary-text:     #0f6099;
    --color-on-primary:       #ffffff;
    --color-secondary:        #535c65;
    --color-secondary-strong: #32373d;
    --color-secondary-hover:  #697480;
    --color-secondary-soft:   #e7eaec;
    --color-on-secondary:     #ffffff;
    --color-neutral-hover:    #363c42;
    --color-danger:       #bd2424;
    --color-on-danger:    #ffffff;
    --color-danger-soft:  #fdeaea;
    --color-danger-hover: #d93434;
    --color-warning:      #985c00;
    --color-on-warning:   #ffffff;
    --color-warning-soft: #fdf2e0;
    --color-on-warning-soft: #8a5200;
    --color-warning-hover: #b87000;
    --color-success:      #256a29;
    --color-on-success:   #ffffff;
    --color-success-soft: #e7f3e8;
    --color-success-hover: #2f8534;
    --color-info:         #0f6099;
    --color-on-info:      #ffffff;
    --color-info-soft:    #e3f1fc;
    --color-background:       #edeff1;
    --step-1: #ffffff;
    --step-2: #eceff1;
    --step-3: #e1e5e9;
    --step-4: #d3d9de;
    --color-surface:          var(--step-1);
    --color-surface-sunken:   var(--step-2);
    --color-surface-hover:    var(--step-3);
    --color-surface-raised:   var(--step-1);
    --color-on-background:    #1a1d1f;
    --color-on-surface:       #1a1d1f;
    --color-on-surface-muted: #474d52;
    --color-on-surface-faint: #575f66;
    --color-divider:       #e0e4e7;
    --color-border:        #e0e4e7;
    --color-border-strong: #ccd2d7;
    --color-border-loud:   #aeb6bd;
    --color-scrim:         rgba(16, 24, 32, .45);
    --rgb-on-surface: 26, 29, 31;
    --rgb-primary:    27, 126, 204;
    --rgb-surface:    255, 255, 255;
    --state-hover:    .045;
    --state-focus:    .09;
    --state-pressed:  .085;
    --state-press-shade:  .16;
    --state-selected: .09;
    --scrollbar-thumb-hover: var(--neutral-60);
    --shadow-1:  0 1px 2px rgba(16,24,32,.08), 0 2px 8px rgba(16,24,32,.06);
    --shadow-2:  var(--shadow-1);
    --shadow-3:  var(--shadow-1);
    --shadow-4:  0 2px 8px rgba(16,24,32,.10), 0 4px 16px rgba(16,24,32,.08);
    --shadow-6:  var(--shadow-4);
    --shadow-8:  var(--shadow-4);
    --shadow-12: 0 4px 16px rgba(16,24,32,.12), 0 12px 36px rgba(16,24,32,.12);
    --shadow-16: var(--shadow-12);
    --shadow-24: var(--shadow-12);
    color-scheme: light;
  }
}


/* --- The relative ladder ---------------------------------------------- */
/* A language that separates planes by fill has one failure mode, and it is
   total: put a component on a container that is already painted in that
   component's own color and the component does not look wrong, it looks
   ABSENT. A six-box one-time-code field inside an auth card simply was not
   there; so were a toolbar on a demo stage and a fieldset on a form.

   Pinning each component to an absolute rung cannot fix it, because the right
   rung depends on what the thing is sitting on, which a component cannot know.
   So the rung names are relative instead: --color-surface means "the plane I
   am on", and any container that has spent that plane re-points the names one
   rung further up for everything inside it. A card is --color-surface on the
   page; inside it, --color-surface is already step 2, so a nested card lands
   on step 2 and a field inside that lands on step 3, with no component having
   to know where it was dropped.

   This is why --step-1 … --step-4 exist as separate fixed names. Writing
   `--color-surface: var(--color-surface-sunken)` here would not shift the
   ladder by one: the right-hand side would resolve against this same block's
   own new value and the whole chain would collapse onto its last rung.

   :where() keeps the whole thing at zero specificity, so every component rule
   still wins over it, and the list is containers that PAINT themselves. Every
   container in the kit now does: .card-outlined used to be the exception and
   was retired into an alias of .card, which is in the list already.

   There are four rungs, so nesting saturates at the third level rather than
   running off the end: a container inside a container inside a container
   re-points to the same top rungs its parent did. That is a deliberate floor,
   not an oversight — anything nested that deep has a layout problem that more
   shades of gray will not solve.

   The `> *` is load-bearing. A custom property declared on an element is in
   scope for that element's OWN declarations, so re-basing the container
   itself moves the container up a rung along with its contents and the two
   collide again exactly as before. Shifting the direct children instead
   leaves the container painting from the ladder it was dropped into, and
   inheritance carries the new rungs down from there. */
:where(.card, .demo-stage, .panel-group, .widget, .stat, .testimonial,
       .price-card, .auth-card, .settings-list, .list, .table-wrap, .toolbar,
       .fieldset, .menu, .popover, .combobox-popup, .calendar, .dialog, .sheet,
       .drawer, .bottombar, .bottom-nav, .rail, .page-hero, .banner, .consent,
       .editor, .filter-panel, .index-card, .pager-link, .message-bubble,
       .board-card, .sortable-item, .chat, .schedule, .virtual-list, .cta,
       .surface, .toast, .tooltip, .inbox-item) > * {
  --color-surface:        var(--step-2);
  --color-surface-sunken: var(--step-3);
  --color-surface-hover:  var(--step-4);
  --color-surface-raised: var(--step-3);
}

/* Two exceptions, both for the same reason: these are painted in a rung ABOVE
   --color-surface, so their contents have to start higher still. */
:where(.demo-stage-sunken, .toolbar-sunken) > * {
  --color-surface:        var(--step-3);
  --color-surface-sunken: var(--step-4);
  --color-surface-hover:  var(--step-4);
}

/* The second rung down. The rule above hands out fixed steps, so on its own a
   container inside a container re-points its contents to exactly the rungs
   its parent already used, and the collision comes straight back one level
   deeper — a field inside a settings list inside a sunken stage was the case
   that found this. Matching the nesting explicitly shifts it once more.

   Two levels is where it stops, because four rungs is where the fills stop.
   Below that it saturates on the top rung: a field nested three containers
   deep stays legible against its parent, it just stops gaining contrast it
   has nowhere to take from. Still zero specificity, and declared after the
   single-level rule so it wins on order rather than on weight. */
:where(.card, .demo-stage, .panel-group, .widget, .stat, .testimonial,
       .price-card, .auth-card, .settings-list, .list, .table-wrap, .toolbar,
       .fieldset, .menu, .popover, .combobox-popup, .calendar, .dialog, .sheet,
       .drawer, .bottombar, .bottom-nav, .rail, .page-hero, .banner, .consent,
       .editor, .filter-panel, .index-card, .pager-link, .message-bubble,
       .board-card, .sortable-item, .chat, .schedule, .virtual-list, .cta,
       .surface, .toast, .tooltip, .inbox-item) :where(.card, .demo-stage, .panel-group, .widget, .stat, .testimonial,
       .price-card, .auth-card, .settings-list, .list, .table-wrap, .toolbar,
       .fieldset, .menu, .popover, .combobox-popup, .calendar, .dialog, .sheet,
       .drawer, .bottombar, .bottom-nav, .rail, .page-hero, .banner, .consent,
       .editor, .filter-panel, .index-card, .pager-link, .message-bubble,
       .board-card, .sortable-item, .chat, .schedule, .virtual-list, .cta,
       .surface, .toast, .tooltip, .inbox-item) > * {
  --color-surface:        var(--step-3);
  --color-surface-sunken: var(--step-4);
  --color-surface-hover:  var(--step-4);
  --color-surface-raised: var(--step-4);
}

/* And one the other way: a stage standing in for the page has spent nothing,
   so the ladder inside it starts where it does at the top level. */
:where(.demo-stage-page) > * {
  --color-surface:        var(--step-1);
  --color-surface-sunken: var(--step-2);
  --color-surface-hover:  var(--step-3);
  --color-surface-raised: var(--step-2);
}

/* ===================================================== 03 Reset and base */
*, *::before, *::after { box-sizing: border-box; }

/* The hidden attribute is only a UA-stylesheet display:none, so any component
   rule that sets a display of its own silently beats it — a .palette-item set
   to flex stays on screen after the script has hidden it, and the state the
   markup reports no longer matches what is drawn. The !important is what makes
   the attribute mean what it says, whatever a component declares. */
[hidden] { display: none !important; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: calc(var(--size-topbar) + var(--space-4));
  /* Always reserve the bar's gutter. Without it, a page that happens to fit
     the viewport — a short demo, or any page while a dialog has locked
     document scrolling — renders a bar's width wider, sliding every centered
     layout sideways the moment the bar comes or goes. The native bar used to
     hold this open simply by existing; now that nothing native is painted,
     this padding is what holds it, and .scrollbar-page is drawn into it.
     overflow-y keeps the root a scroll container either way. */
  padding-inline-end: var(--scrollbar-size);
  overflow-y: scroll;
}

/* Scrollbars ----------------------------------------------------------------
   Nothing native is painted, anywhere. Restyling the native bar can only ever
   go part of the way: ::-webkit-scrollbar reaches its shape in Chrome and
   Safari, Firefox exposes nothing but a two-keyword width and a pair of
   colors, and a touch platform ignores both in favour of its own fading
   overlay. Three engines, three bars, on a surface this language draws down
   the edge of every page.

   So the bar is drawn instead — .scrollbar, built and driven by the scrollbar
   behavior in design_language.js. Only the painting changes hands: the real
   scroller underneath is untouched, so the wheel, the keyboard, text
   selection, touch momentum, snap points and overscroll all stay the
   browser's job, which is the entire reason for overlaying a native scroller
   rather than replacing it with one of our own.

   The bars are never inserted into the scroller they belong to. They are
   fixed elements parked at the end of <body> and placed from the scroller's
   rect, because a child would change what :last-child, :nth-child and :empty
   mean inside every scroller in the kit — and .editor-surface is
   contenteditable, where an injected node is something the writer can select
   and delete. */
* {
  /* Firefox, and the legacy Edge still shipping on kiosks. */
  scrollbar-width: none;
  -ms-overflow-style: none;
}
*::-webkit-scrollbar { display: none; }

/* A track and a thumb. The track is a hit target rather than a visible
   trough: a drawn trough would put a second line down an edge that already
   has the thumb on it. Both are placed by the behavior — everything here is
   appearance. aria-hidden is set in the script, because this is a picture of
   a scroll position that assistive technology already reports properly. */
.scrollbar {
  position: fixed;
  z-index: var(--layer-scrollbar);
  background: transparent;
  /* The bar only ever covers the gutter reserved for it, so taking the
     pointer costs no content underneath. touch-action keeps a drag of the
     thumb from being handed to the page as a pan. */
  touch-action: none;
}
.scrollbar-page { z-index: var(--layer-scrollbar-page); }

.scrollbar-thumb {
  position: absolute;
  border-radius: var(--radius-pill);
  background-color: var(--scrollbar-thumb);
  transition: background-color var(--duration-fast) var(--ease-standard);
}
/* The thumb FILLS the gutter and is only drawn narrower, which is not the
   same thing as being narrower. The difference is the hit area, and it is
   the whole reason this is a transparent border rather than a width.

   A 6px thumb centered in a 10px gutter leaves two dead pixels down each
   side. The pair on the outer side is the expensive mistake: that is the
   edge of the window, and on a maximized window the edge of the screen,
   which is the one target a pointer can be thrown at without aiming because
   it cannot overshoot it. Landing there has to mean grabbing the thumb. With
   a narrow thumb it meant hitting the track and paging instead — a thrown
   pointer did the one thing the gesture was not asking for.

   So the element spans the full gutter, the background is clipped to the
   padding box, and the transparent border carries the inset. The drawn bar
   is identical; every pixel of the gutter now drags it. This is also what
   the native ::-webkit-scrollbar-thumb rule here used to do, for the same
   reason. --scrollbar-inset is derived rather than authored so that retuning
   either token keeps the thumb centered. */
.scrollbar-thumb {
  --scrollbar-inset: calc((var(--scrollbar-size) - var(--scrollbar-thumb-size)) / 2);
  background-clip: padding-box;
}
.scrollbar-y > .scrollbar-thumb {
  inset-inline: 0;
  border-inline: var(--scrollbar-inset) solid transparent;
}
.scrollbar-x > .scrollbar-thumb {
  inset-block: 0;
  border-block: var(--scrollbar-inset) solid transparent;
}

.scrollbar:hover > .scrollbar-thumb { background-color: var(--scrollbar-thumb-hover); }
/* Pressed is darker, the way every pressed control in this language is
   darker: a black wash laid over whatever color the thumb is already
   wearing, so the direction cannot flip with the theme. See the note on
   --state-press-shade. The class stays on for the whole drag, including the
   stretches where the pointer has left the bar. */
.scrollbar.is-dragging > .scrollbar-thumb {
  background-color: var(--scrollbar-thumb-hover);
  background-image: linear-gradient(rgba(0, 0, 0, var(--state-press-shade)),
                                    rgba(0, 0, 0, var(--state-press-shade)));
}
/* A drag that starts on the thumb must not also start a selection in the
   text the thumb is sitting next to. */
body.is-scrollbar-dragging { user-select: none; }

body {
  margin: 0;
  min-height: 100vh;
  background: var(--color-background);
  color: var(--color-on-background);
  font-family: var(--font-sans);
  font-size: var(--type-body-size);
  line-height: var(--type-body-line);
  letter-spacing: var(--type-body-track);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  transition: background-color var(--duration-base) var(--ease-standard),
              color var(--duration-base) var(--ease-standard);
}

h1, h2, h3, h4, h5, h6, p, figure, blockquote, dl, dd { margin: 0; }
ul, ol { margin: 0; padding: 0; }
img, svg, video, canvas, iframe { display: block; max-width: 100%; }
img, video { height: auto; }

button, input, select, textarea {
  font: inherit;
  letter-spacing: inherit;
  color: inherit;
}

/* --color-primary-text, not --color-primary: the accent proper is tuned to be
   read as a FILL with white on it, and as a thin stroke of text on the page
   ground it comes out dim. The -40 step is the same hue one stop lighter and
   is what every piece of accent text in the language uses. */
a {
  color: var(--color-primary-text);
  text-decoration: none;
  border-radius: var(--radius-xs);
}
a:hover { text-decoration: underline; }

hr {
  border: 0;
  border-top: var(--border-width) solid var(--color-border);
  margin: var(--space-5) 0;
}

:focus-visible {
  outline: var(--focus-ring-width) solid var(--focus-ring-color);
  outline-offset: var(--focus-ring-offset);
}

/* The one place the darkest accent step earns a solid fill: a selection has
   to be unmistakable, and a 22% wash of it is not. */
::selection {
  background: var(--accent-70);
  color: #fff;
}

/* Visible only to assistive tech. */
/* Every declaration here is !important, and that is the point of the class
   rather than a shortcut. This is applied to an element that already belongs
   to a component, so it is always competing with that component's own rules,
   and it only ever has one class worth of specificity to do it with. A
   .visually-hidden legend inside a .fieldset lost its width to
   `.fieldset > legend { width: 100% }` and, being absolutely positioned
   against the initial containing block, resolved that 100% to the viewport:
   an invisible element 1430px wide, pushed off the right-hand edge, widening
   the document by 440px and putting a horizontal scrollbar on the page with
   nothing visible anywhere to explain it.

   A utility whose entire contract is "this element is not in the layout" has
   to win that argument every time, or it is not a contract. */
.visually-hidden {
  position: absolute !important;
  width: 1px !important;
  height: 1px !important;
  margin: -1px !important;
  padding: 0 !important;
  overflow: hidden !important;
  clip: rect(0 0 0 0) !important;
  clip-path: inset(50%) !important;
  white-space: nowrap !important;
  border: 0 !important;
}

/* Jumps a keyboard user straight past the chrome. */
.skip-link {
  position: fixed;
  top: var(--space-2);
  left: var(--space-2);
  z-index: var(--layer-tooltip);
  padding: var(--space-2) var(--space-4);
  background: var(--color-primary-ground);
  color: var(--color-on-primary);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-4);
  transform: translateY(-200%);
  transition: transform var(--duration-fast) var(--ease-decelerate);
}
.skip-link:focus { transform: translateY(0); text-decoration: none; }

/* The icon font ships glyph names as text content; keep it from reflowing. */
.icon {
  font-family: 'Material Icons';
  font-weight: normal;
  font-style: normal;
  font-size: var(--size-icon);
  line-height: 1;
  width: 1em;
  height: 1em;
  display: inline-block;
  overflow: hidden;
  text-transform: none;
  letter-spacing: normal;
  white-space: nowrap;
  word-wrap: normal;
  direction: ltr;
  font-feature-settings: 'liga';
  -webkit-font-feature-settings: 'liga';
  -webkit-font-smoothing: antialiased;
  flex: none;
  user-select: none;
}
.icon-sm { font-size: var(--size-icon-sm); }
.icon-lg { font-size: var(--size-icon-lg); }
.icon-xl { font-size: 56px; }

/* ============================================================ 04 Typography */
.text-display-1,
.text-display-2,
.text-display-3,
.text-heading-1,
.text-heading-2,
.text-heading-3,
.text-subtitle,
.text-subtitle-2,
.text-body,
.text-body-2,
.text-label,
.text-caption,
.text-overline { margin: 0; }

/* Everything that names a thing is set in the display face; everything that
   is read is set in the body face. The split happens here, once, so a page
   never has to remember which heading level crosses the line — it is drawn
   between heading-3 and subtitle, the point at which a line stops being a
   label for what follows and starts being the thing itself. */
.text-display-1,
.text-display-2,
.text-display-3,
.text-heading-1,
.text-heading-2,
.text-heading-3 { font-family: var(--font-display); }

.text-display-1 { font-size: var(--type-display-1-size); line-height: var(--type-display-1-line); letter-spacing: var(--type-display-1-track); font-weight: var(--type-display-1-weight); }
.text-display-2 { font-size: var(--type-display-2-size); line-height: var(--type-display-2-line); letter-spacing: var(--type-display-2-track); font-weight: var(--type-display-2-weight); }
.text-display-3 { font-size: var(--type-display-3-size); line-height: var(--type-display-3-line); letter-spacing: var(--type-display-3-track); font-weight: var(--type-display-3-weight); }
.text-heading-1 { font-size: var(--type-heading-1-size); line-height: var(--type-heading-1-line); letter-spacing: var(--type-heading-1-track); font-weight: var(--type-heading-1-weight); }
.text-heading-2 { font-size: var(--type-heading-2-size); line-height: var(--type-heading-2-line); letter-spacing: var(--type-heading-2-track); font-weight: var(--type-heading-2-weight); }
.text-heading-3 { font-size: var(--type-heading-3-size); line-height: var(--type-heading-3-line); letter-spacing: var(--type-heading-3-track); font-weight: var(--type-heading-3-weight); }
.text-subtitle   { font-size: var(--type-subtitle-size);   line-height: var(--type-subtitle-line);   letter-spacing: var(--type-subtitle-track);   font-weight: var(--type-subtitle-weight); }
.text-subtitle-2 { font-size: var(--type-subtitle-2-size); line-height: var(--type-subtitle-2-line); letter-spacing: var(--type-subtitle-2-track); font-weight: var(--type-subtitle-2-weight); }
.text-body       { font-size: var(--type-body-size);   line-height: var(--type-body-line);   letter-spacing: var(--type-body-track);   font-weight: var(--type-body-weight); }
.text-body-2     { font-size: var(--type-body-2-size); line-height: var(--type-body-2-line); letter-spacing: var(--type-body-2-track); font-weight: var(--type-body-2-weight); }
.text-label      { font-size: var(--type-label-size);  line-height: var(--type-label-line);  letter-spacing: var(--type-label-track);  font-weight: var(--type-label-weight);  text-transform: var(--type-label-transform); }
.text-caption    { font-size: var(--type-caption-size); line-height: var(--type-caption-line); letter-spacing: var(--type-caption-track); font-weight: var(--type-caption-weight); }
.text-overline   { font-size: var(--type-overline-size); line-height: var(--type-overline-line); letter-spacing: var(--type-overline-track); font-weight: var(--type-overline-weight); text-transform: var(--type-overline-transform); }

.text-muted   { color: var(--color-on-surface-muted); }
.text-faint   { color: var(--color-on-surface-faint); }
.text-primary { color: var(--color-primary-text); }
.text-display { font-family: var(--font-display); }
.text-danger  { color: var(--color-danger); }
.text-warning { color: var(--color-warning); }
.text-success { color: var(--color-success); }
.text-info    { color: var(--color-info); }

.text-start  { text-align: left; }
.text-center { text-align: center; }
.text-end    { text-align: right; }
.text-justify { text-align: justify; }

.text-light   { font-weight: var(--weight-light); }
.text-regular { font-weight: var(--weight-regular); }
.text-medium  { font-weight: var(--weight-medium); }
.text-bold    { font-weight: var(--weight-bold); }
.text-italic  { font-style: italic; }
.text-upper   { text-transform: uppercase; }
.text-lower   { text-transform: lowercase; }
.text-caps    { text-transform: capitalize; }
.text-mono    { font-family: var(--font-mono); }
.text-nowrap  { white-space: nowrap; }
.text-balance { text-wrap: balance; }
.text-pretty  { text-wrap: pretty; }

/* Clamp to a single line, or to n lines via --lines. */
.text-truncate {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.text-clamp {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: var(--lines, 2);
  line-clamp: var(--lines, 2);
  overflow: hidden;
}

/* ================================================================ 05 Layout */
.container {
  width: 100%;
  max-width: var(--size-container);
  margin-inline: auto;
  padding-inline: var(--size-gutter);
}
.container-narrow { max-width: var(--size-container-narrow); }
.container-wide   { max-width: var(--size-container-wide); }
.container-full   { max-width: none; }
.readable         { max-width: var(--size-readable); }

.section { padding-block: var(--space-9); }
.section-tight { padding-block: var(--space-6); }

/* A 12-column grid with a token gutter. Children opt into spans. */
.grid {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  gap: var(--space-5);
}
.grid-gap-sm { gap: var(--space-2); }
.grid-gap-lg { gap: var(--space-6); }

.col-1  { grid-column: span 1; }
.col-2  { grid-column: span 2; }
.col-3  { grid-column: span 3; }
.col-4  { grid-column: span 4; }
.col-5  { grid-column: span 5; }
.col-6  { grid-column: span 6; }
.col-7  { grid-column: span 7; }
.col-8  { grid-column: span 8; }
.col-9  { grid-column: span 9; }
.col-10 { grid-column: span 10; }
.col-11 { grid-column: span 11; }
.col-12 { grid-column: span 12; }

/* Auto-fitting grid: set --min to change the breakpoint per track. */
.grid-auto {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(var(--min, 260px), 100%), 1fr));
  gap: var(--space-5);
}

.stack       { display: flex; flex-direction: column; gap: var(--space-4); }
.stack-sm    { gap: var(--space-2); }
.stack-lg    { gap: var(--space-6); }
.row         { display: flex; flex-direction: row; gap: var(--space-3); align-items: center; flex-wrap: wrap; }
.row-nowrap  { flex-wrap: nowrap; }
.row-tight   { gap: var(--space-2); }
.row-loose   { gap: var(--space-5); }

.items-start   { align-items: flex-start; }
.items-center  { align-items: center; }
.items-end     { align-items: flex-end; }
.items-stretch { align-items: stretch; }
.items-baseline { align-items: baseline; }
.justify-start   { justify-content: flex-start; }
.justify-center  { justify-content: center; }
.justify-end     { justify-content: flex-end; }
.justify-between { justify-content: space-between; }
.justify-around  { justify-content: space-around; }
.wrap   { flex-wrap: wrap; }
.grow   { flex: 1 1 auto; }
.shrink-0 { flex: none; }
.spacer { flex: 1 1 auto; }

.block         { display: block; }
.inline-block  { display: inline-block; }
.flex          { display: flex; }
.inline-flex   { display: inline-flex; }
.hidden        { display: none !important; }

.relative { position: relative; }
.absolute { position: absolute; }
.sticky-top {
  position: sticky;
  top: calc(var(--size-topbar) + var(--space-2));
  z-index: var(--layer-sticky);
}

.full-width  { width: 100%; }
.full-height { height: 100%; }

/* Scroll containers are positioned so that absolutely positioned descendants
   — a .visually-hidden label, say — are clipped by the scroller rather than
   escaping to the initial containing block and widening the whole document. */
.scroll-x { position: relative; overflow-x: auto; overscroll-behavior-x: contain; }
.scroll-y { position: relative; overflow-y: auto; overscroll-behavior-y: contain; }
.scroll-box {
  position: relative;
  max-height: var(--max-height, 280px);
  overflow-y: auto;
  overscroll-behavior: contain;
}

/* Horizontal scroller with snap stops. */
.scroll-snap {
  position: relative;
  display: flex;
  gap: var(--space-4);
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  padding-bottom: var(--space-3);
}
.scroll-snap > * { scroll-snap-align: start; flex: none; }

.ratio { position: relative; width: 100%; }
.ratio::before { content: ''; display: block; padding-top: var(--ratio, 56.25%); }
.ratio > * { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.ratio-square { --ratio: 100%; }
.ratio-video  { --ratio: 56.25%; }
.ratio-photo  { --ratio: 75%; }
.ratio-wide   { --ratio: 42.85%; }

/* Spacing utilities, generated against the same scale as the tokens. */
.m-0 { margin: 0; }        .p-0 { padding: 0; }
.mt-1 { margin-top: var(--space-1); } .mt-2 { margin-top: var(--space-2); }
.mt-3 { margin-top: var(--space-3); } .mt-4 { margin-top: var(--space-4); }
.mt-5 { margin-top: var(--space-5); } .mt-6 { margin-top: var(--space-6); }
.mt-8 { margin-top: var(--space-8); }
.mb-1 { margin-bottom: var(--space-1); } .mb-2 { margin-bottom: var(--space-2); }
.mb-3 { margin-bottom: var(--space-3); } .mb-4 { margin-bottom: var(--space-4); }
.mb-5 { margin-bottom: var(--space-5); } .mb-6 { margin-bottom: var(--space-6); }
.mb-8 { margin-bottom: var(--space-8); }
.p-2 { padding: var(--space-2); } .p-3 { padding: var(--space-3); }
.p-4 { padding: var(--space-4); } .p-5 { padding: var(--space-5); }
.p-6 { padding: var(--space-6); }

@media (max-width: 1024px) {
  .col-1, .col-2, .col-3, .col-4 { grid-column: span 6; }
  .col-5, .col-6, .col-7, .col-8 { grid-column: span 12; }
  .col-9, .col-10, .col-11 { grid-column: span 12; }
  .hide-lg { display: none !important; }
}
@media (max-width: 720px) {
  /* Collapse to a single track rather than keeping twelve. Eleven gutters of
     their own are wider than a phone viewport, and a twelve-track grid cannot
     shrink below them however small the columns go. */
  .grid { grid-template-columns: minmax(0, 1fr); }
  .grid > [class*="col-"] { grid-column: 1 / -1; }
  .section { padding-block: var(--space-6); }
  .hide-sm { display: none !important; }
}
@media (min-width: 721px) { .show-sm-only { display: none !important; } }

/* Masonry ------------------------------------------------------------------ */
/* Items of unequal height packed so the short ones do not leave gaps. CSS
   columns are the only implementation available everywhere today, and they
   have one consequence worth knowing: they fill column by column, so reading
   order runs down the first column and not across the row. Where order
   carries meaning, use .gallery or the grid instead. */
.masonry {
  columns: var(--masonry-columns, 3);
  column-gap: var(--masonry-gap, var(--space-4));
}
.masonry > * {
  /* An item is only breakable if nothing stops it, and a half card split
     across two columns reads as two broken cards. */
  break-inside: avoid;
  margin-bottom: var(--masonry-gap, var(--space-4));
  /* Chromium leaves a sliver of the first item above the column box without
     this, which shows up as a one-pixel misalignment between columns. */
  display: flow-root;
}
.masonry-2 { --masonry-columns: 2; }
.masonry-4 { --masonry-columns: 4; }
/* Sized by item width rather than a column count, so the track count falls
   out of the available space the way an auto-fit grid does. */
.masonry-fluid { columns: var(--masonry-min, 240px) var(--masonry-columns, 4); }

@media (max-width: 1024px) { .masonry { --masonry-columns: 2; } }
@media (max-width: 720px)  { .masonry { --masonry-columns: 1; } }

/* Grid-native masonry, once a browser ships it. The declaration is invalid
   where it is unsupported, so the columns above stay in force and nothing has
   to detect anything. Where it is supported, grid order is row-major, so
   reading order is restored as a side effect. */
@supports (grid-template-rows: masonry) {
  .masonry {
    columns: auto;
    display: grid;
    grid-template-columns: repeat(var(--masonry-columns, 3), minmax(0, 1fr));
    grid-template-rows: masonry;
    gap: var(--masonry-gap, var(--space-4));
  }
  .masonry > * { margin-bottom: 0; }
  .masonry-fluid {
    grid-template-columns: repeat(auto-fill, minmax(var(--masonry-min, 240px), 1fr));
  }
}

/* Container queries -------------------------------------------------------- */
/* A media query asks how big the viewport is, which is the wrong question for
   a component that can be dropped into a sidebar, a dialog or a full-width
   band. .cq makes an element a query container so its children can ask how
   much room they were actually given. */
.cq        { container-type: inline-size; }
.cq-card   { container: card / inline-size; }
.cq-panel  { container: panel / inline-size; }

/* The adaptive card used across the showcase: stacked when narrow, side by
   side once its own box is wide enough, whatever the viewport is doing. */
.adaptive { display: grid; gap: var(--space-4); grid-template-columns: minmax(0, 1fr); }
.adaptive-media { border-radius: var(--radius-md); overflow: hidden; }

@container (min-width: 420px) {
  .adaptive { grid-template-columns: 160px minmax(0, 1fr); align-items: start; }
}
@container (min-width: 640px) {
  .adaptive { grid-template-columns: 220px minmax(0, 1fr); gap: var(--space-5); }
  .adaptive-title { font-size: var(--type-heading-2-size); line-height: var(--type-heading-2-line); }
}

/* Container query units resolve against the container, not the viewport, so
   type set in cqi scales with the component's own width. */
.cq-fluid-title {
  font-size: clamp(var(--type-heading-3-size), 5cqi, var(--type-heading-1-size));
  line-height: 1.2;
}

/* ============================================================== 06 Surfaces */
.surface {
  background: var(--color-surface);
  color: var(--color-on-surface);
  border-radius: var(--radius-lg);
}
.surface-sunken { background: var(--color-surface-sunken); }
.surface-raised { background: var(--color-surface-raised); }

.elevation-0  { box-shadow: var(--shadow-0); }
.elevation-1  { box-shadow: var(--shadow-1); }
.elevation-2  { box-shadow: var(--shadow-2); }
.elevation-3  { box-shadow: var(--shadow-3); }
.elevation-4  { box-shadow: var(--shadow-4); }
.elevation-6  { box-shadow: var(--shadow-6); }
.elevation-8  { box-shadow: var(--shadow-8); }
.elevation-12 { box-shadow: var(--shadow-12); }
.elevation-16 { box-shadow: var(--shadow-16); }
.elevation-24 { box-shadow: var(--shadow-24); }

/* --- Card ---------------------------------------------------------------- */
/* A card has no shadow and no outline. Its own fill is what separates it from
   the page, and that is the whole mechanism: three cards in a row read as
   three cards because of the gaps between three blocks of color, not because
   each one is drawn around. A resting card that casts a shadow is the single
   clearest tell of the language this replaced. */
.card {
  display: flex;
  flex-direction: column;
  background: var(--color-surface);
  color: var(--color-on-surface);
  border-radius: var(--radius-lg);
  overflow: hidden;
  transition: background-color var(--duration-fast) var(--ease-standard);
}
/* RETIRED, and kept only as an alias.

   This used to be the quietest card: no fill, held together by a hairline,
   for a block that had to be bounded without reading as a surface of its
   own. The argument was that it earned its border by giving up its fill.

   It does not survive contact with the rest of the language. Everything else
   here separates one plane from the next by fill — that is the single idea
   the whole thing is built on, and .button-outlined already gave up its
   border for exactly this reason. A grid of hairline cards sitting in a kit
   where nothing else has an edge does not read as "quieter", it reads as a
   different kit: the overview page had three bordered cards explaining a
   language whose own rule is that borders are not how blocks are told apart.

   And the role it was defending is not a real one. "Bounded but not a
   surface" is a distinction a fill-based language cannot draw, because in a
   fill-based language being bounded IS being a surface. So the quiet card is
   just a card, and the class stays in the contract so existing markup keeps
   working — the same arrangement .input-filled has. A replacement language
   that does separate by edge can give it its border back here, in one line,
   without touching a single page. */
.card-outlined { /* same as the base card */ }
/* One step up the ladder rather than down, matching where a field sits. Use
   it for a card nested inside another card, where the parent has already
   spent --color-surface. */
.card-flat     { background: var(--color-surface-sunken); }

/* Cards that are themselves a link or button. The whole response is a change
   of fill — no lift, no shadow, no scale. */
.card-interactive { cursor: pointer; position: relative; }
.card-interactive::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: var(--color-on-surface);
  opacity: 0;
  transition: opacity var(--duration-fast) var(--ease-standard);
}
.card-interactive:hover::after  { opacity: var(--state-hover); }
.card-interactive:active::after { opacity: var(--state-pressed); }

.card-media { position: relative; }
.card-media img { width: 100%; object-fit: cover; }

.card-header {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-4);
}
.card-header-text { min-width: 0; flex: 1 1 auto; }
.card-title {
  font-family: var(--font-display);
  font-size: var(--type-heading-3-size);
  line-height: var(--type-heading-3-line);
  letter-spacing: var(--type-heading-3-track);
  font-weight: var(--type-heading-3-weight);
}
.card-subtitle {
  font-size: var(--type-body-2-size);
  line-height: var(--type-body-2-line);
  letter-spacing: var(--type-body-2-track);
  color: var(--color-on-surface-muted);
}
.card-body {
  padding: 0 var(--space-4) var(--space-4);
  color: var(--color-on-surface-muted);
  font-size: var(--type-body-2-size);
  line-height: var(--type-body-2-line);
  letter-spacing: var(--type-body-2-track);
}
.card-body:first-child { padding-top: var(--space-4); }
.card-actions {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2);
  margin-top: auto;
}
.card-actions-end { justify-content: flex-end; }
.card-actions-split { justify-content: space-between; }

/* --- Divider -------------------------------------------------------------- */
.divider {
  border: 0;
  border-top: var(--border-width) solid var(--color-border);
  margin: 0;
}
.divider-inset { margin-left: var(--space-9); }
.divider-vertical {
  border-top: 0;
  border-left: var(--border-width) solid var(--color-border);
  align-self: stretch;
  height: auto;
  margin-inline: var(--space-2);
}
/* A divider carrying a centerd label. */
.divider-labeled {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  color: var(--color-on-surface-muted);
  font-size: var(--type-caption-size);
  letter-spacing: var(--type-caption-track);
}
.divider-labeled::before,
.divider-labeled::after {
  content: '';
  flex: 1 1 auto;
  border-top: var(--border-width) solid var(--color-border);
}

/* --- Expansion panel / accordion ------------------------------------------ */
.panel-group {
  border-radius: var(--radius-lg);
  overflow: hidden;
  background: var(--color-surface);
}
/* A hairline between panels, which is what the divider is for: separating
   rows WITHIN one surface, where there is no gap to do the job. */
.panel-group > .panel + .panel { border-top: var(--border-width) solid var(--color-divider); }
.panel { background: var(--color-surface); }
/* Inside a group the panel is a row, not a plane: the group supplies the
   fill and the hairline above separates one row from the next. Painting the
   row as well would either match the group exactly (so the fill is wasted)
   or step up a rung (so every row looks selected). */
.panel-group > .panel { background: transparent; }
/* An open panel is marked by the state of its chevron and by the content it
   has revealed. Lifting it out of the stack on a shadow would say it had
   become a different kind of thing, which it has not. */

.panel-summary {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-4);
  min-height: var(--size-touch);
  cursor: pointer;
  list-style: none;
  font-family: var(--font-display);
  font-weight: var(--weight-semi);
  position: relative;
  user-select: none;
}
.panel-summary::-webkit-details-marker { display: none; }
.panel-summary:hover { background: rgba(var(--rgb-on-surface), var(--state-hover)); }
.panel-summary:focus-visible { outline-offset: -2px; }
.panel-summary::after {
  content: 'expand_more';
  font-family: 'Material Icons';
  font-size: var(--size-icon);
  margin-left: auto;
  color: var(--color-on-surface-muted);
  transition: transform var(--duration-base) var(--ease-standard);
}
.panel[open] > .panel-summary::after { transform: rotate(180deg); }
.panel-content {
  padding: 0 var(--space-4) var(--space-4);
  color: var(--color-on-surface-muted);
  font-size: var(--type-body-2-size);
  line-height: var(--type-body-2-line);
}

/* --- Sheets --------------------------------------------------------------- */
.sheet {
  position: fixed;
  background: var(--color-surface);
  box-shadow: var(--shadow-16);
  z-index: var(--layer-dialog);
  transition: transform var(--duration-slow) var(--ease-standard);
}
/* Square, deliberately. A sheet is a plane anchored to an edge of the window
   and running the full width of it, which is a different kind of thing from
   a dialog: a dialog floats clear of every edge and keeps its radius because
   it has four corners to round. Rounding the top two corners of a sheet
   rounds half an object and leaves the other half cut off by the viewport,
   which reads as a card that did not finish arriving. The radius also fought
   the one job the handle has, which is to say the sheet is draggable. */
.sheet-bottom {
  left: 0; right: 0; bottom: 0;
  max-height: 70vh;
  overflow-y: auto;
  border-radius: 0;
  transform: translateY(101%);
  padding: var(--space-2) 0 var(--space-5);
}
.sheet-bottom[data-open="true"] { transform: translateY(0); }
.sheet-side {
  top: 0; bottom: 0; right: 0;
  width: min(380px, 90vw);
  overflow-y: auto;
  transform: translateX(101%);
  padding: var(--space-4);
}
.sheet-side[data-open="true"] { transform: translateX(0); }
/* The drag affordance at the top of a bottom sheet. */
.sheet-handle {
  width: 32px; height: 4px;
  border-radius: var(--radius-pill);
  background: var(--color-border-strong);
  margin: var(--space-2) auto var(--space-4);
}

/* A shared dimming layer behind sheets, drawers and menus. */
.scrim {
  position: fixed;
  inset: 0;
  background: var(--color-scrim);
  z-index: var(--layer-overlay);
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--duration-base) var(--ease-standard), visibility var(--duration-base);
}
.scrim[data-open="true"] { opacity: 1; visibility: visible; }

/* ================================================================ 07 Buttons */
/* Four voices, and the whole set is told apart by FILL:
     .button            bare      — a verb on the page, no box at all
     .button-text       bare      — the same, tightened for use in a row
     .button-outlined   surface   — a neutral control with a quiet box
     .button-tonal      tint      — engaged / currently-on
     .button-filled     accent    — the one loud thing in its view
   Nothing in that list gains a border or a shadow on hover; each one moves
   one step along the fill ladder instead. */
.button {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-height: var(--size-control);
  padding: 0 var(--space-4);
  border: none;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--color-primary-text);
  font-size: var(--type-label-size);
  line-height: var(--type-label-line);
  letter-spacing: var(--type-label-track);
  font-weight: var(--type-label-weight);
  text-transform: var(--type-label-transform);
  text-decoration: none;
  cursor: pointer;
  overflow: hidden;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
  vertical-align: middle;
  transition: background-color var(--duration-base) var(--ease-standard),
              box-shadow var(--duration-base) var(--ease-standard),
              color var(--duration-base) var(--ease-standard);
}
.button:hover { text-decoration: none; }

/* The translucent layer that carries hover / focus / press feedback. It is
   --color-on-surface rather than currentColor so that the wash is the same
   strength on every variant: keyed to currentColor, an accent-colored
   button washed blue while the neutral one beside it washed gray, and the
   two stopped looking like the same gesture. */
.button::before {
  content: '';
  position: absolute;
  inset: 0;
  background: var(--color-on-surface);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--duration-fast) var(--ease-standard),
              background-color var(--duration-fast) var(--ease-standard);
}
.button:hover::before        { opacity: var(--state-hover); }
.button:focus-visible::before { opacity: var(--state-focus); }

/* PRESS IS A HARD RULE: every button in this language gets darker when it is
   held down, whatever its variant and whatever its intent.

   It used to be a per-variant judgement, and the result was that most filled
   buttons had no visible press at all. Hover and press were the same wash of
   the foreground at .08 and .14 — six percent apart, in the same direction,
   and on a saturated danger or success fill neither one moved the color
   enough to see. Only the primary button looked right, because it alone had
   a real ramp to walk down.

   The rule is worth more than the per-variant tuning. "Held down" is one
   gesture, so it should have one answer, and a reader who learns it on the
   primary button should not have to re-learn it on the one beside it. So the
   press state stops washing the foreground over the control and instead lays
   a shade under the pointer: the same layer, repainted black. That reverses
   direction against hover on a dark ground and deepens it on a light one, and
   in both cases it is unmistakable on every fill the language has.

   Hover is NOT the same kind of rule, and the difference is contrast rather
   than taste. A control with a fill of its own brightens — it walks one step
   up its own ramp to a clean color. A control with no fill (text, icon,
   outlined) has nothing to brighten, so it takes a wash toward the
   foreground, which means it darkens in a light theme. Forcing those two
   together would make one of them invisible. */
.button:active::before {
  background: #000;
  opacity: var(--state-press-shade);
}

.button .icon { font-size: var(--size-icon-sm); }

/* Variants ---------------------------------------------------------------- */
/* The loud one. Flat accent, no shadow at any point in its life: a filled
   button is already the brightest thing in its view, and a shadow under it
   only makes the page look like it is made of paper. Hover moves one step up
   the accent ramp instead of stacking a wash on top, which keeps the fill a
   clean color rather than a muddied one. */
.button-filled {
  background: var(--color-primary-ground);
  color: var(--color-on-primary);
}
.button-filled:hover { background: var(--color-primary); }
/* The shade is capped on a filled button, and the cap is a contrast budget
   rather than a taste. Everywhere else the press shade darkens a surface
   carrying foreground-colored text, so going darker only ever helps the
   label. A filled button's label is tuned to the resting fill — the dark
   theme's danger button reads near-black on #e57373 precisely because white
   on it is 3:1 — so darkening the fill walks that label toward its floor
   instead of away from it. .16 is where every filled intent in both themes
   still clears 4.5:1 while the step from hover to press stays over 1.8. */
.button-filled,
.button-float { --state-press-shade: .16; }

/* The accent fill answers hover by itself, so the shared wash on top of it
   would be a second, redundant signal. The layer is not removed, though —
   it is what carries focus and the press shade, and a filled button is
   subject to the press rule like everything else. Its hover opacity is
   zeroed instead, which leaves the ramp to do the brightening.

   The :not(:active) is load-bearing. A pointer that is pressing is also
   hovering, and this rule and .button:active::before have the same
   specificity — so without it the later of the two would win and a held
   filled button would lose its shade, which is the whole bug this was
   written to fix. */
.button-filled:hover:not(:active)::before { opacity: 0; }

/* Engaged, not primary: the tint the language uses everywhere for "this one
   is currently on" — an active nav row, a selected chip, a set filter. */
.button-tonal {
  background: var(--color-primary-soft);
  color: var(--color-primary-text);
}

/* The neutral workhorse, and the one that moved furthest. In a language that
   separates by fill, an "outlined" button is a contradiction, so it keeps the
   name and takes the treatment the role actually calls for: a quiet filled
   box, one step up the ladder from the surface it sits on. */
.button-outlined {
  background: var(--color-surface-sunken);
  color: var(--color-on-surface);
}
.button-outlined:hover { background: var(--color-surface-hover); }

.button-text { padding-inline: var(--space-2); color: var(--color-on-surface-muted); }
.button-text:hover { color: var(--color-on-surface); }

/* The one button allowed a shadow, because being lifted off the page is the
   entire content of its name. */
.button-elevated {
  background: var(--color-surface);
  color: var(--color-on-surface);
  box-shadow: var(--shadow-1);
}

/* Intent modifiers — combine with any variant above. */
/* Each filled intent needs its own :hover. .button-filled:hover sets the
   accent explicitly now rather than stacking a wash, and that rule outranks
   a plain .button-danger.button-filled — so without these a hovered delete
   button turned blue.

   Every one of them now names a real -hover step, the way the primary button
   names --color-primary. Reusing the base hue as its own hover value, which
   is what these rules did before, is the same thing as having no hover: the
   button sat still under the pointer and the only thing that moved was an 8%
   wash nobody could see. The press shade on .button:active::before covers the
   other half of the problem. */
.button-secondary { color: var(--color-secondary-strong); }
.button-secondary.button-filled { background: var(--color-secondary); color: var(--color-on-secondary); }
.button-secondary.button-filled:hover { background: var(--color-secondary-hover); }

.button-danger { color: var(--color-danger); }
.button-danger.button-filled { background: var(--color-danger); color: var(--color-on-danger); }
.button-danger.button-filled:hover { background: var(--color-danger-hover); }

.button-success { color: var(--color-success); }
.button-success.button-filled { background: var(--color-success); color: var(--color-on-success); }
.button-success.button-filled:hover { background: var(--color-success-hover); }

.button-warning { color: var(--color-on-warning-soft); }
.button-warning.button-filled { background: var(--color-warning); color: var(--color-on-warning); }
.button-warning.button-filled:hover { background: var(--color-warning-hover); }
.button-warning.button-tonal { background: var(--color-warning-soft); color: var(--color-on-warning-soft); }

/* The one fill that is the foreground color rather than a hue, so both of
   its steps are stated outright rather than taken from a ramp. */
.button-neutral { color: var(--color-on-surface); }
.button-neutral.button-filled { background: var(--color-on-surface); color: var(--color-surface); }
.button-neutral.button-filled:hover { background: var(--color-neutral-hover); }
/* Press drops back to the RESTING fill and lets the shade darken that, rather
   than shading the hover step.

   A pointer that is pressing is also hovering, so without these the shade
   would be cancelling the hover lift and landing more or less back on the
   resting color — a pressed danger button came out within four percent of an
   untouched one. Dropping to the base first means press is unambiguously
   below resting, and the step from hover to press is the lift and the shade
   added together, which is the largest move any state in the kit makes.

   One rule per intent, and they have to be this specific: the intent hovers
   are two-class selectors, so a single .button-filled:active would lose to
   them. */
.button-filled:active                  { background: var(--color-primary-ground); }
.button-secondary.button-filled:active { background: var(--color-secondary); }
.button-danger.button-filled:active    { background: var(--color-danger); }
.button-success.button-filled:active   { background: var(--color-success); }
.button-warning.button-filled:active   { background: var(--color-warning); }
.button-neutral.button-filled:active   { background: var(--color-on-surface); }

/* The tonal and outlined variants of an intent read as a tint of that hue,
   which is how status color is spent everywhere else in the language. */
.button-danger.button-tonal  { background: var(--color-danger-soft);  color: var(--color-danger); }
.button-success.button-tonal { background: var(--color-success-soft); color: var(--color-success); }
.button-secondary.button-tonal { background: var(--color-secondary-soft); color: var(--color-on-surface); }

/* Sizes -------------------------------------------------------------------- */
.button-sm { min-height: var(--size-control-sm); padding-inline: var(--space-3); font-size: var(--type-caption-size); }
.button-lg { min-height: var(--size-control-lg); padding-inline: var(--space-6); }
.button-block { display: flex; width: 100%; }

/* States ------------------------------------------------------------------- */
/* Disabled fades; it does not recolor. Repainting a disabled control gray
   works on a white page, but against these fills the "disabled gray" lands
   within a few percent of several enabled ones and the state disappears.
   Dropping the whole control to 55% keeps its shape and its color legible
   while putting it plainly behind everything around it. */
.button:disabled,
.button[aria-disabled="true"],
.button.is-disabled {
  cursor: default;
  box-shadow: none;
  opacity: var(--opacity-disabled);
  pointer-events: none;
}

/* A button mid-request: spinner swaps in, label stays for width stability. */
.button.is-loading { pointer-events: none; color: transparent; }
.button.is-loading::after {
  content: '';
  position: absolute;
  width: 18px; height: 18px;
  border: 2px solid rgba(var(--rgb-on-surface), .25);
  border-top-color: var(--color-primary);
  border-radius: var(--radius-circle);
  animation: spin 700ms linear infinite;
}
.button-filled.is-loading::after { border-color: rgba(255,255,255,.35); border-top-color: #fff; }

/* Icon-only button ---------------------------------------------------------- */
.button-icon {
  width: var(--size-touch);
  height: var(--size-touch);
  min-height: 0;
  padding: 0;
  border-radius: var(--radius-circle);
  color: var(--color-on-surface-muted);
  /* See the note on .button-float: a 50% radius turns any squashing into a
     visible deformation, so these never flex. */
  flex: none;
}
.button-icon .icon { font-size: var(--size-icon); }
.button-icon-sm { width: var(--size-control); height: var(--size-control); }
.button-icon-sm .icon { font-size: var(--size-icon-sm); }
.button-icon.is-active { color: var(--color-primary-text); }

/* Floating action button ----------------------------------------------------- */
/* Accent, not secondary: the secondary voice here is the neutral ramp, and a
   gray disc hovering over the page reads as a bug. It keeps a shadow — it is
   the one control that genuinely floats over scrolling content, so the
   shadow is doing real work rather than decorating — but the shadow is fixed.
   Hover moves the fill along the accent ramp like every other filled control;
   growing the shadow would be the lift this language does not do. */
.button-float {
  width: 56px; height: 56px;
  min-height: 0;
  padding: 0;
  border-radius: var(--radius-circle);
  background: var(--color-primary-ground);
  color: var(--color-on-primary);
  box-shadow: var(--shadow-4);
  /* flex: none, and it is not optional. A 50% radius is only a circle while
     the box is square, and a flex child's default is to shrink — so dropping
     one of these into any flex container whose content does not quite fit
     squashes the height, leaves the width alone and turns the circle into a
     wide oval. That is what happened to the compose button in the navigation
     rail: it was the only fixed-size shrinkable item in a column that came
     out a few pixels over its height, so it alone absorbed the overflow.
     Nothing in the markup said anything was wrong. */
  flex: none;
  aspect-ratio: 1;
}
.button-float:hover { background: var(--color-primary); }
/* Same arrangement as .button-filled: the ramp carries hover, the shared
   layer stays for focus and for the press shade, and press shades the
   resting fill rather than the hover step. */
.button-float:active { background: var(--color-primary-ground); }
.button-float:hover:not(:active)::before { opacity: 0; }
.button-float .icon { font-size: var(--size-icon); }
.button-float-sm { width: 40px; height: 40px; }
.button-float-lg { width: 96px; height: 96px; }
.button-float-lg .icon { font-size: var(--size-icon-lg); }
/* Extended variant carries a label beside the glyph. */
/* The one float that is deliberately not a circle, so it gives the ratio
   back along with the radius. */
.button-float-extended {
  width: auto;
  aspect-ratio: auto;
  height: var(--size-control-lg);
  padding-inline: var(--space-4);
  border-radius: var(--radius-pill);
  gap: var(--space-3);
}

/* Button group / segmented control -------------------------------------------- */
.button-group { display: inline-flex; vertical-align: middle; }
.button-group > .button { border-radius: 0; }
.button-group > .button:first-child { border-radius: var(--radius-sm) 0 0 var(--radius-sm); }
.button-group > .button:last-child  { border-radius: 0 var(--radius-sm) var(--radius-sm) 0; }
.button-group > .button-outlined + .button-outlined { margin-left: calc(var(--border-width) * -1); }
.button-group > .button:hover { z-index: 1; }

/* One filled block with hairlines scored across it, not a pill of outlined
   cells. The outer border is gone — the group's own fill is what bounds it —
   and the radius comes down from the capsule to the same 6px every other
   control uses, so a segmented group sits in a row of chips and selects
   without being the one rounded thing in it.

   The active cell is marked by ACCENT TEXT, not by a tinted fill. Filling it
   would make the group look like one pressed button beside some unpressed
   ones; coloring the label says "you are looking at this one", which is
   what a segmented control actually means. */
.segmented {
  display: inline-flex;
  border-radius: var(--radius-sm);
  overflow: hidden;
  background: var(--color-surface);
}
.segmented > .button {
  border-radius: 0;
  min-height: var(--size-control);
  color: var(--color-on-surface);
  border: 0;
}
.segmented > .button:hover { background: var(--color-surface-sunken); }
.segmented > .button + .button { box-shadow: inset 1px 0 0 var(--color-divider); }
.segmented > .button[aria-pressed="true"],
.segmented > .button.is-active {
  background: transparent;
  color: var(--color-primary-text);
}
/* A segmented group dropped onto a card would vanish, since .card already
   spends --color-surface. One step up puts it back. */
.card .segmented,
.demo-stage .segmented { background: var(--color-surface-sunken); }
.card .segmented > .button:hover,
.demo-stage .segmented > .button:hover { background: var(--color-surface-hover); }

/* A link styled as inline text, with an underline on interaction only. */
.link { color: var(--color-primary-text); text-decoration: none; }
.link:hover { text-decoration: underline; }
.link-muted { color: var(--color-on-surface-muted); }
.link-underline { text-decoration: underline; text-underline-offset: 2px; }
/* Link with a trailing glyph that nudges forward on hover. */
.link-arrow { display: inline-flex; align-items: center; gap: var(--space-1); }
.link-arrow .icon { transition: transform var(--duration-fast) var(--ease-standard); }
.link-arrow:hover .icon { transform: translateX(3px); }

/* Ripple — deliberately inert.

   The ink ripple is the signature gesture of the language this replaced, and
   it is the one piece of it that could not be reinterpreted: an expanding
   disc of ink spreading from the pointer says the surface is a physical
   material reacting to touch, and this language's surfaces are not material.
   Press feedback here is an instant shade under the whole control —
   .button:active::before above, 120ms, no travel, no geometry.

   The class and the keyframes stay so that any markup or script still
   referencing them resolves to nothing rather than to an error, and the
   shared script no longer injects the nodes at all (see BEHAVIORS.press).
   This rule is what guarantees the result even if an older copy of the script
   is loaded beside this stylesheet. */
.ripple { display: none !important; }
@keyframes spin { to { transform: rotate(360deg); } }

/* Toolbar ------------------------------------------------------------------ */
/* A strip of controls that behaves as one tab stop. The keyboard contract is
   roving tabindex, applied by the shared script: Tab reaches the toolbar, the
   arrow keys move within it. A .button-group is only a visual join; this is
   the thing to reach for when the strip has more than a few controls. */
.toolbar {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  padding: var(--space-1);
  background: var(--color-surface);
  border: 0;
  border-radius: var(--radius-sm);
  /* The overflow menu is anchored to the toolbar, so it needs a containing
     block, and the strip must be allowed to shrink before items are hidden. */
  position: relative;
  min-width: 0;
}
.toolbar-flush   { border-radius: 0; padding-inline: 0; background: transparent; }
.toolbar-raised  { box-shadow: var(--shadow-1); }
.toolbar-sunken  { background: var(--color-surface-sunken); }
.toolbar-wrap    { flex-wrap: wrap; }

/* Controls that belong together, so the separators land in the right places. */
.toolbar-group { display: flex; align-items: center; gap: var(--space-1); min-width: 0; }
.toolbar-separator {
  flex: none;
  align-self: stretch;
  width: var(--border-width);
  margin-inline: var(--space-1);
  background: var(--color-border);
}
/* Pushes everything after it to the far end. */
.toolbar-spacer { flex: 1 1 auto; min-width: var(--space-2); }
.toolbar-label {
  flex: none;
  font-size: var(--type-caption-size);
  letter-spacing: var(--type-caption-track);
  color: var(--color-on-surface-muted);
  padding-inline: var(--space-2);
}

/* A toolbar button is square, quiet until interacted with, and tinted rather
   than filled when it is on — a filled toggle inside a strip of ten reads as
   the primary action of the page, which it is not. */
.toolbar .button-icon { border-radius: var(--radius-sm); }
.toolbar-button {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-1);
  min-width: var(--size-control-sm);
  height: var(--size-control-sm);
  padding-inline: var(--space-2);
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--color-on-surface);
  font: inherit;
  font-size: var(--type-body-2-size);
  cursor: pointer;
  position: relative;
  overflow: hidden;
  transition: background var(--duration-fast) var(--ease-standard);
}
.toolbar-button .icon { font-size: var(--size-icon-sm); }
.toolbar-button:hover { background: rgba(var(--rgb-on-surface), var(--state-hover)); }
/* The press rule reaches here too — a toolbar button is a button. */
.toolbar-button:active { background: rgba(0, 0, 0, var(--state-press-shade)); }
.toolbar-button:focus-visible {
  outline: var(--focus-ring-width) solid var(--focus-ring-color);
  outline-offset: -1px;
}
.toolbar-button[aria-pressed="true"],
.toolbar-button[aria-checked="true"],
.toolbar-button.is-active {
  background: var(--color-primary-soft);
  color: var(--color-primary-text);
}
.toolbar-button:disabled {
  opacity: var(--opacity-disabled);
  background: transparent;
  cursor: default;
}

/* Items the script moves out of the strip when it runs out of room, plus the
   button that reveals them. Both are hidden until there is something to show,
   so a toolbar that always fits never renders an empty affordance. */
/* Scoped to .toolbar so it outranks .menu-anchor, which this element also
   carries and which is declared later in the file with its own display. */
.toolbar > .toolbar-overflow { display: none; position: relative; }
.toolbar.has-overflow > .toolbar-overflow { display: inline-flex; }
.toolbar-collapsed { display: none !important; }
.toolbar-overflow-menu { min-width: 200px; }
.toolbar-overflow-menu .menu-item[aria-pressed="true"] { color: var(--color-primary-text); }

/* Vertical variant, for a strip down the side of a canvas. */
.toolbar-vertical { flex-direction: column; align-items: stretch; width: max-content; }
.toolbar-vertical .toolbar-group { flex-direction: column; align-items: stretch; }
.toolbar-vertical .toolbar-separator {
  width: auto;
  height: var(--border-width);
  margin-inline: 0;
  margin-block: var(--space-1);
}

/* ===================================================== 08 Fields and inputs */
.field {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  min-width: 0;
}
/* The field label is an overline, not a sentence. A field's box carries no
   border in this language, so the label is doing more work than usual — it is
   the main thing marking where one field ends and the next begins — and
   setting it as small tracked caps makes it read as a tag on the box rather
   than as more prose above it. */
.field-label {
  font-size: var(--type-overline-size);
  line-height: var(--type-overline-line);
  letter-spacing: var(--type-overline-track);
  text-transform: var(--type-overline-transform);
  color: var(--color-on-surface-faint);
  font-weight: var(--weight-semi);
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  margin-bottom: var(--space-1);
}
.field-hint {
  font-size: var(--type-caption-size);
  line-height: var(--type-caption-line);
  letter-spacing: var(--type-caption-track);
  color: var(--color-on-surface-muted);
  padding-inline: var(--space-3);
}
.field-error {
  font-size: var(--type-caption-size);
  line-height: var(--type-caption-line);
  color: var(--color-danger);
  padding-inline: var(--space-3);
  display: flex;
  align-items: center;
  gap: var(--space-1);
}
.field-counter {
  font-size: var(--type-caption-size);
  color: var(--color-on-surface-muted);
  margin-left: auto;
  padding-inline: var(--space-3);
}
.field-footer { display: flex; align-items: flex-start; gap: var(--space-2); }

/* The filled box every text-ish control shares.

   This is the rule the rest of the language is most often read off, so it is
   worth stating plainly: a field has NO BORDER, in any state. It is a block
   of fill one step up from the surface behind it, and focus moves it one step
   further. Nothing appears around it, nothing changes thickness, nothing
   glows. A ring drawn around a focused field is the single commonest way this
   language gets broken, because every other kit teaches it.

   What makes it work is that the focused fill is reached by nothing else: a
   hovered field and a resting one differ, a focused one differs again, and
   the brightest of the three only ever means "the caret is here". */
.input,
.textarea,
.select {
  width: 100%;
  min-height: var(--size-control-lg);
  padding: var(--space-3) var(--space-4);
  background: var(--color-surface-sunken);
  color: var(--color-on-surface);
  border: 0;
  border-radius: var(--radius-xs);
  font-size: var(--type-body-size);
  letter-spacing: var(--type-body-track);
  transition: background-color var(--duration-fast) var(--ease-standard);
}
.input::placeholder,
.textarea::placeholder { color: var(--color-on-surface-faint); }

.input:focus,
.textarea:focus,
.select:focus,
.select:hover {
  outline: none;
  background: var(--color-surface-hover);
}

.input:disabled,
.textarea:disabled,
.select:disabled {
  opacity: var(--opacity-disabled);
  cursor: default;
}
/* Read-only is not disabled: the value is live, selectable and copyable, it
   just is not yours to change. It keeps its fill and its full contrast — the
   only thing it gives up is the focus brightening, because there is nothing
   to type into. */
.input:read-only:not(:disabled),
.input:read-only:not(:disabled):focus,
.textarea:read-only:not(:disabled),
.textarea:read-only:not(:disabled):focus { background: var(--color-surface-sunken); }

.textarea { min-height: 112px; resize: vertical; line-height: var(--type-body-line); }
.textarea-fixed { resize: none; }
/* Grows with its content; the shared script syncs the height. */
.textarea-auto { resize: none; overflow: hidden; }

/* "Filled" is now what every field is, so the modifier has nothing left to
   add. It is kept as a no-op alias so existing markup keeps working, and it
   stays in the contract because a replacement language may well need the
   distinction back. */
.input-filled,
.textarea-filled,
.select-filled { /* same as the base field */ }

/* Invalid state, driven by aria-invalid so it works without extra classes.
   With no border to recolor, the signal is a wash of the status hue through
   the fill — the same tint treatment status gets everywhere else — plus the
   message in .field-error underneath, which is the part that actually tells
   someone what to do. */
.input[aria-invalid="true"],
.textarea[aria-invalid="true"],
.select[aria-invalid="true"],
.input.is-invalid {
  background: var(--color-danger-soft);
  color: var(--color-on-surface);
}
.input[aria-invalid="true"]:focus,
.textarea[aria-invalid="true"]:focus,
.select[aria-invalid="true"]:focus,
.input.is-invalid:focus {
  /* Focus still has to be distinguishable from rest, and the tint has
     already spent the fill — so this is the one field state that draws a
     ring, and it is drawn inside the box so nothing reflows. */
  box-shadow: inset 0 0 0 var(--border-width-strong) var(--color-danger);
}
.input.is-valid { background: var(--color-success-soft); }

/* A field wrapper that hosts leading / trailing adornments. */
/* The wrapper takes the fill and the focus response; the input inside it goes
   transparent so the two read as one box rather than as a field sitting in a
   tray. focus-within is what lets the whole group answer the caret. */
.input-group {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding-inline: var(--space-4);
  background: var(--color-surface-sunken);
  border: 0;
  border-radius: var(--radius-xs);
  min-height: var(--size-control-lg);
  transition: background-color var(--duration-fast) var(--ease-standard);
}
.input-group:focus-within { background: var(--color-surface-hover); }
.input-group > .input {
  border: 0;
  box-shadow: none;
  padding-inline: 0;
  background: transparent;
  min-height: auto;
}
.input-group > .input:focus,
.input-group > .input:read-only:not(:disabled) { background: transparent; box-shadow: none; }
.input-affix {
  color: var(--color-on-surface-muted);
  font-size: var(--type-body-2-size);
  white-space: nowrap;
  flex: none;
}

/* A label that used to sit inside the box and ride up out of it on focus.
   The float is gone, and the reasoning is worth keeping: the gesture exists
   to let a label double as a placeholder, which it can only do by occupying
   the box — so an empty form is a column of boxes whose labels vanish the
   moment anyone starts filling them in, and a half-filled form is a mix of
   two different label positions. It also needs a transform and a scale on
   every field, which is more motion than this language spends on anything.

   The label simply sits above the box, as .field-label does everywhere else.
   Markup order is input-then-label, so `order` lifts it rather than absolute
   positioning: the label keeps its place in the flow, and the field keeps its
   natural height instead of reserving room for a label that may be elsewhere.

   The class is kept so existing markup renders correctly rather than
   upside-down — without this rule the label would land under its input. */
.field-float { position: relative; }
.field-float > .field-label { order: -1; }

/* Native select needs its own chevron; the native one is unstyleable. */
.select {
  appearance: none;
  -webkit-appearance: none;
  padding-right: var(--space-8);
  /* currentColor cannot reach a background-image, so the chevron is drawn in
     the one gray that reads acceptably against every fill in both themes
     rather than being themed per block. */
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%2393999f' d='M7 10l5 5 5-5z'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right var(--space-3) center;
  background-size: 22px;
  cursor: pointer;
}
.select option { background: var(--color-surface-sunken); color: var(--color-on-surface); }
.select-multiple { padding-right: var(--space-4); background-image: none; min-height: 140px; }

/* Search field — a filled box with room for a leading glyph. Not a pill: the
   capsule is reserved for the switch track and the scrollbar thumb, and a
   pill-shaped search bar beside 6px-cornered controls is the detail that
   makes a toolbar look assembled from two different kits. */
.search {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding-inline: var(--space-4);
  height: var(--size-control-lg);
  background: var(--color-surface-sunken);
  border-radius: var(--radius-xs);
  border: 0;
  transition: background-color var(--duration-fast) var(--ease-standard);
}
.search:focus-within { background: var(--color-surface-hover); }
.search > input {
  flex: 1 1 auto;
  border: 0;
  background: transparent;
  outline: none;
  min-width: 0;
}
.search > .icon { color: var(--color-on-surface-muted); }

/* File input — the native control is replaced by a styled drop target.
   No dashed outline. The dash is a convention worth losing: it is the one
   place a border is asked to mean "put something here", and a flat fill with
   a glyph and a line of copy says it at least as clearly. Dragging a file
   over it brightens the fill, the same answer every other surface gives. */
.file-drop {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  padding: var(--space-7) var(--space-5);
  border: 0;
  border-radius: var(--radius-md);
  background: var(--color-surface-sunken);
  color: var(--color-on-surface-faint);
  text-align: center;
  cursor: pointer;
  transition: background-color var(--duration-fast) var(--ease-standard),
              color var(--duration-fast) var(--ease-standard);
}
.file-drop:hover,
.file-drop.is-dragover {
  background: var(--color-surface-hover);
  color: var(--color-on-surface);
}
.file-list { display: flex; flex-direction: column; gap: var(--space-2); }
.file-item {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-3);
  border: 0;
  border-radius: var(--radius-xs);
  background: var(--color-surface-sunken);
  font-size: var(--type-body-2-size);
}

/* Range slider — the track and thumb are restyled per browser engine. */
.slider {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  height: var(--size-touch);
  background: transparent;
  cursor: pointer;
  margin: 0;
}
.slider::-webkit-slider-runnable-track {
  height: 2px;
  background: rgba(var(--rgb-primary), .24);
  border-radius: var(--radius-pill);
}
.slider::-moz-range-track {
  height: 2px;
  background: rgba(var(--rgb-primary), .24);
  border-radius: var(--radius-pill);
}
.slider::-moz-range-progress { height: 2px; background: var(--color-primary); border-radius: var(--radius-pill); }
/* A fixed thumb. The growing halo and the scale-up on press are the ripple
   again in a different shape — a disc of translucent accent blooming out of
   the control — and the thumb is already the only round accent-colored
   thing on its row. Hover brightens it one step along the ramp, which is
   what every other control does. */
.slider::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 14px; height: 14px;
  margin-top: -6px;
  border-radius: var(--radius-circle);
  background: var(--color-primary);
  border: none;
  transition: background-color var(--duration-fast) var(--ease-standard);
}
.slider::-moz-range-thumb {
  width: 14px; height: 14px;
  border-radius: var(--radius-circle);
  background: var(--color-primary);
  border: none;
  transition: background-color var(--duration-fast) var(--ease-standard);
}
.slider:hover::-webkit-slider-thumb { background: var(--color-primary-hover); }
.slider:hover::-moz-range-thumb     { background: var(--color-primary-hover); }
.slider:disabled { cursor: default; opacity: var(--opacity-disabled); }
.slider-row { display: flex; align-items: center; gap: var(--space-4); }
.slider-value {
  min-width: 3.5ch;
  text-align: right;
  font-variant-numeric: tabular-nums;
  font-size: var(--type-body-2-size);
  color: var(--color-on-surface-muted);
}

.color-input {
  width: var(--size-control-lg);
  height: var(--size-control-lg);
  padding: var(--space-1);
  border: 0;
  border-radius: var(--radius-xs);
  background: var(--color-surface-sunken);
  cursor: pointer;
}

/* A fieldset reads as a grouped block by taking a fill, like everything else
   that has to be bounded. The legend sits above the box rather than cut into
   its top edge: notching a border is only available to something that has a
   border, and the notch is a browser default worth losing anyway. */
/* --color-surface, not the field fill: a fieldset is a container for fields,
   so it has to sit one rung below the things inside it or they disappear
   into it. */
.fieldset {
  border: 0;
  border-radius: var(--radius-md);
  background: var(--color-surface);
  padding: var(--space-4);
  margin: 0;
  min-width: 0;
}
.fieldset > legend {
  float: left;
  width: 100%;
  padding: 0 0 var(--space-3);
  font-size: var(--type-overline-size);
  line-height: var(--type-overline-line);
  letter-spacing: var(--type-overline-track);
  text-transform: var(--type-overline-transform);
  font-weight: var(--weight-semi);
  color: var(--color-on-surface-faint);
}
/* A floated legend is taken out of flow, so the fields after it would ride
   up alongside. This clears them back under it. */
.fieldset > legend + * { clear: both; }
.form-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(240px, 100%), 1fr));
  gap: var(--space-5);
}
.form-actions {
  display: flex;
  gap: var(--space-3);
  justify-content: flex-end;
  padding-top: var(--space-5);
}

/* Rich text editor --------------------------------------------------------- */
/* A .toolbar above a contenteditable surface. The surface is styled from the
   same type tokens as .prose, so what is typed matches what is published. */
/* Three stacked bands of fill — toolbar, surface, footer — rather than one
   box ruled into thirds. The toolbar and footer take the brighter step so
   the writing surface between them reads as the thing you are in. */
.editor {
  display: flex;
  flex-direction: column;
  border: 0;
  border-radius: var(--radius-md);
  background: var(--color-surface);
  overflow: hidden;
  min-width: 0;
}
.editor > .toolbar {
  border: 0;
  border-radius: 0;
  flex-wrap: wrap;
  background: var(--color-surface-sunken);
}
.editor-surface {
  padding: var(--space-4);
  min-height: var(--editor-min-height, 180px);
  max-height: var(--editor-max-height, 420px);
  overflow-y: auto;
  font-size: var(--type-body-size);
  line-height: var(--type-body-line);
  letter-spacing: var(--type-body-track);
  color: var(--color-on-surface);
}
/* The caret has to be visible before anything is typed, and an editor with no
   ring of its own looks broken next to a focused .input. */
.editor-surface:focus { outline: 0; }
.editor-surface:focus-visible { outline: 0; }

/* contenteditable collapses to zero height when emptied, and an empty element
   cannot carry a placeholder attribute, so the hint is drawn from data-*. */
.editor-surface:empty::before,
.editor-surface[data-empty="true"]::before {
  content: attr(data-placeholder);
  color: var(--color-on-surface-faint);
  pointer-events: none;
}

/* Block elements inside the surface. These are deliberately narrower in scope
   than .prose: the editor only produces what its toolbar can insert. */
.editor-surface > :first-child { margin-top: 0; }
.editor-surface > :last-child { margin-bottom: 0; }
.editor-surface p { margin: 0 0 var(--space-3); }
.editor-surface h2 {
  font-size: var(--type-heading-2-size);
  line-height: var(--type-heading-2-line);
  font-weight: var(--type-heading-2-weight);
  margin: var(--space-5) 0 var(--space-2);
}
.editor-surface h3 {
  font-size: var(--type-heading-3-size);
  line-height: var(--type-heading-3-line);
  font-weight: var(--type-heading-3-weight);
  margin: var(--space-4) 0 var(--space-2);
}
.editor-surface ul,
.editor-surface ol { margin: 0 0 var(--space-3); padding-inline-start: var(--space-6); }
.editor-surface li { margin-bottom: var(--space-1); }
.editor-surface a { color: var(--color-primary-text); text-decoration: underline; }
.editor-surface blockquote {
  margin: 0 0 var(--space-3);
  padding-inline-start: var(--space-4);
  border-inline-start: var(--border-width-strong) solid var(--color-border-loud);
  color: var(--color-on-surface-muted);
}
.editor-surface pre,
.editor-surface code {
  font-family: var(--font-mono);
  font-size: var(--type-body-2-size);
}
.editor-surface pre {
  margin: 0 0 var(--space-3);
  padding: var(--space-3);
  background: var(--color-surface-sunken);
  border-radius: var(--radius-sm);
  overflow-x: auto;
}
.editor-surface :where(code):not(pre code) {
  padding: 2px 4px;
  background: var(--color-surface-sunken);
  border-radius: var(--radius-xs);
}
.editor-surface hr { border: 0; border-top: var(--border-width) solid var(--color-border); margin: var(--space-5) 0; }

.editor-footer {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-3);
  border-top: 0;
  background: var(--color-surface-sunken);
  font-size: var(--type-caption-size);
  color: var(--color-on-surface-muted);
}

/* Password strength -------------------------------------------------------- */
/* Four segments rather than one bar: a continuous bar invites reading the
   exact fill as a score, which no strength estimate deserves. */
.strength { display: flex; flex-direction: column; gap: var(--space-1); }
.strength-track {
  display: flex;
  gap: var(--space-1);
  /* Height is set here so the row keeps its space before anything is typed
     and the field below does not jump when the meter appears. */
  height: 4px;
}
.strength-segment {
  flex: 1;
  border-radius: var(--radius-pill);
  background: rgba(var(--rgb-on-surface), var(--state-disabled-surface));
  transition: background var(--duration-fast) var(--ease-standard);
}
.strength[data-score="1"] .strength-segment.is-on { background: var(--color-danger); }
.strength[data-score="2"] .strength-segment.is-on { background: var(--color-warning); }
.strength[data-score="3"] .strength-segment.is-on { background: var(--color-info); }
.strength[data-score="4"] .strength-segment.is-on { background: var(--color-success); }

.strength-row { display: flex; justify-content: space-between; gap: var(--space-3); }
.strength-label { font-size: var(--type-caption-size); font-weight: var(--weight-medium); }
.strength[data-score="1"] .strength-label { color: var(--color-danger); }
.strength[data-score="2"] .strength-label { color: var(--color-warning); }
.strength[data-score="3"] .strength-label { color: var(--color-info); }
.strength[data-score="4"] .strength-label { color: var(--color-success); }
.strength-hint { font-size: var(--type-caption-size); color: var(--color-on-surface-muted); }

/* The outstanding requirements, which is the part that actually helps someone
   choose a better password. A bar on its own only grades. */
.strength-rules { list-style: none; margin: var(--space-2) 0 0; padding: 0; display: grid; gap: 2px; }
.strength-rule {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--type-caption-size);
  color: var(--color-on-surface-muted);
}
.strength-rule .icon { font-size: var(--size-icon-sm); }
.strength-rule[data-met="true"] { color: var(--color-success); }

/* Formatted inputs --------------------------------------------------------- */
/* A masked field is an ordinary .input; the script reformats as it is typed.
   Monospace keeps the digits from shifting under the caret as groups form. */
.input-masked { font-family: var(--font-mono); letter-spacing: .04em; }
.input-numeric { font-variant-numeric: tabular-nums; }

/* The detected card brand, shown inside the trailing adornment slot. */
.card-brand {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: 2px var(--space-2);
  border-radius: var(--radius-xs);
  background: var(--color-surface-sunken);
  font-size: var(--type-caption-size);
  font-weight: var(--weight-medium);
  letter-spacing: 0;
  color: var(--color-on-surface-muted);
  white-space: nowrap;
}
.card-brand[data-brand]:not([data-brand=""]) { color: var(--color-on-surface); }

/* ==================================================== 09 Selection controls */
/* Checkbox and radio share a hidden native input plus a drawn indicator. */
.choice {
  display: inline-flex;
  align-items: flex-start;
  gap: var(--space-3);
  cursor: pointer;
  min-height: var(--size-control);
  user-select: none;
  position: relative;
  padding: var(--space-1) 0;
}
.choice > input {
  position: absolute;
  opacity: 0;
  width: 0; height: 0;
  margin: 0;
}
/* The box is the one component that keeps a visible stroke, because an
   unchecked checkbox has nothing else to be: it is an empty 18px square, and
   a fill alone cannot distinguish "empty box" from "small gap". */
.choice-box {
  position: relative;
  flex: none;
  width: 18px; height: 18px;
  margin-top: 3px;
  border: var(--border-width-strong) solid var(--color-on-surface-faint);
  border-radius: var(--radius-xs);
  background: transparent;
  transition: background-color var(--duration-fast) var(--ease-standard),
              border-color var(--duration-fast) var(--ease-standard);
}
/* No 40px halo. It is the ripple's resting state — a translucent disc twice
   the size of the control it belongs to — and on a row of six checkboxes the
   haloes overlap their neighbours' labels. Hover brightens the stroke
   instead, which is legible at any density, and focus is already carried by
   the shared ring. */
.choice:hover .choice-box { border-color: var(--color-on-surface); }
/* Forwarded from the hidden native input, which has no box to ring. */
.choice > input:focus-visible + .choice-box {
  outline: var(--focus-ring-width) solid var(--focus-ring-color);
  outline-offset: var(--focus-ring-offset);
}

.choice-box::after {
  content: '';
  position: absolute;
  left: 4px; top: 0;
  width: 5px; height: 10px;
  border: solid var(--color-on-primary);
  border-width: 0 2px 2px 0;
  transform: rotate(45deg) scale(0);
  transition: transform var(--duration-fast) var(--ease-decelerate);
}
.choice > input:checked + .choice-box {
  background: var(--color-primary);
  border-color: var(--color-primary);
  color: var(--color-primary-text);
}
.choice > input:checked + .choice-box::after { transform: rotate(45deg) scale(1); }

/* Partially-selected parent in a checkbox tree. */
.choice > input:indeterminate + .choice-box {
  background: var(--color-primary);
  border-color: var(--color-primary);
}
.choice > input:indeterminate + .choice-box::after {
  transform: none;
  left: 3px; top: 7px;
  width: 10px; height: 0;
  border-width: 0 0 2px 0;
}
.choice:has(> input:disabled) { cursor: default; opacity: var(--opacity-disabled); }

.choice-radio .choice-box { border-radius: var(--radius-circle); }
.choice-radio .choice-box::after {
  left: 50%; top: 50%;
  width: 10px; height: 10px;
  border: 0;
  border-radius: var(--radius-circle);
  background: var(--color-primary);
  transform: translate(-50%, -50%) scale(0);
}
.choice-radio > input:checked + .choice-box { background: transparent; border-color: var(--color-primary); }
.choice-radio > input:checked + .choice-box::after { transform: translate(-50%, -50%) scale(1); }

.choice-label { line-height: var(--type-body-line); }
.choice-description {
  display: block;
  font-size: var(--type-caption-size);
  color: var(--color-on-surface-muted);
}
.choice-group { display: flex; flex-direction: column; gap: var(--space-1); }
.choice-group-row { flex-direction: row; flex-wrap: wrap; gap: var(--space-5); }

/* Switch ------------------------------------------------------------------- */
.switch {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  cursor: pointer;
  user-select: none;
  min-height: var(--size-control);
}
/* The thumb sits INSIDE the track rather than overhanging it, so the control
   is one 32×18 capsule instead of a bar with a disc riding on top. That is
   what lets it drop into a 36px row of chips and selects without being the
   one thing that overflows its line box, and it is why the thumb needs no
   shadow: it is contained, so it does not have to be lifted to read.

   On is a solid accent track with a white thumb, which is the only pairing
   that stays unambiguous at this size — a 50%-opacity track reads as a
   disabled control rather than an engaged one. */
.switch > input { position: absolute; opacity: 0; width: 0; height: 0; }
.switch-track {
  position: relative;
  flex: none;
  width: 32px; height: 18px;
  border-radius: var(--radius-pill);
  background: var(--color-surface-hover);
  transition: background-color var(--duration-fast) var(--ease-standard);
}
.switch-thumb {
  position: absolute;
  top: 2px; left: 2px;
  width: 14px; height: 14px;
  border-radius: var(--radius-circle);
  background: var(--color-on-surface-muted);
  transition: transform var(--duration-fast) var(--ease-standard),
              background-color var(--duration-fast) var(--ease-standard);
}
.switch:hover .switch-track { background: var(--color-border-loud); }
.switch > input:checked ~ .switch-track { background: var(--color-primary); }
.switch:hover > input:checked ~ .switch-track { background: var(--color-primary-hover); }
.switch > input:checked ~ .switch-track .switch-thumb {
  transform: translateX(14px);
  background: #fff;
}
.switch:has(> input:disabled) { cursor: default; opacity: var(--opacity-disabled); }
/* The native input is 0×0 and transparent, so the shared :focus-visible ring
   would be drawn around nothing. It has to be forwarded to the part that is
   actually on screen — the same reason .choice does it below. */
.switch > input:focus-visible ~ .switch-track {
  outline: var(--focus-ring-width) solid var(--focus-ring-color);
  outline-offset: var(--focus-ring-offset);
}

/* Toggle chips used as a multi-select filter row. */
.choice-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  height: var(--size-control);
  padding-inline: var(--space-3);
  border-radius: var(--radius-xs);
  border: 0;
  background: var(--color-surface-sunken);
  color: var(--color-on-surface-muted);
  font-size: var(--type-body-2-size);
  font-weight: var(--weight-medium);
  cursor: pointer;
  position: relative;
  overflow: hidden;
  transition: background-color var(--duration-fast) var(--ease-standard),
              color var(--duration-fast) var(--ease-standard);
}
.choice-chip > input { position: absolute; opacity: 0; }
.choice-chip:hover { background: var(--color-surface-hover); color: var(--color-on-surface); }
/* Selected is the accent tint and a full-strength label — the same pair the
   drawer uses for its current page and the filter row uses for a set filter.
   One treatment for "this one is on", wherever it appears. */
.choice-chip:has(> input:checked) {
  background: var(--color-primary-soft);
  color: var(--color-on-surface);
}
.choice-chip:has(> input:focus-visible) { outline: var(--focus-ring-width) solid var(--focus-ring-color); outline-offset: 2px; }

/* A selectable card — a radio or checkbox wearing a whole surface. */
.choice-card {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  padding: var(--space-4);
  border: 0;
  border-radius: var(--radius-md);
  cursor: pointer;
  background: var(--color-surface-sunken);
  transition: background-color var(--duration-fast) var(--ease-standard);
}
.choice-card:hover { background: var(--color-surface-hover); }
/* Tint, no ring. The radio or checkbox inside the card is already showing
   the state in accent; outlining the card as well says it twice. */
.choice-card:has(input:checked) { background: var(--color-primary-soft); }

/* ============================================================= 10 Navigation */
/* Top app bar ---------------------------------------------------------------- */
.topbar {
  position: sticky;
  top: 0;
  z-index: var(--layer-topbar);
  display: flex;
  align-items: center;
  gap: var(--space-3);
  height: var(--size-topbar);
  padding-inline: var(--size-gutter);
  /* The bar is the page. Not an accent band, not a raised surface — the same
     fill as the background it sits on, so the chrome reads as the edge of the
     window rather than as a colored strip laid across the top of it. A
     saturated bar spends the one accent on something that is never the
     subject of the page, and then the one genuinely important button in the
     bar has no color left to distinguish it.

     Nothing separates it from the content either, until there IS content
     passing underneath — see .topbar::after. */
  background: var(--color-background);
  color: var(--color-on-background);
  box-shadow: none;
  transition: background-color var(--duration-base) var(--ease-standard);
}
.topbar .button-icon { color: var(--color-on-surface-muted); }
.topbar .button-icon:hover { color: var(--color-on-surface); }

/* The separation is a soft falloff beneath the bar rather than a line or a
   box-shadow, and it is absent until the page has actually scrolled: a page
   resting at the top has nothing underneath the bar to cast a shadow, so
   drawing one says the bar is floating when it is not. The stops approximate
   a gaussian rather than a linear fade, which is what keeps it reading as
   depth instead of as a gray stripe. */
.topbar::after {
  content: '';
  position: absolute;
  left: 0; right: 0; top: 100%;
  height: 20px;
  pointer-events: none;
  background: linear-gradient(to bottom,
    rgba(0,0,0,.42) 0%,
    rgba(0,0,0,.22) 20%,
    rgba(0,0,0,.08) 45%,
    rgba(0,0,0,0) 100%);
  opacity: 0;
  transition: opacity var(--duration-slower) var(--ease-standard);
}
.topbar.is-stuck::after { opacity: 1; }
/* On a light ground the same black falloff reads as dirt. Halved, and tinted
   towards the page rather than towards pure black. */
:root[data-theme="light"] .topbar::after { opacity: 0; }
:root[data-theme="light"] .topbar.is-stuck::after { opacity: .5; }
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) .topbar.is-stuck::after { opacity: .5; }
}
.topbar-compact { height: var(--size-topbar-compact); }
/* A bar shown inline as part of the content rather than stuck to the viewport.
   z-index applies to flex and grid items even when position is static, so the
   layer has to be cleared too, or the bar paints over the page's own chrome.
   position stays relative rather than going static: the falloff below the bar
   is an absolutely positioned ::after, and with no positioned ancestor it
   would escape to the initial containing block and paint across the page. */
.topbar-inline { position: relative; z-index: auto; }
/* For a bar over content that is itself on --color-background, where the bar
   needs to be the thing that is distinct. */
.topbar-surface { background: var(--color-surface); color: var(--color-on-surface); }
/* An explicit hairline instead of the falloff, for a bar that never scrolls
   under anything — a docked panel header, a bar inside a card. */
.topbar-flat { border-bottom: var(--border-width) solid var(--color-divider); }
.topbar-flat::after { content: none; }
/* The falloff is the default behavior now rather than a variant, so this
   only has to exist for the markup that still names it. */
.topbar-dynamic { box-shadow: none; }
.topbar-title {
  font-family: var(--font-display);
  font-size: var(--type-heading-3-size);
  font-weight: var(--type-heading-3-weight);
  letter-spacing: var(--type-heading-3-track);
  margin-right: var(--space-4);
  white-space: nowrap;
}
.topbar-actions { display: flex; align-items: center; gap: var(--space-1); margin-left: auto; }

/* A bar that stretches tall and collapses as the page scrolls. */
.topbar-prominent {
  height: 128px;
  align-items: flex-start;
  padding-top: var(--space-3);
}
.topbar-prominent .topbar-title { align-self: flex-end; padding-bottom: var(--space-4); }

/* Bottom bar ------------------------------------------------------------------ */
.bottombar {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  height: var(--size-topbar-compact);
  padding-inline: var(--space-2);
  background: var(--color-surface);
  border-radius: var(--radius-lg);
}

/* Navigation drawer ------------------------------------------------------------ */
.drawer {
  position: fixed;
  top: 0; bottom: 0; left: 0;
  width: var(--size-drawer);
  background: var(--color-surface);
  color: var(--color-on-surface);
  box-shadow: var(--shadow-16);
  z-index: var(--layer-dialog);
  display: flex;
  flex-direction: column;
  transform: translateX(-101%);
  transition: transform var(--duration-slow) var(--ease-standard);
  overflow-y: auto;
  overscroll-behavior: contain;
}
.drawer[data-open="true"] { transform: translateX(0); }
.drawer-end { left: auto; right: 0; transform: translateX(101%); }
/* Permanently docked variant for wide viewports. */
/* Docked, it is no longer floating, so the shadow goes and the fill does the
   separating — the same trade the rest of the language makes. */
.drawer-docked {
  position: sticky;
  z-index: var(--layer-base);
  transform: none;
  box-shadow: none;
  height: 100vh;
  top: 0;
}
/* The header measures from the same two values the top bar does, so the menu
   button and the brand land in exactly the same place in both. Without that,
   opening the drawer nudges the logo sideways and the drawer reads as
   replacing the bar rather than as opening underneath it. */
.drawer-header {
  padding-inline: var(--size-gutter);
  height: var(--size-topbar);
  flex: none;
  display: flex;
  align-items: center;
  gap: var(--space-3);
}
.drawer-body { padding: var(--space-2); flex: 1 1 auto; }
.drawer-footer { padding: var(--space-4) var(--size-gutter); color: var(--color-on-surface-faint); }
.drawer-section-title {
  padding: var(--space-4) var(--space-4) var(--space-2);
  font-size: var(--type-overline-size);
  letter-spacing: var(--type-overline-track);
  text-transform: var(--type-overline-transform);
  color: var(--color-on-surface-muted);
  font-weight: var(--weight-medium);
}

/* One row in a drawer or sidebar. */
.nav-item {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  height: var(--size-touch);
  padding-inline: var(--space-3);
  border-radius: var(--radius-sm);
  color: var(--color-on-surface-muted);
  font-size: var(--type-body-2-size);
  font-weight: var(--weight-medium);
  text-decoration: none;
  cursor: pointer;
  position: relative;
  overflow: hidden;
  transition: background-color var(--duration-fast) var(--ease-standard),
              color var(--duration-fast) var(--ease-standard);
}
.nav-item:hover { background: var(--color-surface-sunken); color: var(--color-on-surface); text-decoration: none; }
/* Tint behind, full-strength label, accent glyph. The label goes to full
   contrast rather than to the accent: the row is where you ARE, and a page
   you are already on should not look like a link to somewhere else. The
   accent is carried by the icon alone, which is enough to find at a glance
   down a column of twelve. */
.nav-item.is-active,
.nav-item[aria-current="page"] {
  background: var(--color-primary-soft);
  color: var(--color-on-surface);
}
.nav-item .icon { color: inherit; }
.nav-item.is-active .icon,
.nav-item[aria-current="page"] .icon { color: var(--color-primary-text); }
.nav-item-trailing { margin-left: auto; font-size: var(--type-caption-size); color: var(--color-on-surface-faint); }

/* Navigation rail — a narrow, always-visible column of destinations. */
.rail {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2);
  width: var(--size-rail);
  padding-block: var(--space-3);
  background: var(--color-surface);
}
.rail-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-1);
  width: 100%;
  padding-block: var(--space-2);
  color: var(--color-on-surface-muted);
  font-size: var(--type-caption-size);
  text-decoration: none;
  cursor: pointer;
  border-radius: var(--radius-md);
}
.rail-item:hover { background: rgba(var(--rgb-on-surface), var(--state-hover)); text-decoration: none; }
/* The active glyph sits in a tinted plate, not a pill: 6px is the radius
   every other active-state block in the language uses. */
.rail-item.is-active { color: var(--color-primary-text); }
.rail-item.is-active .icon {
  background: var(--color-primary-soft);
  border-radius: var(--radius-sm);
  padding: var(--space-1) var(--space-4);
  width: auto;
}

/* Bottom navigation — three to five top-level destinations on small screens. */
.bottom-nav {
  display: flex;
  background: var(--color-surface);
  border-radius: var(--radius-lg);
  overflow: hidden;
}
.bottom-nav-item {
  flex: 1 1 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-1);
  min-height: 56px;
  padding: var(--space-2);
  color: var(--color-on-surface-muted);
  font-size: var(--type-caption-size);
  cursor: pointer;
  border: 0;
  background: transparent;
  text-decoration: none;
  position: relative;
  overflow: hidden;
  transition: color var(--duration-fast) var(--ease-standard);
}
.bottom-nav-item:hover { background: rgba(var(--rgb-on-surface), var(--state-hover)); text-decoration: none; }
.bottom-nav-item.is-active { color: var(--color-primary-text); }

/* Tabs --------------------------------------------------------------------- */
.tabs {
  display: flex;
  position: relative;
  border-bottom: var(--border-width) solid var(--color-border);
  overflow-x: auto;
  scrollbar-width: none;
}
.tabs::-webkit-scrollbar { display: none; }
.tab {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-height: var(--size-control-lg);
  padding-inline: var(--space-4);
  border: 0;
  background: transparent;
  color: var(--color-on-surface-muted);
  font-size: var(--type-label-size);
  font-weight: var(--type-label-weight);
  letter-spacing: var(--type-label-track);
  text-transform: var(--type-label-transform);
  white-space: nowrap;
  cursor: pointer;
  position: relative;
  overflow: hidden;
  transition: color var(--duration-fast) var(--ease-standard);
}
.tab:hover { background: rgba(var(--rgb-on-surface), var(--state-hover)); }
.tab[aria-selected="true"], .tab.is-active { color: var(--color-primary-text); }
/* The sliding indicator under the active tab; the script positions it. */
.tabs-indicator {
  position: absolute;
  bottom: 0;
  height: 2px;
  background: var(--color-primary);
  transition: transform var(--duration-slow) var(--ease-standard),
              width var(--duration-slow) var(--ease-standard);
  pointer-events: none;
}
.tabs-fill .tab { flex: 1 1 0; }
.tabs-contained { background: var(--color-surface); border-radius: var(--radius-md) var(--radius-md) 0 0; }
.tab-panel { padding-block: var(--space-5); }
.tab-panel[hidden] { display: none; }

/* Breadcrumbs ---------------------------------------------------------------- */
.breadcrumbs {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-2);
  list-style: none;
  font-size: var(--type-body-2-size);
  color: var(--color-on-surface-muted);
}
.breadcrumbs > li { display: flex; align-items: center; gap: var(--space-2); }
.breadcrumbs > li:not(:last-child)::after {
  content: 'chevron_right';
  font-family: 'Material Icons';
  font-size: var(--size-icon-sm);
  color: var(--color-on-surface-faint);
}
.breadcrumbs a { color: var(--color-on-surface-muted); }
.breadcrumbs a:hover { color: var(--color-on-surface); }
.breadcrumbs [aria-current="page"] { color: var(--color-on-surface); font-weight: var(--weight-medium); }

/* Pagination ------------------------------------------------------------------ */
.pagination { display: flex; align-items: center; gap: var(--space-1); list-style: none; flex-wrap: wrap; }
.pagination-item {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: var(--size-control);
  height: var(--size-control);
  padding-inline: var(--space-2);
  border-radius: var(--radius-sm);
  color: var(--color-on-surface-muted);
  font-size: var(--type-body-2-size);
  font-variant-numeric: tabular-nums;
  text-decoration: none;
  cursor: pointer;
  border: 0;
  background: transparent;
  transition: background-color var(--duration-fast) var(--ease-standard),
              color var(--duration-fast) var(--ease-standard);
}
.pagination-item:hover { background: rgba(var(--rgb-on-surface), var(--state-hover)); color: var(--color-on-surface); text-decoration: none; }
/* Tint, not a solid accent disc. "Page 4 of 60" is a position indicator, not
   a call to action, and filling it makes the quietest control on the page
   the brightest thing on it. */
.pagination-item.is-active {
  background: var(--color-primary-soft);
  color: var(--color-on-surface);
  font-weight: var(--weight-semi);
}
.pagination-item:disabled, .pagination-item.is-disabled {
  opacity: var(--opacity-disabled);
  pointer-events: none;
}
.pagination-ellipsis { color: var(--color-on-surface-faint); padding-inline: var(--space-1); }

/* Stepper ---------------------------------------------------------------------- */
.stepper { display: flex; align-items: center; gap: var(--space-2); overflow-x: auto; }
.stepper-vertical { flex-direction: column; align-items: stretch; gap: 0; }
.step { display: flex; align-items: center; gap: var(--space-3); flex: none; }
.step-marker {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px; height: 24px;
  border-radius: var(--radius-circle);
  background: var(--color-surface-hover);
  color: var(--color-on-surface-faint);
  font-size: var(--type-caption-size);
  font-weight: var(--weight-semi);
  flex: none;
}
/* Done and current have to be told apart, and both are "not pending", so
   they split the accent between them: a finished step is the solid darker
   step, the one you are on is the tint with an accent ring inside it. */
.step.is-complete .step-marker { background: var(--color-primary-strong); color: #fff; }
.step.is-active .step-marker {
  background: var(--color-primary-soft);
  color: var(--color-primary-text);
  box-shadow: inset 0 0 0 var(--border-width-strong) var(--color-primary-text);
}
.step.is-error .step-marker { background: var(--color-danger); color: var(--color-on-danger); box-shadow: none; }
.step-label { font-size: var(--type-body-2-size); color: var(--color-on-surface-faint); }
.step.is-complete .step-label { color: var(--color-on-surface-muted); }
.step.is-active .step-label { color: var(--color-primary-text); font-weight: var(--weight-medium); }
.step-connector {
  flex: 1 1 24px;
  min-width: 24px;
  border-top: var(--border-width) solid var(--color-border);
}
.stepper-vertical .step { align-items: flex-start; padding-bottom: var(--space-5); position: relative; }
.stepper-vertical .step:not(:last-child)::before {
  content: '';
  position: absolute;
  left: 11px; top: 28px; bottom: 4px;
  border-left: var(--border-width) solid var(--color-border);
}

/* Menu — an anchored surface of actions. */
/* Genuinely floating, so it genuinely gets a shadow — and the raised fill as
   well, because a menu that opens over a card of the same color has nothing
   but its shadow to separate it, and a shadow alone is not enough at these
   contrast levels. It opens by fading rather than by scaling up from 90%:
   the scale is the dialog's entrance gesture borrowed at small size, and on
   something this close to the pointer it reads as a wobble. */
.menu {
  position: absolute;
  min-width: 180px;
  padding: var(--space-1);
  background: var(--color-surface-raised);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-4);
  z-index: var(--layer-dialog);
  opacity: 0;
  visibility: hidden;
  transform: translateY(-4px);
  transform-origin: top left;
  transition: opacity var(--duration-fast) var(--ease-standard),
              transform var(--duration-fast) var(--ease-standard),
              visibility var(--duration-fast);
}
.menu[data-open="true"] { opacity: 1; visibility: visible; transform: none; }
/* Placement against the .menu-anchor, so a menu does not need an inline
   style to sit under its trigger. Logical insets, so both mirror. */
.menu-start { top: calc(100% + var(--space-1)); inset-inline-start: 0; }
.menu-end   { top: calc(100% + var(--space-1)); inset-inline-end: 0; transform-origin: top right; }
.menu-above { bottom: calc(100% + var(--space-1)); top: auto; inset-inline-start: 0; transform-origin: bottom left; }
[dir="rtl"] .menu       { transform-origin: top right; }
[dir="rtl"] .menu-end   { transform-origin: top left; }
[dir="rtl"] .menu-above { transform-origin: bottom right; }
.menu-item {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  width: 100%;
  min-height: var(--size-control);
  padding-inline: var(--space-3);
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--color-on-surface);
  font-size: var(--type-body-2-size);
  font-weight: var(--weight-medium);
  text-align: left;
  text-decoration: none;
  cursor: pointer;
  white-space: nowrap;
  transition: background-color var(--duration-fast) var(--ease-standard);
}
/* The row lifts to the next fill rather than taking a translucent wash: the
   menu is already a raised plane, and a wash over it lands on a color that
   is not one of the four in the ladder. */
.menu-item:hover { background: var(--color-surface-hover); text-decoration: none; }
.menu-item:disabled, .menu-item.is-disabled { opacity: var(--opacity-disabled); pointer-events: none; }
.menu-item-danger { color: var(--color-danger); }
.menu-item-shortcut { margin-left: auto; color: var(--color-on-surface-faint); font-size: var(--type-caption-size); }
.menu-divider { border: 0; border-top: var(--border-width) solid var(--color-divider); margin: var(--space-1) var(--space-2); }
.menu-label {
  padding: var(--space-2) var(--space-4);
  font-size: var(--type-overline-size);
  letter-spacing: var(--type-overline-track);
  text-transform: var(--type-overline-transform);
  color: var(--color-on-surface-muted);
}
.menu-anchor { position: relative; display: inline-block; }

/* ======================================================= 11 Lists and tables */
.list {
  list-style: none;
  padding: var(--space-1);
  margin: 0;
  background: var(--color-surface);
  border-radius: var(--radius-lg);
}
.list-item {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  min-height: var(--size-control-lg);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  color: var(--color-on-surface);
  text-decoration: none;
  position: relative;
  transition: background-color var(--duration-fast) var(--ease-standard);
}
.list-item-interactive { cursor: pointer; overflow: hidden; }
.list-item-interactive:hover { background: rgba(var(--rgb-on-surface), var(--state-hover)); text-decoration: none; }
.list-item.is-selected { background: var(--color-primary-soft); }
.list-item-two-line { min-height: 72px; }
.list-item-three-line { min-height: 88px; align-items: flex-start; padding-block: var(--space-3); }
.list-item-text { flex: 1 1 auto; min-width: 0; }
.list-item-title {
  font-size: var(--type-subtitle-2-size);
  font-weight: var(--weight-medium);
  line-height: 1.4;
  letter-spacing: var(--type-subtitle-2-track);
}
.list-item-subtitle {
  font-size: var(--type-body-2-size);
  line-height: var(--type-body-2-line);
  color: var(--color-on-surface-muted);
}
.list-item-leading { flex: none; color: var(--color-on-surface-muted); }
.list-item-trailing { flex: none; margin-left: auto; color: var(--color-on-surface-muted); display: flex; align-items: center; gap: var(--space-2); }
.list-subheader {
  padding: var(--space-3) var(--space-4) var(--space-1);
  font-size: var(--type-body-2-size);
  font-weight: var(--weight-medium);
  color: var(--color-on-surface-muted);
}
/* Rows inside one surface have no gap to separate them, which is exactly the
   case a hairline is for. */
.list-divided > .list-item + .list-item { border-top: var(--border-width) solid var(--color-divider); }
/* Retired for the same reason as .card-outlined, and kept as an alias on the
   same terms. A list that needs to read as one block takes the fill its
   container hands it; the hairline was the one in this language's way. */
.list-bordered { /* same as the base list */ }

/* Table ------------------------------------------------------------------------ */
.table-wrap {
  position: relative;
  overflow-x: auto;
  background: var(--color-surface);
  border-radius: var(--radius-lg);
}
.table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--type-body-2-size);
}
.table caption {
  caption-side: top;
  text-align: left;
  padding: var(--space-4);
  font-family: var(--font-display);
  font-size: var(--type-heading-3-size);
  font-weight: var(--type-heading-3-weight);
  color: var(--color-on-surface);
}
.table th,
.table td {
  padding: var(--space-3) var(--space-4);
  text-align: left;
  border-bottom: var(--border-width) solid var(--color-divider);
  vertical-align: middle;
}
/* The header row is an overline, like every other label for a block. */
.table thead th {
  font-size: var(--type-overline-size);
  letter-spacing: var(--type-overline-track);
  text-transform: var(--type-overline-transform);
  font-weight: var(--weight-semi);
  color: var(--color-on-surface-faint);
  height: 44px;
  white-space: nowrap;
}
.table tbody tr { transition: background-color var(--duration-fast) var(--ease-standard); }
.table tbody tr:last-child td { border-bottom: 0; }
.table-hover tbody tr:hover { background: rgba(var(--rgb-on-surface), var(--state-hover)); }
.table-striped tbody tr:nth-child(even) { background: rgba(var(--rgb-on-surface), .025); }
.table-dense th, .table-dense td { padding: var(--space-2) var(--space-3); }
.table-dense thead th { height: 40px; }
.table tbody tr.is-selected { background: var(--color-primary-soft); }
.table-numeric { text-align: right; font-variant-numeric: tabular-nums; }
.table th.table-numeric, .table td.table-numeric { text-align: right; }
/* A header that stays put while the body scrolls. */
.table-sticky thead th {
  position: sticky;
  top: 0;
  background: var(--color-surface);
  z-index: 1;
}
/* Sortable column headers announce direction with a glyph. */
.table-sort {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  color: inherit;
  cursor: pointer;
}
.table-sort::after {
  content: 'arrow_upward';
  font-family: 'Material Icons';
  font-size: 16px;
  opacity: 0;
  transition: opacity var(--duration-fast), transform var(--duration-fast);
}
.table-sort:hover::after { opacity: .4; }
th[aria-sort="ascending"] .table-sort { color: var(--color-on-surface); }
th[aria-sort="ascending"] .table-sort::after { opacity: 1; }
th[aria-sort="descending"] .table-sort { color: var(--color-on-surface); }
th[aria-sort="descending"] .table-sort::after { opacity: 1; transform: rotate(180deg); }

.table-toolbar {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border-bottom: var(--border-width) solid var(--color-divider);
}
.table-footer {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--space-5);
  padding: var(--space-2) var(--space-4);
  font-size: var(--type-caption-size);
  color: var(--color-on-surface-muted);
  border-top: var(--border-width) solid var(--color-divider);
}

/* Description list rendered as aligned label / value rows. */
.detail-list { display: grid; grid-template-columns: max-content 1fr; gap: var(--space-2) var(--space-5); }
.detail-list dt { color: var(--color-on-surface-muted); font-size: var(--type-body-2-size); }
.detail-list dd { margin: 0; font-size: var(--type-body-2-size); }

/* ============================================================== 12 Feedback */
/* Dialog — built on the native element so focus trapping comes for free. */
.dialog {
  padding: 0;
  border: 0;
  max-width: min(560px, calc(100vw - var(--space-8)));
  width: 100%;
  background: var(--color-surface-raised);
  color: var(--color-on-surface);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-12);
  overflow: visible;
}
.dialog::backdrop { background: var(--color-scrim); }
.dialog[open] { animation: dialog-in var(--duration-slow) var(--ease-decelerate); }
/* A short rise and a fade. The 0.88 scale it replaces is a zoom-from-nothing
   that draws the eye to the motion rather than to the question being asked. */
@keyframes dialog-in {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: none; }
}
.dialog-sm { max-width: 400px; }
.dialog-lg { max-width: 880px; }
.dialog-full {
  max-width: none;
  width: 100vw; height: 100vh;
  max-height: 100vh;
  border-radius: 0;
}
.dialog-header {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-5) var(--space-5) var(--space-3);
}
.dialog-title {
  font-family: var(--font-display);
  font-size: var(--type-heading-3-size);
  line-height: var(--type-heading-3-line);
  font-weight: var(--weight-semi);
  letter-spacing: var(--type-heading-3-track);
  flex: 1 1 auto;
}
.dialog-body {
  padding: 0 var(--space-5) var(--space-5);
  color: var(--color-on-surface-muted);
  font-size: var(--type-body-size);
  line-height: var(--type-body-line);
  max-height: 60vh;
  overflow-y: auto;
}
.dialog-actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--space-2);
  padding: var(--space-2);
}
.dialog-divided .dialog-body { border-block: var(--border-width) solid var(--color-divider); padding-block: var(--space-4); }

/* Toast — transient confirmation, stacked bottom-left. */
.toast-region {
  position: fixed;
  left: var(--space-2);
  bottom: var(--space-2);
  z-index: var(--layer-toast);
  display: flex;
  flex-direction: column-reverse;
  gap: var(--space-2);
  pointer-events: none;
  max-width: min(560px, calc(100vw - var(--space-4)));
}
.toast {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  min-height: var(--size-control);
  padding: var(--space-3) var(--space-4);
  /* The brightest step of the ladder rather than a hard-coded charcoal, so
     the toast belongs to the theme it appears in instead of being the one
     element that stays dark when the page goes light. */
  background: var(--color-surface-hover);
  color: var(--color-on-surface);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-4);
  font-size: var(--type-body-2-size);
  pointer-events: auto;
  animation: toast-in var(--duration-base) var(--ease-standard);
}
.toast.is-leaving { animation: toast-out var(--duration-base) var(--ease-accelerate) forwards; }
.toast-message { flex: 1 1 auto; }
.toast-action {
  flex: none;
  background: none;
  border: 0;
  color: var(--color-primary-text);
  font-size: var(--type-label-size);
  font-weight: var(--type-label-weight);
  letter-spacing: var(--type-label-track);
  text-transform: var(--type-label-transform);
  cursor: pointer;
  padding: var(--space-1) var(--space-2);
  border-radius: var(--radius-sm);
}
.toast-action:hover { background: rgba(var(--rgb-on-surface), var(--state-hover)); }
/* Status is carried by the TEXT, not by repainting the whole toast. A solid
   red bar sliding in from the corner is louder than almost anything it could
   be announcing, and it leaves the message set in white on a color that was
   chosen to be a fill rather than a ground. */
.toast-success .toast-message { color: var(--color-success); }
.toast-danger  .toast-message { color: var(--color-danger); }
.toast-warning .toast-message { color: var(--color-on-warning-soft); }
@keyframes toast-in  { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
@keyframes toast-out { to { opacity: 0; transform: translateY(16px); } }

/* Tooltip — a plain title replacement that is styleable and keyboard-reachable. */
.tooltip-anchor { position: relative; display: inline-flex; }
.tooltip {
  position: absolute;
  bottom: calc(100% + var(--space-2));
  left: 50%;
  transform: translateX(-50%) translateY(2px);
  padding: var(--space-1) var(--space-2);
  background: var(--color-surface-hover);
  color: var(--color-on-surface);
  box-shadow: var(--shadow-1);
  border-radius: var(--radius-xs);
  font-size: var(--type-caption-size);
  line-height: 1.4;
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  z-index: var(--layer-tooltip);
  transition: opacity var(--duration-fast) var(--ease-standard),
              transform var(--duration-fast) var(--ease-standard);
}
.tooltip-anchor:hover > .tooltip,
.tooltip-anchor:focus-within > .tooltip { opacity: 1; transform: translateX(-50%); }
.tooltip-bottom { bottom: auto; top: calc(100% + var(--space-2)); }
.tooltip-rich { white-space: normal; width: max-content; max-width: 240px; padding: var(--space-3); }

/* Banner — a persistent, dismissible message anchored to the top of content. */
.banner {
  display: flex;
  align-items: flex-start;
  gap: var(--space-4);
  padding: var(--space-4) var(--size-gutter);
  background: var(--color-surface);
  font-size: var(--type-body-2-size);
}
.banner-text { flex: 1 1 auto; }
.banner-actions { display: flex; gap: var(--space-2); flex: none; }

/* Alert — inline, status-colored message block. */
.alert {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-sm);
  /* No colored rule down the left edge. The tint already says which status
     this is, across the whole block rather than in a 4px stripe, and the
     glyph says it again. The bar was a third statement of the same fact and
     the only one that needed a border to make it. */
  background: var(--color-info-soft);
  color: var(--color-on-surface);
  font-size: var(--type-body-2-size);
  line-height: var(--type-body-2-line);
}
.alert > .icon { color: var(--color-info); flex: none; }
.alert-title { font-weight: var(--weight-medium); display: block; }
.alert-body { flex: 1 1 auto; min-width: 0; }
.alert-success { background: var(--color-success-soft); }
.alert-success > .icon { color: var(--color-success); }
.alert-warning { background: var(--color-warning-soft); }
.alert-warning > .icon { color: var(--color-warning); }
.alert-danger  { background: var(--color-danger-soft); }
.alert-danger > .icon { color: var(--color-danger); }
.alert-neutral { background: var(--color-surface-sunken); }
.alert-neutral > .icon { color: var(--color-on-surface-muted); }

/* Progress ----------------------------------------------------------------------- */
.progress-linear {
  position: relative;
  height: 4px;
  width: 100%;
  background: var(--color-surface-hover);
  border-radius: var(--radius-pill);
  overflow: hidden;
}
.progress-bar {
  height: 100%;
  width: var(--value, 0%);
  background: var(--color-primary);
  border-radius: inherit;
  transition: width var(--duration-slow) var(--ease-standard);
}
.progress-linear.is-indeterminate .progress-bar {
  width: 40%;
  animation: progress-slide 1.6s var(--ease-standard) infinite;
}
@keyframes progress-slide {
  0%   { transform: translateX(-100%); }
  100% { transform: translateX(260%); }
}
.progress-buffer { background: var(--color-surface-sunken); }
.progress-linear-lg { height: 8px; }
.progress-success .progress-bar { background: var(--color-success); }
.progress-danger  .progress-bar { background: var(--color-danger); }

.progress-circle { display: inline-flex; transform: rotate(-90deg); }
.progress-circle circle { fill: none; stroke-width: 4; }
.progress-circle .progress-circle-track { stroke: var(--color-surface-hover); }
.progress-circle .progress-circle-bar {
  stroke: var(--color-primary);
  stroke-linecap: round;
  transition: stroke-dashoffset var(--duration-slow) var(--ease-standard);
}
/* Indeterminate spinner: fixed arc, continuous rotation. */
.spinner {
  width: var(--size, 40px);
  height: var(--size, 40px);
  border: 4px solid var(--color-surface-hover);
  border-top-color: var(--color-primary);
  border-radius: var(--radius-circle);
  animation: spin 900ms linear infinite;
}
.spinner-sm { --size: 20px; border-width: 2px; }
.spinner-lg { --size: 64px; border-width: 5px; }

/* Skeleton — static blocks, deliberately not shimmering.

   The travelling highlight is a gradient, which this language does not use,
   and it is perpetual motion in the corner of the eye for as long as the
   request takes. A page of eight skeletons shimmering out of phase is more
   distracting than the empty space it replaced. A quiet block at the fill a
   field would use says "something is coming here" perfectly well, and says
   nothing at all once it has arrived. */
.skeleton {
  background: var(--color-surface-sunken);
  border-radius: var(--radius-xs);
}
.skeleton-text { height: 1em; margin-block: .25em; }
.skeleton-text-sm { width: 60%; }
.skeleton-title { height: 1.5em; width: 45%; }
.skeleton-circle { border-radius: var(--radius-circle); width: 40px; height: 40px; }
.skeleton-block { height: 160px; border-radius: var(--radius-md); }

/* Empty state — what a list looks like before it has anything in it. */
.empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-3);
  padding: var(--space-9) var(--space-5);
  text-align: center;
  color: var(--color-on-surface-muted);
}
/* Direct child only. As a descendant selector this also caught the glyph
   inside the empty state's own call-to-action button and blew it up to 64px,
   so the icon spilled out over its label. */
.empty-state > .icon { font-size: 64px; color: var(--color-on-surface-faint); }

/* ========================================================= 13 Data display */
.chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  /* 28px and the house 6px corner, not 32px and a capsule. A chip is a
     label, and a pill-shaped one at full control height competes with the
     buttons beside it for the role of "the thing to press". */
  height: 28px;
  padding-inline: var(--space-3);
  border-radius: var(--radius-xs);
  background: var(--color-surface-sunken);
  color: var(--color-on-surface-muted);
  font-size: var(--type-body-2-size);
  font-weight: var(--weight-medium);
  white-space: nowrap;
  max-width: 100%;
  border: 0;
  transition: background-color var(--duration-fast) var(--ease-standard),
              color var(--duration-fast) var(--ease-standard);
}
/* Retired with .card-outlined, whose trade it copied. The case it existed
   for — a chip on a surface already painted in the chip's own fill — is the
   case the relative ladder solves: the chip's container re-bases the rungs,
   so the chip steps up along with everything else inside it and stays
   visible without an edge. Kept as an alias. */
.chip-outlined { /* same as the base chip */ }
.chip-primary { background: var(--color-primary-soft); color: var(--color-primary-text); }
.chip-success { background: var(--color-success-soft); color: var(--color-success); }
.chip-warning { background: var(--color-warning-soft); color: var(--color-on-warning-soft); }
.chip-danger  { background: var(--color-danger-soft);  color: var(--color-danger); }
.chip-info    { background: var(--color-info-soft);    color: var(--color-primary-text); }
.chip-clickable { cursor: pointer; position: relative; overflow: hidden; }
.chip-clickable:hover { background: var(--color-surface-hover); color: var(--color-on-surface); }
/* A plain chip on a card is the fill the card already spent, so it reads as
   bare text rather than as a control. One step up puts the box back. */
.card .chip:not([class*="chip-"]),
.demo-stage .chip:not([class*="chip-"]) { background: var(--color-surface-hover); }
.chip-avatar { width: 24px; height: 24px; border-radius: var(--radius-circle); margin-left: calc(var(--space-3) * -1 + 4px); }
/* The trailing remove affordance on an input chip. */
.chip-remove {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px; height: 16px;
  margin-right: -4px;
  border: 0;
  border-radius: var(--radius-circle);
  background: transparent;
  color: var(--color-on-surface-faint);
  cursor: pointer;
  padding: 0;
  transition: color var(--duration-fast) var(--ease-standard);
}
.chip-remove .icon { font-size: 14px; }
/* A glyph that brightens, not a gray disc that darkens. The disc is a second
   filled shape inside a filled shape, at 18px across. */
.chip-remove:hover { background: transparent; color: var(--color-on-surface); }
.chip-set { display: flex; flex-wrap: wrap; gap: var(--space-2); }

/* Badge — a count or dot pinned to the corner of its anchor. */
.badge-anchor { position: relative; display: inline-flex; }
.badge {
  position: absolute;
  top: -2px; right: -2px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 20px;
  height: 20px;
  padding-inline: 6px;
  border-radius: var(--radius-pill);
  background: var(--color-primary-ground);
  color: var(--color-on-primary);
  font-size: 11px;
  font-weight: var(--weight-semi);
  line-height: 1;
  /* A ring in the page color rather than a border: it is a cut-out keeping
     the badge legible where it overlaps the glyph behind it, not an outline
     around the badge. .badge-on-surface switches it for a badge pinned to
     something sitting on a card. */
  box-shadow: 0 0 0 2px var(--color-background);
  border: 0;
}
.badge-danger { background: var(--color-danger); color: var(--color-on-danger); }
.badge-on-surface { box-shadow: 0 0 0 2px var(--color-surface); }
.badge-dot { min-width: 10px; width: 10px; height: 10px; padding: 0; }
.badge-primary { background: var(--color-primary-ground); color: var(--color-on-primary); }
.badge-success { background: var(--color-success); color: var(--color-on-success); }
/* Standalone pill label, not anchored to anything. */
.tag {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: 3px var(--space-2);
  border-radius: var(--radius-xs);
  font-size: var(--type-overline-size);
  font-weight: var(--weight-semi);
  letter-spacing: .06em;
  text-transform: uppercase;
  background: var(--color-surface-sunken);
  color: var(--color-on-surface-faint);
}
.tag-success { background: var(--color-success-soft); color: var(--color-success); }
.tag-danger  { background: var(--color-danger-soft);  color: var(--color-danger); }
.tag-warning { background: var(--color-warning-soft); color: var(--color-on-warning-soft); }
.tag-info    { background: var(--color-info-soft);    color: var(--color-primary-text); }
/* A colored dot that prefixes a status word. */
.status-dot {
  width: 8px; height: 8px;
  border-radius: var(--radius-circle);
  background: var(--color-on-surface-faint);
  flex: none;
}
.status-dot-success { background: var(--color-success); }
.status-dot-danger  { background: var(--color-danger); }
.status-dot-warning { background: var(--color-warning); }
.status-dot-pulse { animation: pulse 1.8s var(--ease-standard) infinite; }
@keyframes pulse {
  0%, 100% { box-shadow: 0 0 0 0 currentColor; opacity: 1; }
  50%      { box-shadow: 0 0 0 5px transparent; opacity: .6; }
}

/* Avatar --------------------------------------------------------------------- */
/* A flat single color carrying the initials. Never a gradient, and never
   the accent tint, which is spoken for: the tint means "this one is active"
   everywhere else, and a list of six people would read as six active rows.
   The darkest accent step is quiet enough to hold white text without looking
   like a control. .avatar-2 to .avatar-5 give a set to cycle through when
   several people appear together. */
.avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px; height: 40px;
  border-radius: var(--radius-circle);
  background: var(--accent-70);
  color: #fff;
  font-family: var(--font-display);
  font-size: var(--type-body-2-size);
  font-weight: var(--weight-semi);
  overflow: hidden;
  flex: none;
  user-select: none;
}
/* Each one has to clear 4.5:1 against the white initials on it, which is
   what pulled the amber down from #c06a00 (3.2:1) and the green from
   #2e7d32. They are identity colors, so they do not change with the theme —
   a person is not a different person in light mode. */
.avatar-2 { background: #2a6e2e; }
.avatar-3 { background: #96500a; }
.avatar-4 { background: #5c4f8a; }
.avatar-5 { background: var(--neutral-50); }
.avatar img { width: 100%; height: 100%; object-fit: cover; }
.avatar-sm { width: 28px; height: 28px; font-size: var(--type-caption-size); }
.avatar-lg { width: 56px; height: 56px; font-size: var(--type-heading-3-size); }
.avatar-xl { width: 96px; height: 96px; font-size: var(--type-heading-1-size); }
.avatar-square { border-radius: var(--radius-sm); }
/* For an avatar standing in for a thing rather than a person, where a solid
   identity color would imply one. */
.avatar-tint { background: var(--color-primary-soft); color: var(--color-primary-text); }
/* Overlapping row of avatars, newest on top. */
.avatar-stack { display: flex; }
.avatar-stack > .avatar { box-shadow: 0 0 0 2px var(--color-surface); }
.avatar-stack > .avatar + .avatar { margin-left: -12px; }

/* Stat tile --------------------------------------------------------------------- */
.stat {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding: var(--space-5);
  background: var(--color-surface);
  border-radius: var(--radius-lg);
}
.stat-label {
  font-size: var(--type-overline-size);
  letter-spacing: var(--type-overline-track);
  text-transform: var(--type-overline-transform);
  font-weight: var(--weight-semi);
  color: var(--color-on-surface-faint);
}
/* The figure is the point of the tile, so it takes the display face, by the
   same rule that sets a page title in Sora and its body copy in Roboto. */
.stat-value {
  font-family: var(--font-display);
  font-size: var(--type-heading-1-size);
  line-height: 1.1;
  font-weight: var(--weight-semi);
  font-variant-numeric: tabular-nums;
}
.stat-delta { display: inline-flex; align-items: center; gap: var(--space-1); font-size: var(--type-body-2-size); }
.stat-delta-up   { color: var(--color-success); }
.stat-delta-down { color: var(--color-danger); }

/* Meter / gauge ------------------------------------------------------------------- */
.meter {
  height: 8px;
  width: 100%;
  background: var(--color-surface-hover);
  border-radius: var(--radius-pill);
  overflow: hidden;
}
.meter-fill {
  height: 100%;
  width: var(--value, 0%);
  background: var(--color-primary);
  border-radius: inherit;
  transition: width var(--duration-slow) var(--ease-standard);
}
.meter-success .meter-fill { background: var(--color-success); }
.meter-warning .meter-fill { background: var(--color-warning); }
.meter-danger  .meter-fill { background: var(--color-danger); }
/* A segmented bar where each child is one proportional slice. */
.meter-stacked { display: flex; }
.meter-stacked > span { height: 100%; }

/* Timeline ------------------------------------------------------------------------- */
.timeline { list-style: none; padding: 0; margin: 0; }
.timeline-item {
  position: relative;
  display: flex;
  gap: var(--space-4);
  padding-bottom: var(--space-6);
}
.timeline-item:last-child { padding-bottom: 0; }
.timeline-item::before {
  content: '';
  position: absolute;
  left: 11px;
  top: 26px;
  bottom: 0;
  border-left: var(--border-width-strong) solid var(--color-surface-hover);
}
.timeline-item:last-child::before { display: none; }
/* A tinted disc rather than a ring. At 24px a 2px ring is most of the mark,
   and a column of them down a timeline reads as a chain rather than as a
   series of points. */
.timeline-marker {
  flex: none;
  width: 24px; height: 24px;
  border-radius: var(--radius-circle);
  background: var(--color-primary-soft);
  border: 0;
  display: grid;
  place-items: center;
  z-index: 1;
}
.timeline-marker .icon { font-size: 14px; color: var(--color-primary-text); }
.timeline-marker-filled { background: var(--color-primary-strong); }
.timeline-marker-filled .icon { color: #fff; }
.timeline-content { flex: 1 1 auto; min-width: 0; padding-top: 1px; }
.timeline-time { font-size: var(--type-caption-size); color: var(--color-on-surface-muted); }

/* Tree view ----------------------------------------------------------------------- */
.tree, .tree ul { list-style: none; margin: 0; padding: 0; }
.tree ul { padding-left: var(--space-5); border-left: var(--border-width) solid var(--color-divider); margin-left: var(--space-3); }
.tree-item > summary,
.tree-leaf {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--size-control);
  padding-inline: var(--space-2);
  border-radius: var(--radius-sm);
  cursor: pointer;
  font-size: var(--type-body-2-size);
  list-style: none;
}
.tree-item > summary::-webkit-details-marker { display: none; }
.tree-item > summary:hover,
.tree-leaf:hover { background: rgba(var(--rgb-on-surface), var(--state-hover)); }
.tree-item > summary::before {
  content: 'chevron_right';
  font-family: 'Material Icons';
  font-size: var(--size-icon-sm);
  color: var(--color-on-surface-muted);
  transition: transform var(--duration-fast) var(--ease-standard);
}
.tree-item[open] > summary::before { transform: rotate(90deg); }
.tree-leaf { padding-left: calc(var(--space-2) + var(--size-icon-sm)); }

/* A pair of swatch chips used on the color reference page. */
.swatch {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  min-height: 92px;
  padding: var(--space-3);
  border-radius: var(--radius-sm);
  border: var(--border-width) solid var(--color-border);
  font-size: var(--type-caption-size);
  line-height: 1.5;
  overflow: hidden;
  isolation: isolate;
}
/* The captions sit on an opaque plate rather than on the color itself. A
   swatch shows whatever token it points at, so there is no single text
   color legible on all of them — and the markup should not have to guess an
   `on-` color per swatch either. A color reference whose captions are
   unreadable on four of its forty entries is not a reference. */
.swatch::after {
  content: '';
  position: absolute;
  inset-inline: 0;
  bottom: 0;
  height: 3.6em;
  background: var(--color-surface);
  z-index: -1;
}
.swatch-name,
.swatch-value { color: var(--color-on-surface); }
.swatch-name { font-weight: var(--weight-medium); }
.swatch-value { font-family: var(--font-mono); color: var(--color-on-surface-faint); font-size: 11px; }

/* Keyboard key ----------------------------------------------------------------------- */
/* The thickened bottom border is a drawn key cap, and the last piece of
   skeuomorphism in the kit. The mono face and the filled box already read as
   a key without it. */
.kbd {
  display: inline-block;
  padding: 2px 6px;
  min-width: 22px;
  text-align: center;
  font-family: var(--font-mono);
  font-size: 12px;
  line-height: 1.5;
  color: var(--color-on-surface);
  background: var(--color-surface-sunken);
  border: 0;
  border-radius: var(--radius-xs);
}

/* Donut and gauge -------------------------------------------------------------------- */
/* Radial readouts drawn with conic-gradient rather than SVG, so a slice is one
   custom property and nothing has to compute an arc path. The trade-off is
   that a conic gradient has no stroke, so these cannot be hovered slice by
   slice; where a slice needs a tooltip, use .chart-bar or real SVG.

   Every radial form shares one flaw: an angle is harder to compare than a
   length. One figure in the middle of a donut reads well; six slices of
   similar size do not. Prefer a bar chart whenever the point is comparison. */
.donut {
  --donut-size: 140px;
  --donut-thickness: 16px;
  --donut-value: 0;          /* 0–100 */
  --donut-color: var(--color-primary);
  --donut-track: var(--color-surface-hover);
  position: relative;
  flex: none;
  width: var(--donut-size);
  height: var(--donut-size);
  border-radius: var(--radius-circle);
  background: conic-gradient(
    var(--donut-color) calc(var(--donut-value) * 1%),
    var(--donut-track) 0
  );
  /* The hole. A mask leaves the middle genuinely transparent, so the donut
     sits correctly on a sunken stage or a colored band; a plain white inner
     circle would only work on white.

     The stops are calc(100% - thickness), not calc(50% - thickness): in a
     radial gradient a percentage is a fraction of the gradient ray, and
     farthest-side makes that ray the radius. 100% is therefore the outer
     edge, and subtracting the thickness from it gives the inner edge. */
  -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - var(--donut-thickness)), #000 calc(100% - var(--donut-thickness) + 1px));
          mask: radial-gradient(farthest-side, transparent calc(100% - var(--donut-thickness)), #000 calc(100% - var(--donut-thickness) + 1px));
  transition: background var(--duration-slow) var(--ease-standard);
}
.donut-sm { --donut-size: 88px;  --donut-thickness: 10px; }
.donut-lg { --donut-size: 200px; --donut-thickness: 24px; }
.donut-thin  { --donut-thickness: 8px; }
.donut-success { --donut-color: var(--color-success); }
.donut-warning { --donut-color: var(--color-warning); }
.donut-danger  { --donut-color: var(--color-danger); }
.donut-info    { --donut-color: var(--color-info); }

/* The hole is masked out of the ring, so a label cannot be a child of .donut —
   the mask would cut it too. It is positioned over the whole figure instead. */
.donut-figure { position: relative; display: inline-flex; flex: none; }
.donut-center {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  text-align: center;
  pointer-events: none;
}
.donut-value {
  font-family: var(--font-display);
  font-size: var(--type-heading-2-size);
  line-height: 1;
  font-weight: var(--weight-semi);
  font-variant-numeric: tabular-nums;
}
.donut-sm + .donut-center .donut-value { font-size: var(--type-subtitle-size); }
.donut-label {
  font-size: var(--type-caption-size);
  letter-spacing: var(--type-caption-track);
  color: var(--color-on-surface-muted);
}

/* Multi-slice variant. Each slice is a stop the markup supplies in --slices,
   because a conic gradient cannot be composed from child elements. */
.donut-multi { background: conic-gradient(var(--slices)); }

/* Gauge — a half donut, for a value inside a known range. The sweep is 180°,
   so the same --donut-value percentage covers half the turn. */
.gauge {
  --gauge-size: 160px;
  --gauge-thickness: 16px;
  --gauge-value: 0;          /* 0–100 */
  --gauge-color: var(--color-primary);
  --gauge-track: var(--color-surface-hover);
  position: relative;
  flex: none;
  width: var(--gauge-size);
  /* Half the circle plus nothing below it: the box is clipped by the mask, so
     the height has to be halved here or the figure keeps dead space. */
  height: calc(var(--gauge-size) / 2);
  overflow: hidden;
}
.gauge::before {
  content: '';
  position: absolute;
  inset: 0;
  height: var(--gauge-size);
  border-radius: var(--radius-circle);
  /* Rotated so the sweep starts at the nine o'clock position and runs over
     the top, which is the direction a dial is read in. */
  background: conic-gradient(
    from .75turn,
    var(--gauge-color) calc(var(--gauge-value) * .5%),
    var(--gauge-track) 0 50%,
    transparent 0
  );
  -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - var(--gauge-thickness)), #000 calc(100% - var(--gauge-thickness) + 1px));
          mask: radial-gradient(farthest-side, transparent calc(100% - var(--gauge-thickness)), #000 calc(100% - var(--gauge-thickness) + 1px));
  transition: background var(--duration-slow) var(--ease-standard);
}
.gauge-success::before { --gauge-color: var(--color-success); }
.gauge-warning::before { --gauge-color: var(--color-warning); }
.gauge-danger::before  { --gauge-color: var(--color-danger); }

.gauge-figure { position: relative; display: inline-flex; flex-direction: column; flex: none; }
.gauge-center {
  position: absolute;
  inset-inline: 0;
  bottom: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  pointer-events: none;
}
.gauge-value {
  font-family: var(--font-display);
  font-size: var(--type-heading-2-size);
  line-height: 1;
  font-weight: var(--weight-semi);
  font-variant-numeric: tabular-nums;
}
.gauge-scale {
  display: flex;
  justify-content: space-between;
  margin-top: var(--space-1);
  font-size: var(--type-caption-size);
  color: var(--color-on-surface-muted);
  font-variant-numeric: tabular-nums;
}

/* Shared legend for either figure. */
.chart-legend { display: flex; flex-wrap: wrap; gap: var(--space-2) var(--space-4); }
.chart-legend-item {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--type-body-2-size);
}
.chart-legend-swatch {
  width: 12px; height: 12px;
  flex: none;
  border-radius: var(--radius-xs);
  background: var(--swatch, var(--color-primary));
}
.chart-legend-value {
  color: var(--color-on-surface-muted);
  font-variant-numeric: tabular-nums;
}

/* ================================================================== 14 Media */
.media-image { display: block; width: 100%; border-radius: var(--radius-md); object-fit: cover; }
.media-rounded { border-radius: var(--radius-lg); }
.media-circle  { border-radius: var(--radius-circle); aspect-ratio: 1; }
.media-bordered { border: var(--border-width) solid var(--color-border); }

.figure { margin: 0; display: flex; flex-direction: column; gap: var(--space-2); }
.figure-caption {
  font-size: var(--type-caption-size);
  line-height: var(--type-caption-line);
  color: var(--color-on-surface-muted);
}

/* Image grid with a configurable track size. */
.gallery {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(var(--min, 160px), 100%), 1fr));
  gap: var(--space-2);
}
.gallery-item {
  position: relative;
  overflow: hidden;
  border-radius: var(--radius-sm);
  aspect-ratio: 1;
  cursor: pointer;
}
.gallery-item img { width: 100%; height: 100%; object-fit: cover; transition: transform var(--duration-slow) var(--ease-standard); }
.gallery-item:hover img { transform: scale(1.06); }
/* Caption bar that slides over the bottom of a tile. */
.gallery-caption {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  padding: var(--space-3);
  background: linear-gradient(transparent, rgba(0,0,0,.75));
  color: #fff;
  font-size: var(--type-caption-size);
}

/* Carousel — a snap scroller plus dot indicators. */
.carousel { position: relative; overflow: hidden; border-radius: var(--radius-md); }
.carousel-track {
  display: flex;
  transition: transform var(--duration-slow) var(--ease-standard);
}
.carousel-slide { flex: 0 0 100%; min-width: 0; }
.carousel-control {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  background: rgba(0,0,0,.45);
  color: #fff;
}
.carousel-control:hover { background: rgba(0,0,0,.65); }
.carousel-prev { left: var(--space-2); }
.carousel-next { right: var(--space-2); }
.carousel-dots {
  position: absolute;
  left: 0; right: 0; bottom: var(--space-3);
  display: flex;
  justify-content: center;
  gap: var(--space-2);
}
.carousel-dot {
  width: 8px; height: 8px;
  border-radius: var(--radius-circle);
  border: 0;
  padding: 0;
  background: rgba(255,255,255,.5);
  cursor: pointer;
  transition: background-color var(--duration-fast), transform var(--duration-fast);
}
.carousel-dot.is-active { background: #fff; transform: scale(1.3); }

/* A placeholder block standing in for art that is not shipped with the page. */
.placeholder {
  display: grid;
  place-items: center;
  background:
    repeating-linear-gradient(45deg,
      rgba(var(--rgb-on-surface), .04) 0 10px,
      rgba(var(--rgb-on-surface), .07) 10px 20px);
  color: var(--color-on-surface-faint);
  border-radius: var(--radius-md);
  min-height: 120px;
  font-size: var(--type-caption-size);
  letter-spacing: var(--type-overline-track);
  text-transform: var(--type-overline-transform);
}

.video-frame { border-radius: var(--radius-md); overflow: hidden; background: #000; }
.audio-player { width: 100%; }

/* Lightbox ------------------------------------------------------------------- */
/* A full-viewport image viewer. It is modal, so it gets the same contract as
   the dialog: a scrim, a focus trap, Escape to dismiss, and the page behind it
   made inert. The script adds the trap; the colors here are fixed rather than
   tokenised because the surround is a neutral backdrop for the picture, and a
   light theme must not light it up. */
.lightbox {
  position: fixed;
  inset: 0;
  z-index: var(--layer-dialog);
  display: none;
  flex-direction: column;
  /* Nearly opaque, not merely dark. At .92 the text of a light-themed page
     behind it bleeds through at eight per cent, which is enough to read and
     therefore enough to compete with the picture — the one thing the viewer
     exists to show. The remaining sliver keeps it reading as an overlay. */
  background: rgba(0, 0, 0, .97);
  /* Dark chrome regardless of theme, so the controls stay legible on it. */
  color: #fff;
  color-scheme: dark;
}
.lightbox[data-open="true"] { display: flex; }
.lightbox-header {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  flex: none;
}
.lightbox-count {
  font-size: var(--type-body-2-size);
  font-variant-numeric: tabular-nums;
  opacity: .7;
}
.lightbox-title { font-size: var(--type-subtitle-2-size); font-weight: var(--weight-medium); }
.lightbox .button-icon { color: #fff; }
.lightbox .button-icon:hover { background: rgba(255, 255, 255, .12); }
.lightbox .button-icon:focus-visible { outline-color: #fff; }

.lightbox-stage {
  flex: 1;
  min-height: 0;
  display: grid;
  /* Explicit minmax(0, 1fr) rather than an implicit auto track. A percentage
     height resolves to nothing against an auto-sized row, so max-height on
     the image below would be ignored and a tall picture would push the
     caption and the filmstrip off the bottom of the screen. */
  grid-template-rows: minmax(0, 1fr);
  grid-template-columns: minmax(0, 1fr);
  place-items: center;
  /* Room for the step buttons, which sit over the stage. */
  padding-inline: var(--space-9);
  position: relative;
}
.lightbox-image {
  max-width: 100%;
  /* min-height: 0 on the stage plus a definite row above is what keeps a tall
     image inside the viewport instead of pushing the caption off it. */
  max-height: 100%;
  object-fit: contain;
  border-radius: var(--radius-sm);
  animation: lightbox-in var(--duration-base) var(--ease-decelerate);
}
@keyframes lightbox-in {
  from { opacity: 0; transform: scale(.97); }
  to   { opacity: 1; transform: none; }
}

/* The step buttons sit over the stage so the picture keeps the full width, and
   they are large targets because this is the control people reach for most. */
.lightbox-step {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: var(--size-touch);
  height: var(--size-touch);
  display: grid;
  place-items: center;
  border: 0;
  border-radius: var(--radius-circle);
  background: rgba(255, 255, 255, .12);
  color: #fff;
  cursor: pointer;
  transition: background var(--duration-fast) var(--ease-standard);
}
.lightbox-step:hover { background: rgba(255, 255, 255, .24); }
.lightbox-step:focus-visible { outline: var(--focus-ring-width) solid #fff; outline-offset: 2px; }
.lightbox-step:disabled { opacity: .3; cursor: default; background: rgba(255, 255, 255, .12); }
.lightbox-prev { inset-inline-start: var(--space-3); }
.lightbox-next { inset-inline-end: var(--space-3); }
/* In a right-to-left document the arrows still point the way the collection
   advances, so the glyphs are mirrored with the layout. */
[dir="rtl"] .lightbox-step .icon { transform: scaleX(-1); }

.lightbox-footer {
  flex: none;
  padding: var(--space-3) var(--space-4) var(--space-5);
  text-align: center;
}
.lightbox-caption {
  font-size: var(--type-body-2-size);
  max-width: var(--size-readable);
  margin-inline: auto;
  opacity: .85;
}

/* Filmstrip, for jumping rather than stepping. */
.lightbox-thumbs {
  display: flex;
  gap: var(--space-2);
  justify-content: center;
  flex-wrap: wrap;
  margin-top: var(--space-3);
}
.lightbox-thumb {
  width: 56px;
  height: 40px;
  padding: 0;
  border: var(--border-width-strong) solid transparent;
  border-radius: var(--radius-xs);
  overflow: hidden;
  background: rgba(255, 255, 255, .1);
  cursor: pointer;
  opacity: .55;
  transition: opacity var(--duration-fast) var(--ease-standard);
}
.lightbox-thumb img { width: 100%; height: 100%; object-fit: cover; }
.lightbox-thumb:hover { opacity: .85; }
.lightbox-thumb[aria-current="true"] { opacity: 1; border-color: #fff; }
.lightbox-thumb:focus-visible { outline: var(--focus-ring-width) solid #fff; outline-offset: 2px; }

/* A gallery item that opens the viewer gets a cursor and a hover hint, so it
   is distinguishable from one that is only a picture. */
.gallery-item[data-lightbox-item] { cursor: zoom-in; }
.gallery-item[data-lightbox-item]::after {
  content: 'zoom_in';
  font-family: 'Material Icons';
  font-size: var(--size-icon-sm);
  position: absolute;
  inset-block-start: var(--space-2);
  inset-inline-end: var(--space-2);
  width: 28px; height: 28px;
  display: grid;
  place-items: center;
  border-radius: var(--radius-circle);
  background: rgba(0, 0, 0, .55);
  color: #fff;
  opacity: 0;
  transition: opacity var(--duration-fast) var(--ease-standard);
}
.gallery-item[data-lightbox-item]:hover::after,
.gallery-item[data-lightbox-item]:focus-within::after { opacity: 1; }

/* ======================================================== 15 Content blocks */
.prose { max-width: var(--size-readable); color: var(--color-on-surface); }
.prose > * + * { margin-top: var(--space-4); }
/* Headings in the display face, body in the reading face — the same split
   the .text-* scale makes, applied to raw markup. */
.prose :is(h1, h2, h3, h4) { font-family: var(--font-display); }
.prose h1 { font-size: var(--type-heading-1-size); line-height: var(--type-heading-1-line); font-weight: var(--weight-semi); margin-top: var(--space-8); }
.prose h2 { font-size: var(--type-heading-2-size); line-height: var(--type-heading-2-line); font-weight: var(--weight-semi); margin-top: var(--space-7); }
.prose h3 { font-size: var(--type-heading-3-size); line-height: var(--type-heading-3-line); font-weight: var(--weight-semi); margin-top: var(--space-6); }
.prose h4 { font-size: var(--type-subtitle-size); font-weight: var(--weight-semi); margin-top: var(--space-5); }
.prose p  { color: var(--color-on-surface-muted); }
.prose ul, .prose ol { padding-left: var(--space-5); color: var(--color-on-surface-muted); }
.prose li + li { margin-top: var(--space-2); }
.prose a { text-decoration: underline; text-underline-offset: 2px; }
.prose img { border-radius: var(--radius-md); }
.prose hr { margin-block: var(--space-7); }

/* The rule down the side is neutral, not accent. A quotation is not an
   action and not a current state, which is all the accent is for, and a page
   of three quotes would otherwise carry more accent than its one button. */
.blockquote {
  margin: 0;
  padding: var(--space-2) var(--space-5);
  border-left: var(--border-width-strong) solid var(--color-border-loud);
  color: var(--color-on-surface-muted);
  font-size: var(--type-subtitle-size);
  line-height: 1.6;
  font-style: italic;
}
.blockquote-footer {
  display: block;
  margin-top: var(--space-2);
  font-size: var(--type-caption-size);
  font-style: normal;
  color: var(--color-on-surface-faint);
}
/* A large centerd quotation used as a page break. */
.pullquote {
  margin: 0;
  padding: var(--space-5) 0;
  font-family: var(--font-display);
  font-size: var(--type-heading-2-size);
  line-height: 1.4;
  font-weight: var(--weight-semi);
  color: var(--color-on-surface);
  text-align: center;
}

.code-inline {
  font-family: var(--font-mono);
  font-size: .875em;
  padding: 2px 6px;
  border-radius: var(--radius-xs);
  background: var(--color-surface-sunken);
  color: var(--color-on-surface);
  word-break: break-word;
}
.code-block {
  position: relative;
  margin: 0;
  padding: var(--space-4);
  background: var(--color-surface-sunken);
  border: 0;
  border-radius: var(--radius-md);
  overflow-x: auto;
  font-family: var(--font-mono);
  font-size: var(--type-caption-size);
  line-height: 1.7;
  color: var(--color-on-surface);
  tab-size: 2;
}
.code-block code { font: inherit; }
/* The copy affordance sits in the corner and appears on hover. */
.code-copy {
  position: absolute;
  top: var(--space-2);
  right: var(--space-2);
  opacity: 0;
  transition: opacity var(--duration-fast) var(--ease-standard);
}
.code-block:hover .code-copy,
.code-copy:focus-visible { opacity: 1; }

.marked { background: rgba(255, 213, 79, .5); border-radius: 2px; padding: 0 2px; }
:root[data-theme="dark"] .marked { background: rgba(255, 213, 79, .28); }

.footnote-ref { font-size: .75em; vertical-align: super; line-height: 0; }
.footnotes { font-size: var(--type-caption-size); color: var(--color-on-surface-muted); }
.footnotes ol { padding-left: var(--space-5); }

.definition-list dt { font-weight: var(--weight-medium); margin-top: var(--space-3); }
.definition-list dd { margin: 0; color: var(--color-on-surface-muted); }

/* ==================================================== 16 Overlays and pickers */
/* Anchored, interactive overlays and the composite controls built on them.
   The popover is the primitive here: the combobox, the date picker and the
   time picker all reuse its box, its placement classes and its open contract. */

/* --- Popover -------------------------------------------------------------- */
/* Interactive anchored content. Unlike .tooltip it takes focus, and unlike
   .menu it is not restricted to a list of actions. */
.popover-anchor { position: relative; display: inline-block; }
/* For an anchor wrapping a full-width field rather than a button. */
.popover-anchor-block { display: block; }
.popover {
  position: absolute;
  z-index: var(--layer-dialog);
  min-width: 220px;
  max-width: min(360px, calc(100vw - var(--space-5)));
  padding: var(--space-4);
  background: var(--color-surface-raised);
  color: var(--color-on-surface);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-4);
  opacity: 0;
  visibility: hidden;
  transform: translateY(-4px);
  transform-origin: top center;
  transition: opacity var(--duration-fast) var(--ease-standard),
              transform var(--duration-fast) var(--ease-standard),
              visibility var(--duration-fast) step-end;
}
.popover[data-open="true"] {
  opacity: 1;
  visibility: visible;
  transform: none;
  transition: opacity var(--duration-base) var(--ease-decelerate),
              transform var(--duration-base) var(--ease-decelerate),
              visibility 0s;
}
.popover-lg { min-width: 320px; max-width: min(480px, calc(100vw - var(--space-5))); }
.popover-flush { padding: 0; overflow: hidden; }

/* Placement. The script swaps these when the preferred side has no room. */
.popover-bottom     { top: calc(100% + var(--space-2)); inset-inline-start: 0; }
.popover-bottom-end { top: calc(100% + var(--space-2)); inset-inline-end: 0; }
.popover-top        { bottom: calc(100% + var(--space-2)); inset-inline-start: 0; transform-origin: bottom center; }
.popover-top-end    { bottom: calc(100% + var(--space-2)); inset-inline-end: 0; transform-origin: bottom center; }
.popover-end        { inset-inline-start: calc(100% + var(--space-2)); top: 0; transform-origin: left center; }
.popover-start      { inset-inline-end: calc(100% + var(--space-2)); top: 0; transform-origin: right center; }

.popover-header {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-4) var(--space-4) var(--space-3);
  font-size: var(--type-subtitle-2-size);
  font-weight: var(--weight-medium);
}
.popover-body { padding: var(--space-4); font-size: var(--type-body-2-size); }
.popover-footer {
  display: flex;
  justify-content: flex-end;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3) var(--space-3);
  border-top: var(--border-width) solid var(--color-border);
}

/* --- Listbox -------------------------------------------------------------- */
/* The scrolling option list shared by the combobox and the time picker. */
.listbox {
  list-style: none;
  margin: 0;
  padding: var(--space-1) 0;
  max-height: 280px;
  overflow-y: auto;
  overscroll-behavior: contain;
}
.listbox-option {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-height: var(--size-control-lg);
  padding-inline: var(--space-4);
  font-size: var(--type-body-2-size);
  color: var(--color-on-surface);
  cursor: pointer;
  user-select: none;
}
.listbox-option:hover { background: rgba(var(--rgb-on-surface), var(--state-hover)); }
/* The virtual cursor driven by aria-activedescendant, which moves
   independently of both the pointer and real DOM focus. */
.listbox-option.is-active { background: rgba(var(--rgb-primary), var(--state-focus)); }
.listbox-option[aria-selected="true"] {
  background: rgba(var(--rgb-primary), var(--state-selected));
  color: var(--color-primary-text);
  font-weight: var(--weight-medium);
}
.listbox-option[aria-disabled="true"] { color: var(--color-on-surface-faint); pointer-events: none; }
.listbox-option-text { min-width: 0; }
.listbox-option-title { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.listbox-option-subtitle {
  display: block;
  font-size: var(--type-caption-size);
  color: var(--color-on-surface-muted);
}
/* Kept in the layout at all times so a row does not shift when it is picked. */
.listbox-option-check { margin-inline-start: auto; color: var(--color-primary-text); visibility: hidden; }
.listbox-option[aria-selected="true"] .listbox-option-check { visibility: visible; }
.listbox-group {
  padding: var(--space-2) var(--space-4) var(--space-1);
  font-size: var(--type-overline-size);
  letter-spacing: var(--type-overline-track);
  text-transform: var(--type-overline-transform);
  color: var(--color-on-surface-muted);
}
.listbox-empty {
  padding: var(--space-5) var(--space-4);
  text-align: center;
  font-size: var(--type-body-2-size);
  color: var(--color-on-surface-muted);
}
/* The run of characters in an option that matched what was typed. */
.listbox-option mark {
  background: transparent;
  color: var(--color-primary-text);
  font-weight: var(--weight-medium);
}

/* --- Combobox ------------------------------------------------------------- */
/* A text input that filters an attached listbox. The input keeps real focus
   throughout; the highlighted option is tracked with aria-activedescendant. */
.combobox { position: relative; display: block; }
.combobox > .input,
.combobox > .tag-input { padding-inline-end: var(--space-8); }
.combobox-toggle {
  position: absolute;
  inset-inline-end: var(--space-1);
  top: 50%;
  transform: translateY(-50%);
  color: var(--color-on-surface-muted);
}
.combobox-toggle .icon { transition: transform var(--duration-fast) var(--ease-standard); }
.combobox[data-open="true"] .combobox-toggle .icon { transform: rotate(180deg); }
.combobox-popup {
  position: absolute;
  inset-inline: 0;
  top: calc(100% + var(--space-1));
  z-index: var(--layer-dialog);
  background: var(--color-surface);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-8);
  opacity: 0;
  visibility: hidden;
  transform: translateY(-4px);
  transition: opacity var(--duration-fast) var(--ease-standard),
              transform var(--duration-fast) var(--ease-standard),
              visibility var(--duration-fast) step-end;
}
.combobox[data-open="true"] .combobox-popup {
  opacity: 1;
  visibility: visible;
  transform: none;
  transition: opacity var(--duration-base) var(--ease-decelerate),
              transform var(--duration-base) var(--ease-decelerate),
              visibility 0s;
}
/* Flipped above the field when there is no room below it. */
.combobox-popup-above { top: auto; bottom: calc(100% + var(--space-1)); }

/* --- Tag input ------------------------------------------------------------ */
/* Several values entered as removable chips inside one field box. */
.tag-input {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  width: 100%;
  min-height: var(--size-control-lg);
  padding: var(--space-2) var(--space-3);
  background: var(--color-surface-sunken);
  border: 0;
  border-radius: var(--radius-xs);
  cursor: text;
  transition: background-color var(--duration-fast) var(--ease-standard);
}
.tag-input:focus-within { background: var(--color-surface-hover); }
/* The chip the next Backspace would remove. */
.tag-input .chip.is-armed {
  background: var(--color-danger-soft);
  color: var(--color-danger);
}
.tag-input-field {
  flex: 1 1 80px;
  min-width: 80px;
  border: 0;
  outline: none;
  padding: var(--space-1) 0;
  background: transparent;
  color: var(--color-on-surface);
  font: inherit;
  font-size: var(--type-body-size);
}
.tag-input-field::placeholder { color: var(--color-on-surface-faint); }
.tag-input.is-full .tag-input-field { display: none; }
.tag-input[aria-disabled="true"] {
  background: rgba(var(--rgb-on-surface), .03);
  border-color: var(--color-border);
  pointer-events: none;
}

/* --- Calendar ------------------------------------------------------------- */
.calendar {
  width: 17.5rem;
  padding: var(--space-3);
  background: var(--color-surface);
  color: var(--color-on-surface);
}
.calendar-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  margin-bottom: var(--space-2);
}
.calendar-title {
  font-size: var(--type-subtitle-2-size);
  font-weight: var(--weight-medium);
  letter-spacing: var(--type-subtitle-2-track);
}
.calendar-nav { display: flex; gap: var(--space-1); }
.calendar-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; }
.calendar-weekday {
  text-align: center;
  padding-block: var(--space-1);
  font-size: var(--type-caption-size);
  font-weight: var(--weight-medium);
  color: var(--color-on-surface-faint);
}
.calendar-day {
  display: flex;
  align-items: center;
  justify-content: center;
  aspect-ratio: 1;
  min-width: 34px;
  border: 0;
  background: transparent;
  color: var(--color-on-surface);
  border-radius: var(--radius-circle);
  font-size: var(--type-body-2-size);
  font-family: inherit;
  font-variant-numeric: tabular-nums;
  cursor: pointer;
  transition: background-color var(--duration-fast) var(--ease-standard),
              color var(--duration-fast) var(--ease-standard);
}
.calendar-day:hover { background: rgba(var(--rgb-on-surface), var(--state-hover)); }
/* Today is marked by accent text alone; selected is the solid fill. The two
   have to be distinguishable when they are the same day, which is why today
   does not also take a tint — the pair would then differ only in saturation. */
.calendar-day.is-today {
  color: var(--color-primary-text);
  font-weight: var(--weight-semi);
}
.calendar-day.is-selected {
  background: var(--color-primary-ground);
  color: var(--color-on-primary);
  font-weight: var(--weight-semi);
}
.calendar-day.is-outside { color: var(--color-on-surface-faint); }
.calendar-day[disabled] { opacity: var(--opacity-disabled); cursor: default; }
.calendar-day[disabled]:hover { background: transparent; }
/* Range selection: square middles, rounded ends. */
.calendar-day.is-in-range { background: var(--color-primary-soft); border-radius: 0; }
.calendar-day.is-range-start { border-start-end-radius: 0; border-end-end-radius: 0; }
.calendar-day.is-range-end { border-start-start-radius: 0; border-end-start-radius: 0; }
.calendar-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  margin-top: var(--space-2);
  padding-top: var(--space-2);
  border-top: var(--border-width) solid var(--color-border);
}
/* Two months side by side, for picking a range. */
.calendar-pair { display: flex; gap: var(--space-4); }
@media (max-width: 720px) {
  .calendar-pair { flex-direction: column; }
  .calendar { width: 100%; }
}

/* --- Time picker ---------------------------------------------------------- */
.time-columns { display: flex; gap: var(--border-width); background: var(--color-border); }
.time-column {
  flex: 1 1 0;
  max-height: 208px;
  overflow-y: auto;
  overscroll-behavior: contain;
  background: var(--color-surface);
  list-style: none;
  margin: 0;
  padding: 0;
}
.time-column-head {
  position: sticky;
  top: 0;
  padding: var(--space-1);
  text-align: center;
  background: var(--color-surface);
  border-bottom: var(--border-width) solid var(--color-border);
  font-size: var(--type-overline-size);
  letter-spacing: var(--type-overline-track);
  text-transform: var(--type-overline-transform);
  color: var(--color-on-surface-muted);
}
.time-option {
  display: block;
  width: 100%;
  padding: var(--space-2);
  text-align: center;
  font-family: inherit;
  font-size: var(--type-body-2-size);
  font-variant-numeric: tabular-nums;
  border: 0;
  background: transparent;
  color: inherit;
  cursor: pointer;
}
.time-option:hover { background: rgba(var(--rgb-on-surface), var(--state-hover)); }
.time-option[aria-selected="true"] {
  background: var(--color-primary-soft);
  color: var(--color-on-surface);
  font-weight: var(--weight-semi);
}

/* --- Number stepper ------------------------------------------------------- */
/* Replaces the native spinners, which no token can reach. */
.number-input {
  display: inline-flex;
  align-items: stretch;
  border: 0;
  border-radius: var(--radius-xs);
  background: var(--color-surface-sunken);
  overflow: hidden;
  transition: background-color var(--duration-fast) var(--ease-standard);
}
.number-input:focus-within { background: var(--color-surface-hover); }
.number-input-field {
  width: 4.5rem;
  min-height: var(--size-control-lg);
  padding: var(--space-2);
  border: 0;
  border-radius: 0;
  outline: none;
  background: transparent;
  color: var(--color-on-surface);
  font: inherit;
  font-size: var(--type-body-size);
  text-align: center;
  font-variant-numeric: tabular-nums;
  appearance: textfield;
  -moz-appearance: textfield;
}
.number-input-field::-webkit-outer-spin-button,
.number-input-field::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.number-step {
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--size-control-lg);
  flex: 0 0 auto;
  border: 0;
  background: transparent;
  color: var(--color-on-surface-muted);
  cursor: pointer;
  /* A step button is pressed, never read. Without this a press that lands a
     fraction outside the glyph starts a text selection that sweeps the whole
     box and stays there until something else is clicked. */
  user-select: none;
  -webkit-user-select: none;
}
.number-step:hover { background: rgba(var(--rgb-on-surface), var(--state-hover)); color: var(--color-on-surface); }
.number-step:disabled { color: var(--color-on-surface-faint); cursor: not-allowed; background: transparent; }
.number-step-down { border-inline-end: var(--border-width) solid var(--color-border); }
.number-step-up { border-inline-start: var(--border-width) solid var(--color-border); }
.number-input-sm .number-input-field { min-height: var(--size-control); width: 3.25rem; }
.number-input-sm .number-step { width: var(--size-control); }
.number-input[aria-disabled="true"] {
  background: rgba(var(--rgb-on-surface), .03);
  border-color: var(--color-border);
  pointer-events: none;
}

/* --- Rating --------------------------------------------------------------- */
/* An interactive rating is a radio group; a reported one is plain output. */
.rating { display: inline-flex; align-items: center; gap: var(--space-1); }
.rating-star {
  position: relative;
  display: inline-flex;
  color: var(--color-on-surface-faint);
  cursor: pointer;
  transition: color var(--duration-fast) var(--ease-standard),
              transform var(--duration-fast) var(--ease-standard);
}
.rating-star input {
  position: absolute;
  inset: 0;
  opacity: 0;
  margin: 0;
  cursor: pointer;
}
.rating-star.is-on { color: var(--color-warning); }
.rating-star:hover { transform: scale(1.12); }
.rating-star input:focus-visible + .icon {
  outline: var(--focus-ring-width) solid var(--focus-ring-color);
  outline-offset: var(--focus-ring-offset);
  border-radius: var(--radius-xs);
}
.rating-value {
  margin-inline-start: var(--space-2);
  font-size: var(--type-body-2-size);
  color: var(--color-on-surface-muted);
  font-variant-numeric: tabular-nums;
}
.rating-sm .icon { font-size: var(--size-icon-sm); width: var(--size-icon-sm); height: var(--size-icon-sm); }
.rating-lg .icon { font-size: var(--size-icon-lg); width: var(--size-icon-lg); height: var(--size-icon-lg); }
.rating-readonly .rating-star,
.rating[aria-readonly="true"] .rating-star { cursor: default; }
.rating-readonly .rating-star:hover,
.rating[aria-readonly="true"] .rating-star:hover { transform: none; }
/* A fractional average, drawn by clipping a filled row over an empty one.
   The two rows have to line up exactly, so the space between stars is each
   star's own padding rather than a flex `gap`: the fill is one flex box and
   the row beneath is another, and a gap would have to be repeated identically
   in both. Equal-width star boxes also make the fill's percentage map onto
   the value — with a gap in play, 86% of the row is not 4.3 of its 5 stars. */
.rating-fraction { position: relative; display: inline-flex; gap: 0; color: var(--color-on-surface-faint); }
.rating-fraction .icon {
  /* content-box so the padding widens the box rather than eating the glyph,
     which .icon clips to 1em. */
  box-sizing: content-box;
  padding-inline: calc(var(--space-1) / 2);
}
.rating-fraction-fill {
  position: absolute;
  inset-block: 0;
  inset-inline-start: 0;
  display: inline-flex;
  gap: 0;
  overflow: hidden;
  color: var(--color-warning);
  white-space: nowrap;
  pointer-events: none;
}

/* --- One-time code ------------------------------------------------------- */
.otp { display: flex; gap: var(--space-2); max-width: 100%; }
.otp-slot {
  /* Fixed at its preferred size but allowed to shrink, so a six-box code
     still fits a narrow column instead of pushing the page sideways. */
  flex: 0 1 2.75rem;
  min-width: 0;
  width: 2.75rem;
  height: 3.25rem;
  padding: 0;
  text-align: center;
  font-size: var(--type-heading-3-size);
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  /* The field fill, not the surface fill: these sit inside an auth card that
     has already spent --color-surface, and matching it made six boxes
     disappear into the card entirely. */
  background: var(--color-surface-sunken);
  color: var(--color-on-surface);
  border: 0;
  border-radius: var(--radius-xs);
  transition: background-color var(--duration-fast) var(--ease-standard);
}
.otp-slot:focus {
  outline: none;
  background: var(--color-surface-hover);
}
.otp-slot.is-filled { background: var(--color-surface-hover); color: var(--color-on-surface); }
.otp[data-invalid="true"] .otp-slot { border-color: var(--color-danger); }
.otp-separator { align-self: center; color: var(--color-on-surface-faint); }
@media (max-width: 720px) {
  .otp-slot { width: 2.25rem; height: 2.75rem; font-size: var(--type-subtitle-size); }
}

/* --- Split button -------------------------------------------------------- */
/* A default action plus a menu of alternatives, joined into one control. */
.button-split {
  display: inline-flex;
  align-items: stretch;
  border-radius: var(--radius-sm);
  transition: box-shadow var(--duration-base) var(--ease-standard);
}
.button-split > .button:first-child { border-start-end-radius: 0; border-end-end-radius: 0; }
/* The toggle half is matched by adjacency, not :last-child — the menu it
   opens is a sibling that follows it, so the button is never the last child. */
.button-split > .button + .button {
  border-start-start-radius: 0;
  border-end-start-radius: 0;
  min-width: var(--size-control-lg);
  padding-inline: var(--space-2);
}
/* A hairline between the halves, drawn in the on-color so it reads on the
   filled, tonal and elevated treatments alike. A border rather than an inset
   shadow: box-shadow does not stack across rules, so an inset hairline would
   replace the variant's elevation outright and leave this half flat. */
.button-split > .button + .button-filled,
.button-split > .button + .button-tonal,
.button-split > .button + .button-elevated {
  border-inline-start: var(--border-width) solid rgba(var(--rgb-surface), .4);
}
.button-split > .button + .button-outlined { margin-inline-start: calc(var(--border-width) * -1); }

/* The two halves are one control, so the elevation belongs to the pair. Cast
   per half it breaks at the seam — the halves are siblings, so the second one
   paints its own shadow over the first one's edge. Moving it to the container
   gives the whole control a single continuous shadow.

   Guarded by @supports, so that without :has() the halves keep their own
   shadows: a faint seam is a better failure than a control that looks flat. */
@supports selector(:has(*)) {
  .button-split > .button,
  .button-split > .button:hover,
  .button-split > .button:active { box-shadow: none; }

  /* Only .button-elevated carries a shadow in this language, and it does not
     grow, so the group needs one rule rather than a chain of five. */
  .button-split:has(> .button-elevated) { box-shadow: var(--shadow-1); }

  /* A disabled pair sits flat, matching a disabled button on its own. */
  .button-split:has(> .button:disabled),
  .button-split:has(> .button[aria-disabled="true"]) { box-shadow: none; }
}

/* --- Date and time together ---------------------------------------------- */
/* One panel holding the calendar and the clock side by side, and dropping to
   a stack below the point where the two would start squeezing each other. */
/* Wider than a popover's default, because this one holds two panels. Without
   it the calendar is clipped by .popover-flush's overflow: hidden and the
   month name loses its first few letters with nothing to say why. */
.popover-datetime { max-width: min(520px, calc(100vw - var(--space-5))); }
.datetime-panel {
  display: flex;
  align-items: stretch;
  gap: 0;
}
.datetime-panel-date { flex: 1 1 auto; min-width: 0; }
/* Fixed, so the clock keeps the three columns legible and the calendar takes
   whatever is left rather than the two fighting over it. */
.datetime-panel-time {
  flex: 0 0 auto;
  width: 176px;
  display: flex;
  border-inline-start: var(--border-width) solid var(--color-divider);
}
.datetime-panel-time > [data-time-columns] { flex: 1 1 auto; min-width: 0; }
@media (max-width: 560px) {
  .datetime-panel { flex-direction: column; }
  .datetime-panel-time {
    width: auto;
    border-inline-start: 0;
    border-top: var(--border-width) solid var(--color-divider);
  }
}

/* --- Month picker --------------------------------------------------------- */
/* Shares the calendar's header and footer so the two panels read as the same
   control at different resolutions. */
/* Named -picker- throughout, because .month-grid and .month-day are already
   the schedule's month view further down this file. Two components that both
   want to be called "the month grid" is how a calendar cell ends up seven
   columns wide inside a picker that asked for four. */
.month-picker { padding: var(--space-2); min-width: 248px; }
.month-picker-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--space-1);
  padding: var(--space-1) var(--space-2) var(--space-2);
}
.month-picker-cell {
  min-height: var(--size-control);
  padding: var(--space-2);
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--color-on-surface);
  font: inherit;
  font-size: var(--type-body-2-size);
  font-variant-numeric: tabular-nums;
  cursor: pointer;
  transition: background-color var(--duration-fast) var(--ease-standard);
}
.month-picker-cell:hover { background: rgba(var(--rgb-on-surface), var(--state-hover)); }
.month-picker-cell:active { background: rgba(0, 0, 0, var(--state-press-shade)); }
.month-picker-cell.is-selected {
  background: var(--color-primary-ground);
  color: var(--color-on-primary);
}
.month-picker-cell:disabled {
  color: var(--color-on-surface-faint);
  background: transparent;
  cursor: not-allowed;
}

/* --- Color picker --------------------------------------------------------- */
/* The trigger is a swatch rather than a glyph: a color field's value is an
   appearance, so showing it is more use than naming the action that changes
   it. It is sized to the icon buttons it sits beside in an .input-group. */
.color-trigger {
  width: var(--size-control-lg);
  height: var(--size-control-lg);
  min-height: 0;
  padding: var(--space-2);
  border-radius: var(--radius-xs);
  flex: none;
}
.color-trigger-swatch {
  display: block;
  width: 100%;
  height: 100%;
  border-radius: calc(var(--radius-xs) - 1px);
  /* The one place in the kit that draws a hairline on a fill, and it earns
     it: the swatch can be holding any color at all, including the exact
     fill it is sitting on, and a swatch you cannot find is not a swatch.
     currentColor rather than a token so it reads on either theme. */
  box-shadow: inset 0 0 0 1px rgba(var(--rgb-on-surface), .22);
}
.color-field {
  font-family: var(--font-mono);
  font-size: var(--type-body-2-size);
  text-transform: lowercase;
}

.color-panel {
  display: grid;
  gap: var(--space-3);
  min-width: 268px;
}
.color-preview {
  display: block;
  padding: var(--space-4) var(--space-3);
  border-radius: var(--radius-xs);
  text-align: center;
  font-family: var(--font-mono);
  font-size: var(--type-body-2-size);
  /* The readout sits ON the chosen color, which is the only honest preview —
     and the script picks black or white text for it per color, because the
     chosen color is by definition not something the tokens know about. */
  box-shadow: inset 0 0 0 1px rgba(var(--rgb-on-surface), .18);
}
.color-slider-row {
  display: grid;
  grid-template-columns: 5.5rem 1fr 3rem;
  align-items: center;
  gap: var(--space-2);
}
.color-slider-label {
  font-size: var(--type-caption-size);
  color: var(--color-on-surface-muted);
}
.color-slider-readout {
  font-size: var(--type-caption-size);
  font-variant-numeric: tabular-nums;
  text-align: end;
  color: var(--color-on-surface-muted);
}
/* The tracks show what the slider would do rather than being a neutral rail:
   the hue track is the spectrum, and the other two are drawn at the current
   hue by the script. A track that does not preview its own axis makes the
   whole panel guesswork. */
.color-slider { height: var(--size-control); }
.color-slider::-webkit-slider-runnable-track {
  height: 8px;
  border-radius: var(--radius-pill);
  background: linear-gradient(to right,
    var(--color-slider-from, #000),
    var(--color-slider-mid, var(--color-slider-from, #000)),
    var(--color-slider-to, #fff));
}
.color-slider::-moz-range-track {
  height: 8px;
  border-radius: var(--radius-pill);
  background: linear-gradient(to right,
    var(--color-slider-from, #000),
    var(--color-slider-mid, var(--color-slider-from, #000)),
    var(--color-slider-to, #fff));
}
/* Firefox paints -moz-range-progress over the track, which would cover the
   gradient with a block of accent. */
.color-slider::-moz-range-progress { background: transparent; height: 8px; }
.color-slider::-webkit-slider-thumb { margin-top: -3px; }
/* Two rules, not one selector list. A browser drops an entire rule if any
   selector in its list is one it does not recognise, and no browser knows
   both of these prefixes — grouping them means neither one ever applies and
   the hue slider silently falls back to the black-to-white default. */
.color-slider-hue::-webkit-slider-runnable-track {
  background: linear-gradient(to right,
    hsl(0 90% 55%), hsl(60 90% 55%), hsl(120 90% 55%), hsl(180 90% 55%),
    hsl(240 90% 55%), hsl(300 90% 55%), hsl(360 90% 55%));
}
.color-slider-hue::-moz-range-track {
  background: linear-gradient(to right,
    hsl(0 90% 55%), hsl(60 90% 55%), hsl(120 90% 55%), hsl(180 90% 55%),
    hsl(240 90% 55%), hsl(300 90% 55%), hsl(360 90% 55%));
}

.color-panel-heading {
  margin: var(--space-1) 0 0;
  font-size: var(--type-caption-size);
  color: var(--color-on-surface-muted);
}
.color-swatches {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(32px, 1fr));
  gap: var(--space-2);
}
.color-swatch {
  aspect-ratio: 1;
  border: 0;
  border-radius: var(--radius-xs);
  cursor: pointer;
  position: relative;
  box-shadow: inset 0 0 0 1px rgba(var(--rgb-on-surface), .22);
}
/* The tick is drawn in the color the script worked out reads on this swatch,
   so selection does not vanish on a pale one or a dark one. */
.color-swatch.is-selected::after {
  content: 'check';
  font-family: 'Material Icons';
  font-size: 18px;
  line-height: 1;
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* --- File field ----------------------------------------------------------- */
/* The kit's button in front of the browser's file input. The input itself is
   still the control — still focusable, still what the keyboard operates, still
   what opens the dialog — it is only taken out of the layout. */
.file-field {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  max-width: 100%;
}
/* Not .visually-hidden: that would take the input out of the hit area as
   well, and the point is that a click anywhere on the button reaches the real
   input so the dialog opens from a genuine user gesture on it. It is laid
   over the button at zero opacity instead, which also means the focus ring
   lands in the right place. */
.file-field-input {
  position: absolute;
  inset-block: 0;
  inset-inline-start: 0;
  width: 11rem;
  max-width: 100%;
  opacity: 0;
  cursor: pointer;
}
.file-field-name {
  font-size: var(--type-body-2-size);
  color: var(--color-on-surface);
  min-width: 0;
}
.file-field-name.is-empty { color: var(--color-on-surface-faint); }
/* The real input has the focus, so the ring has to be drawn around what the
   eye thinks is the control. */
.file-field-input:focus-visible + .button {
  outline: var(--focus-ring-width) solid var(--focus-ring-color);
  outline-offset: var(--focus-ring-offset);
}

/* --- Speed dial ---------------------------------------------------------- */
/* A floating action button that fans out into related actions. */
.speed-dial {
  position: relative;
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  /* The width of the fanned-out mini buttons. The column below is positioned
     from it, so a dial built with a different action size only has to restate
     this one value. */
  --speed-dial-action-size: 40px;
}
/* The column is pinned so that the mini buttons share the trigger's vertical
   axis, and the labels run off the inline-start side from there.

   It used to be centered on the trigger with align-items: center, and that is
   not the same thing: each row is a label plus a button, the labels are
   different lengths, so centering the ROWS put every button at a different
   offset. The fan came out as a zigzag that nothing in the markup explained —
   it was being aligned, just on the wrong thing. Both properties here are
   logical, so the whole arrangement mirrors in a right-to-left layout. */
.speed-dial-actions {
  position: absolute;
  bottom: calc(100% + var(--space-3));
  inset-inline-end: calc(50% - var(--speed-dial-action-size) / 2);
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: var(--space-3);
  pointer-events: none;
}
.speed-dial-action {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  opacity: 0;
  transform: translateY(var(--space-4)) scale(.8);
  pointer-events: none;
  transition: opacity var(--duration-fast) var(--ease-standard),
              transform var(--duration-fast) var(--ease-standard);
}
.speed-dial[data-open="true"] .speed-dial-actions { pointer-events: auto; }
.speed-dial[data-open="true"] .speed-dial-action { opacity: 1; transform: none; pointer-events: auto; }
/* Staggered so the fan reads as one gesture rather than four. */
.speed-dial[data-open="true"] .speed-dial-action:nth-last-child(1) { transition-delay: 0ms; }
.speed-dial[data-open="true"] .speed-dial-action:nth-last-child(2) { transition-delay: 30ms; }
.speed-dial[data-open="true"] .speed-dial-action:nth-last-child(3) { transition-delay: 60ms; }
.speed-dial[data-open="true"] .speed-dial-action:nth-last-child(4) { transition-delay: 90ms; }
.speed-dial-label {
  order: -1;
  padding: var(--space-1) var(--space-2);
  background: var(--color-surface);
  border-radius: var(--radius-xs);
  box-shadow: var(--shadow-2);
  font-size: var(--type-caption-size);
  white-space: nowrap;
}
.speed-dial-trigger .icon { transition: transform var(--duration-base) var(--ease-standard); }
.speed-dial[data-open="true"] .speed-dial-trigger .icon { transform: rotate(45deg); }

/* --- Inline edit --------------------------------------------------------- */
/* Read mode is text, edit mode is a field in the same box, so nothing jumps. */
.inline-edit { display: inline-flex; align-items: center; gap: var(--space-2); max-width: 100%; }
.inline-edit-value {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--size-control);
  padding: var(--space-1) var(--space-2);
  border: var(--border-width) solid transparent;
  border-radius: var(--radius-sm);
  background: transparent;
  color: inherit;
  font: inherit;
  text-align: start;
  cursor: text;
  transition: background-color var(--duration-fast) var(--ease-standard);
}
.inline-edit-value:hover { background: rgba(var(--rgb-on-surface), var(--state-hover)); }
.inline-edit-value .icon {
  color: var(--color-on-surface-faint);
  opacity: 0;
  transition: opacity var(--duration-fast) var(--ease-standard);
}
.inline-edit-value:hover .icon,
.inline-edit-value:focus-visible .icon { opacity: 1; }
.inline-edit-form { display: inline-flex; align-items: center; gap: var(--space-2); }
.inline-edit-input { min-height: var(--size-control); padding: var(--space-1) var(--space-2); font: inherit; }
.inline-edit[data-editing="true"] .inline-edit-value { display: none; }
.inline-edit:not([data-editing="true"]) .inline-edit-form { display: none; }

/* --- Lifted dragging ----------------------------------------------------- */
/* Shared by every draggable thing in the kit — sortable rows, board cards,
   anything a dev hands to UI.lift. Two states and a placeholder, all of them
   component-agnostic on purpose: the mechanism is one piece of code, so it is
   one piece of CSS.

   Positioning is set inline by the script, which is the only thing that knows
   where the pointer is. Everything here is the look of being in the air. */
.is-lifted {
  z-index: var(--layer-drag);
  box-shadow: var(--shadow-12);
  cursor: grabbing;
}
/* Landing. Same elevation, so the row slides over its neighbors rather than
   under them, and the shadow outlives the slide by one transition — it is the
   row setting down, not the shadow being switched off. */
.is-settling {
  z-index: var(--layer-drag);
  box-shadow: var(--shadow-12);
}
/* Where it was picked up from. The same dashed well as an emptied board
   column, because it means the same thing: a space something belongs in. */
.drag-gap {
  box-sizing: border-box;
  flex: none;
  list-style: none;
  border: var(--border-width) dashed var(--color-border-strong);
  border-radius: var(--radius-sm);
  background: none;
  pointer-events: none;
}

/* --- Sortable list ------------------------------------------------------- */
/* Reorderable by pointer drag and, on the handle, by keyboard. */
.sortable { list-style: none; margin: 0; padding: 0; }
/* A list taller than the room it has. Auto-scroll during a drag can only be
   seen in one of these, and five rows is not where that problem lives. */
.sortable-scroll {
  max-height: 264px;
  overflow-y: auto;
  overscroll-behavior-y: contain;
  padding: var(--space-2);
  border: var(--border-width) solid var(--color-border);
  border-radius: var(--radius-md);
}
.sortable-item {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  background: var(--color-surface);
  border: var(--border-width) solid var(--color-border);
  border-radius: var(--radius-sm);
  margin-bottom: var(--space-2);
  transition: box-shadow var(--duration-fast) var(--ease-standard),
              opacity var(--duration-fast) var(--ease-standard);
}
.sortable-item:last-child { margin-bottom: 0; }
.sortable-handle {
  display: flex;
  align-items: center;
  flex: 0 0 auto;
  padding: var(--space-1);
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--color-on-surface-faint);
  cursor: grab;
  touch-action: none;
}
.sortable-handle:hover { color: var(--color-on-surface); background: rgba(var(--rgb-on-surface), var(--state-hover)); }
.sortable-handle:active { cursor: grabbing; }
/* Fading the row in place is what a drag looks like with no lift to show —
   data-drag-proxy="off". Lifted, the row is the thing you are watching, so it
   stays solid and the gap it left does the explaining. */
.sortable-item.is-dragging:not(.is-lifted) { opacity: .4; }
/* Where the dragged row would land. */
/* The drop line is the one place a 3px accent rule is the right answer: it
   marks a position BETWEEN two rows, which no fill can express. */
.sortable-item.is-over { box-shadow: inset 0 3px 0 var(--color-primary); }
.sortable-item.is-over-below { box-shadow: inset 0 -3px 0 var(--color-primary); }
/* Picked up by keyboard: the arrow keys now move it. */
.sortable-item.is-grabbed {
  box-shadow: 0 0 0 var(--border-width-strong) var(--color-primary);
  background: rgba(var(--rgb-primary), var(--state-selected));
}
.sortable-item-text { flex: 1 1 auto; min-width: 0; }
.sortable-position {
  font-size: var(--type-caption-size);
  color: var(--color-on-surface-faint);
  font-variant-numeric: tabular-nums;
}

/* --- Long lists ---------------------------------------------------------- */
.load-more { display: flex; flex-direction: column; align-items: center; gap: var(--space-2); padding-block: var(--space-5); }
.load-more-count { font-size: var(--type-caption-size); color: var(--color-on-surface-muted); }
/* The element an IntersectionObserver watches in order to append the next page. */
.list-sentinel {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-3);
  padding-block: var(--space-5);
  font-size: var(--type-body-2-size);
  color: var(--color-on-surface-muted);
}
.list-end {
  padding-block: var(--space-5);
  text-align: center;
  font-size: var(--type-caption-size);
  color: var(--color-on-surface-faint);
}

/* Windowed list: only the visible rows exist in the DOM, and a sizer stands
   in for the rest so the scrollbar still reports the full height. */
.virtual-list {
  position: relative;
  height: 320px;
  overflow-y: auto;
  overscroll-behavior: contain;
  background: var(--color-surface);
  border: var(--border-width) solid var(--color-border);
  border-radius: var(--radius-md);
}
.virtual-list-sizer { position: relative; width: 100%; }
.virtual-list-window { position: absolute; inset-inline: 0; top: 0; }
.virtual-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  height: 48px;
  padding-inline: var(--space-4);
  border-bottom: var(--border-width) solid var(--color-border);
  font-size: var(--type-body-2-size);
}
.virtual-row-index {
  min-width: 3.5rem;
  color: var(--color-on-surface-faint);
  font-variant-numeric: tabular-nums;
  font-size: var(--type-caption-size);
}

/* Command palette ------------------------------------------------------------- */
/* A modal search over everything the page can do. Structurally it is a
   combobox in a dialog: one text input that never leaves focus, and a listbox
   below it driven by aria-activedescendant, so the arrow keys move the
   selection while the typing stays where it is.

   It is an accelerator, never the only route to an action. Anything reachable
   only from here is unreachable for anyone who does not know it exists. */
.palette-scrim {
  position: fixed;
  inset: 0;
  z-index: var(--layer-dialog);
  display: none;
  background: var(--color-scrim);
  /* Anchored a third of the way down rather than centerd: the list grows
     downward, and a centerd palette jumps as results come and go. */
  padding: 10vh var(--space-4) var(--space-4);
  justify-content: center;
  align-items: flex-start;
}
.palette-scrim[data-open="true"] { display: flex; }

.palette {
  width: 100%;
  max-width: 560px;
  background: var(--color-surface-raised);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-24);
  overflow: hidden;
  display: flex;
  flex-direction: column;
  max-height: 70vh;
  animation: palette-in var(--duration-base) var(--ease-decelerate);
}
@keyframes palette-in {
  from { opacity: 0; transform: translateY(-8px) scale(.98); }
  to   { opacity: 1; transform: none; }
}

.palette-search {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border-bottom: var(--border-width) solid var(--color-border);
  flex: none;
}
.palette-search .icon { color: var(--color-on-surface-muted); flex: none; }
.palette-input {
  flex: 1;
  min-width: 0;
  border: 0;
  background: transparent;
  font: inherit;
  font-size: var(--type-subtitle-size);
  color: var(--color-on-surface);
}
.palette-input:focus { outline: 0; }
.palette-input::placeholder { color: var(--color-on-surface-faint); }

.palette-list {
  list-style: none;
  margin: 0;
  padding: var(--space-2) 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  flex: 1;
  min-height: 0;
}
.palette-group-label {
  padding: var(--space-2) var(--space-4) var(--space-1);
  font-size: var(--type-overline-size);
  line-height: var(--type-overline-line);
  letter-spacing: var(--type-overline-track);
  text-transform: var(--type-overline-transform);
  color: var(--color-on-surface-muted);
}
.palette-item {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-4);
  cursor: pointer;
  /* The pointer moves the same highlight the arrow keys do, so there is never
     a hover row and a selected row disagreeing about what Enter will run. */
  scroll-margin: var(--space-2);
}
.palette-item .icon { color: var(--color-on-surface-muted); flex: none; }
.palette-item.is-active {
  background: rgba(var(--rgb-primary), var(--state-focus));
}
.palette-item.is-active .icon,
.palette-item.is-active .palette-item-title { color: var(--color-primary-text); }
.palette-item-text { min-width: 0; flex: 1; }
.palette-item-title {
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: var(--type-body-2-size);
}
.palette-item-subtitle {
  display: block;
  font-size: var(--type-caption-size);
  color: var(--color-on-surface-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.palette-item-shortcut { display: flex; gap: var(--space-1); flex: none; }
/* The matched run of the query, marked up by the script. */
.palette-item mark {
  background: transparent;
  color: var(--color-primary-text);
  font-weight: var(--weight-medium);
}
.palette-empty {
  padding: var(--space-6) var(--space-4);
  text-align: center;
  color: var(--color-on-surface-muted);
  font-size: var(--type-body-2-size);
}
.palette-footer {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-2) var(--space-4);
  border-top: var(--border-width) solid var(--color-border);
  background: var(--color-surface-sunken);
  font-size: var(--type-caption-size);
  color: var(--color-on-surface-muted);
  flex: none;
}
.palette-footer-hint { display: inline-flex; align-items: center; gap: var(--space-1); }

/* The affordance that tells people the palette exists at all. */
.palette-trigger {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-width: 220px;
  height: var(--size-control);
  padding-inline: var(--space-3);
  border: var(--border-width) solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  color: var(--color-on-surface-muted);
  font: inherit;
  font-size: var(--type-body-2-size);
  cursor: pointer;
  text-align: start;
}
.palette-trigger:hover { background: rgba(var(--rgb-on-surface), var(--state-hover)); }
.palette-trigger .kbd { margin-inline-start: auto; }

@media (max-width: 720px) {
  .palette-scrim { padding-top: var(--space-4); }
  .palette { max-height: 85vh; }
  .palette-item-shortcut { display: none; }
}

/* =================================================== 17 Application patterns */
/* Whole-screen compositions. These assemble the components above rather than
   introducing new primitives — each one is a layout plus a few labels. */

/* --- Settings rows ------------------------------------------------------- */
.settings-group + .settings-group { margin-top: var(--space-6); }
.settings-group-title {
  font-size: var(--type-overline-size);
  letter-spacing: var(--type-overline-track);
  text-transform: var(--type-overline-transform);
  color: var(--color-on-surface-muted);
  font-weight: var(--weight-medium);
  margin-bottom: var(--space-2);
}
.settings-list {
  background: var(--color-surface);
  border: 0;
  border-radius: var(--radius-lg);
  overflow: hidden;
}
.settings-row {
  display: flex;
  align-items: center;
  gap: var(--space-5);
  padding: var(--space-4) var(--space-5);
  border-bottom: var(--border-width) solid var(--color-divider);
}
.settings-row:last-child { border-bottom: 0; }
.settings-row-text { flex: 1 1 auto; min-width: 0; }
.settings-row-title { font-size: var(--type-body-size); display: flex; align-items: center; gap: var(--space-2); }
.settings-row-description {
  font-size: var(--type-body-2-size);
  color: var(--color-on-surface-muted);
  margin-top: 2px;
  max-width: var(--size-readable);
}
.settings-row-control { flex: 0 0 auto; display: flex; align-items: center; gap: var(--space-3); }
.settings-row-danger .settings-row-title { color: var(--color-danger); }
/* On a narrow screen the control drops below its label instead of squeezing. */
@media (max-width: 720px) {
  .settings-row { flex-direction: column; align-items: stretch; gap: var(--space-3); }
  .settings-row-control { justify-content: flex-start; }
}

/* --- Dashboard ---------------------------------------------------------- */
.dashboard-grid { display: grid; grid-template-columns: repeat(12, 1fr); gap: var(--space-4); }
.widget {
  grid-column: span 3;
  display: flex;
  flex-direction: column;
  background: var(--color-surface);
  border-radius: var(--radius-lg);
  overflow: hidden;
}
.widget-span-4  { grid-column: span 4; }
.widget-span-6  { grid-column: span 6; }
.widget-span-8  { grid-column: span 8; }
.widget-span-12 { grid-column: span 12; }
.widget-header { display: flex; align-items: center; gap: var(--space-3); padding: var(--space-4) var(--space-5) var(--space-2); }
.widget-title {
  flex: 1 1 auto;
  font-size: var(--type-overline-size);
  letter-spacing: var(--type-overline-track);
  text-transform: var(--type-overline-transform);
  font-weight: var(--weight-semi);
  color: var(--color-on-surface-faint);
}
.widget-body { padding: var(--space-2) var(--space-5) var(--space-5); flex: 1 1 auto; }
.widget-body-flush { padding: 0; }
/* The widget is already a raised surface, so anything inside it that carries
   its own card treatment has to drop it or the result is a box in a box. */
.widget-body > .stat,
.widget-body > .card-flat { padding: 0; background: none; box-shadow: none; }
.widget-footer {
  padding: var(--space-3) var(--space-5);
  border-top: var(--border-width) solid var(--color-divider);
  font-size: var(--type-caption-size);
  color: var(--color-on-surface-muted);
}
@media (max-width: 1024px) {
  .widget { grid-column: span 6; }
  .widget-span-8, .widget-span-12 { grid-column: span 12; }
}
@media (max-width: 720px) {
  .widget, .widget-span-4, .widget-span-6, .widget-span-8 { grid-column: span 12; }
}

/* --- Filter panel and results ------------------------------------------- */
.filter-layout {
  display: grid;
  grid-template-columns: 260px minmax(0, 1fr);
  gap: var(--space-6);
  align-items: start;
}
.filter-panel {
  padding: var(--space-4);
  background: var(--color-surface);
  border: var(--border-width) solid var(--color-border);
  border-radius: var(--radius-md);
}
.filter-panel-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  margin-bottom: var(--space-3);
  padding-bottom: var(--space-3);
  border-bottom: var(--border-width) solid var(--color-border);
}
.filter-group + .filter-group {
  margin-top: var(--space-4);
  padding-top: var(--space-4);
  border-top: var(--border-width) solid var(--color-border);
}
.filter-group-title {
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-size: var(--type-subtitle-2-size);
  font-weight: var(--weight-medium);
  margin-bottom: var(--space-2);
}
.filter-count { font-size: var(--type-caption-size); color: var(--color-on-surface-faint); }
.result-header { display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-3); margin-bottom: var(--space-4); }
.result-count { font-size: var(--type-body-2-size); color: var(--color-on-surface-muted); }
/* Chips echoing what is currently filtered, each one removable. */
.filter-summary { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-bottom: var(--space-4); }
@media (max-width: 1024px) {
  .filter-layout { grid-template-columns: minmax(0, 1fr); }
}

/* --- Chat --------------------------------------------------------------- */
.chat {
  display: flex;
  flex-direction: column;
  height: 480px;
  background: var(--color-surface);
  border: var(--border-width) solid var(--color-border);
  border-radius: var(--radius-md);
  overflow: hidden;
}
.chat-header {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border-bottom: var(--border-width) solid var(--color-border);
}
.chat-log {
  flex: 1 1 auto;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: var(--space-4);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  background: var(--color-background);
}
.chat-day {
  align-self: center;
  padding: var(--space-1) var(--space-3);
  margin-block: var(--space-2);
  border-radius: var(--radius-pill);
  background: var(--color-surface-sunken);
  font-size: var(--type-caption-size);
  color: var(--color-on-surface-muted);
}
.message { display: flex; gap: var(--space-2); max-width: 78%; align-items: flex-end; }
.message-bubble {
  min-width: 0;
  padding: var(--space-2) var(--space-3);
  background: var(--color-surface);
  border-radius: var(--radius-lg);
  /* The tail is a SQUARE corner, not a smaller one. It used to be 4px against
     a 12px bubble; now that every corner in the language is 6px there is no
     smaller radius left to cut it with, and a tail you cannot see is not a
     tail. */
  border-end-start-radius: var(--radius-none);
  font-size: var(--type-body-2-size);
  line-height: 1.45;
  overflow-wrap: break-word;
}
.message-author { font-size: var(--type-caption-size); font-weight: var(--weight-medium); color: var(--color-primary-text); margin-bottom: 2px; }
.message-meta {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  margin-top: var(--space-1);
  font-size: var(--type-caption-size);
  color: var(--color-on-surface-faint);
}
/* The viewer's own messages mirror to the trailing edge. */
.message-self { align-self: flex-end; flex-direction: row-reverse; }
.message-self .message-bubble {
  background: var(--color-primary-soft);
  border-end-start-radius: var(--radius-lg);
  border-end-end-radius: var(--radius-none);
}
.message-self .message-meta { justify-content: flex-end; }
/* Consecutive messages from one person: only the first keeps its tail. */
.message-run .message-bubble { border-end-start-radius: var(--radius-lg); }
.message-self.message-run .message-bubble { border-end-end-radius: var(--radius-lg); }
.message-run .avatar { visibility: hidden; }
.chat-composer {
  flex: 0 0 auto;
  display: flex;
  align-items: flex-end;
  gap: var(--space-2);
  padding: var(--space-3);
  border-top: var(--border-width) solid var(--color-border);
}
.typing { display: inline-flex; align-items: center; gap: 3px; padding: var(--space-2) var(--space-1); }
.typing-dot {
  width: 6px;
  height: 6px;
  border-radius: var(--radius-circle);
  background: var(--color-on-surface-faint);
  animation: typing-bounce 1.2s var(--ease-standard) infinite;
}
.typing-dot:nth-child(2) { animation-delay: .15s; }
.typing-dot:nth-child(3) { animation-delay: .3s; }
@keyframes typing-bounce {
  0%, 60%, 100% { transform: translateY(0);    opacity: .5; }
  30%           { transform: translateY(-4px); opacity: 1; }
}

/* --- Comment thread ----------------------------------------------------- */
.comments { display: flex; flex-direction: column; gap: var(--space-5); }
.comment { display: flex; gap: var(--space-3); }
.comment-body { flex: 1 1 auto; min-width: 0; }
.comment-header { display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-2); }
.comment-author { font-size: var(--type-subtitle-2-size); font-weight: var(--weight-medium); }
.comment-time { font-size: var(--type-caption-size); color: var(--color-on-surface-faint); }
.comment-text { font-size: var(--type-body-2-size); margin-top: var(--space-1); max-width: var(--size-readable); }
.comment-actions { display: flex; align-items: center; gap: var(--space-1); margin-top: var(--space-1); }
/* Replies indent once and stop. Deeper nesting renders flat with a mention
   instead, which is what keeps a narrow screen readable. */
.comment-replies {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  margin-top: var(--space-4);
  padding-inline-start: var(--space-5);
  border-inline-start: var(--border-width-strong) solid var(--color-border);
}
.comment-collapsed {
  padding: var(--space-1) 0;
  border: 0;
  background: 0;
  font-family: inherit;
  font-size: var(--type-body-2-size);
  color: var(--color-primary-text);
  cursor: pointer;
  text-align: start;
}

/* --- Week schedule and month view --------------------------------------- */
.schedule {
  display: grid;
  grid-template-columns: 4rem repeat(var(--schedule-days, 5), minmax(0, 1fr));
  background: var(--color-surface);
  border: var(--border-width) solid var(--color-border);
  border-radius: var(--radius-md);
  overflow: auto;
  max-height: 460px;
}
.schedule-corner,
.schedule-day-head {
  position: sticky;
  top: 0;
  z-index: var(--layer-raised);
  padding: var(--space-2);
  text-align: center;
  background: var(--color-surface);
  border-bottom: var(--border-width) solid var(--color-border);
  font-size: var(--type-caption-size);
  font-weight: var(--weight-medium);
}
.schedule-day-head small { display: block; font-weight: var(--weight-regular); color: var(--color-on-surface-muted); }
.schedule-hour {
  height: 48px;
  padding: var(--space-1) var(--space-2);
  text-align: end;
  font-size: var(--type-caption-size);
  font-variant-numeric: tabular-nums;
  color: var(--color-on-surface-faint);
  border-bottom: var(--border-width) solid var(--color-border);
  border-inline-end: var(--border-width) solid var(--color-border);
}
.schedule-cell {
  position: relative;
  height: 48px;
  border-bottom: var(--border-width) solid var(--color-border);
  border-inline-end: var(--border-width) solid var(--color-border);
}
.schedule-event {
  position: absolute;
  inset-inline: 2px;
  top: 2px;
  padding: var(--space-1) var(--space-2);
  background: var(--color-primary-soft);
  border-inline-start: 3px solid var(--color-primary);
  border-radius: var(--radius-xs);
  font-size: var(--type-caption-size);
  color: var(--color-on-surface);
  overflow: hidden;
  cursor: pointer;
  z-index: var(--layer-raised);
}
.schedule-event-title { display: block; font-weight: var(--weight-medium); }
.schedule-event-secondary { background: var(--color-secondary-soft); border-inline-start-color: var(--color-secondary-strong); }
.schedule-event-warning { background: var(--color-warning-soft); border-inline-start-color: var(--color-warning); }

.month-grid {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: var(--border-width);
  background: var(--color-border);
  border: var(--border-width) solid var(--color-border);
  border-radius: var(--radius-md);
  overflow: hidden;
}
.month-weekday {
  padding: var(--space-2);
  text-align: center;
  background: var(--color-surface);
  font-size: var(--type-caption-size);
  font-weight: var(--weight-medium);
  color: var(--color-on-surface-muted);
}
.month-day {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-height: 96px;
  padding: var(--space-2);
  background: var(--color-surface);
}
.month-day.is-outside { background: var(--color-surface-sunken); }
.month-day-number { font-size: var(--type-caption-size); color: var(--color-on-surface-muted); font-variant-numeric: tabular-nums; }
.month-day.is-today .month-day-number {
  align-self: flex-start;
  display: grid;
  place-items: center;
  width: 22px;
  height: 22px;
  background: var(--color-primary-ground);
  color: var(--color-on-primary);
  border-radius: var(--radius-circle);
  font-weight: var(--weight-semi);
}
.month-event {
  padding: 1px var(--space-1);
  border-radius: var(--radius-xs);
  background: var(--color-primary-soft);
  color: var(--color-on-surface);
  font-size: var(--type-caption-size);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  cursor: pointer;
}
.month-event-secondary { background: var(--color-secondary-soft); }
.month-more { font-size: var(--type-caption-size); color: var(--color-on-surface-muted); }
/* Below this width a month grid cannot show titles, so it falls back to a
   dot per event and the day view carries the detail. */
@media (max-width: 720px) {
  .month-day { min-height: 56px; }
  .month-event {
    width: 6px;
    height: 6px;
    padding: 0;
    border-radius: var(--radius-circle);
    background: var(--color-primary);
    text-indent: -999em;
  }
  .month-day { flex-direction: row; flex-wrap: wrap; align-content: flex-start; }
  .month-day-number { flex: 1 0 100%; }
}

/* --- Error and status screens ------------------------------------------- */
.error-page {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-4);
  text-align: center;
  padding-block: var(--space-10);
  max-width: var(--size-container-narrow);
  margin-inline: auto;
}
.error-code {
  font-size: var(--type-display-2-size);
  font-weight: var(--weight-light);
  line-height: 1;
  color: var(--color-primary-text);
  font-variant-numeric: tabular-nums;
}
.error-mark {
  display: grid;
  place-items: center;
  width: 96px;
  height: 96px;
  border-radius: var(--radius-circle);
  background: var(--color-primary-soft);
  color: var(--color-primary-text);
}
.error-mark .icon { font-size: 48px; width: 48px; height: 48px; }
.error-mark-danger  { background: var(--color-danger-soft);  color: var(--color-danger); }
.error-mark-warning { background: var(--color-warning-soft); color: var(--color-warning); }
.error-title { font-size: var(--type-heading-1-size); font-weight: var(--weight-regular); }
.error-body { color: var(--color-on-surface-muted); max-width: 52ch; }
.error-actions { display: flex; flex-wrap: wrap; gap: var(--space-3); justify-content: center; margin-top: var(--space-2); }
/* The reference an operator would quote, kept quiet but selectable. */
.error-detail {
  margin-top: var(--space-5);
  font-family: var(--font-mono);
  font-size: var(--type-caption-size);
  color: var(--color-on-surface-faint);
  user-select: all;
}

/* --- Authentication ----------------------------------------------------- */
.auth { display: grid; place-items: center; padding: var(--space-6) var(--space-4); }
.auth-card {
  width: 100%;
  max-width: 400px;
  /* A grid item's automatic minimum is its content, which would let a wide
     child — a six-box code, a long provider button — push the card past its
     column and scroll the whole page sideways. */
  min-width: 0;
  padding: var(--space-6);
  background: var(--color-surface);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-8);
}
.auth-brand {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  margin-bottom: var(--space-5);
  color: var(--color-primary-text);
  font-weight: var(--weight-medium);
}
.auth-title { font-size: var(--type-heading-2-size); font-weight: var(--weight-regular); text-align: center; }
.auth-lede { margin-top: var(--space-2); text-align: center; font-size: var(--type-body-2-size); color: var(--color-on-surface-muted); }
.auth-form { display: flex; flex-direction: column; gap: var(--space-4); margin-top: var(--space-5); }
.auth-row { display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); flex-wrap: wrap; }
.auth-providers { display: flex; flex-direction: column; gap: var(--space-2); }
/* A rule with the word "or" sitting in a gap in it. */
.auth-divider {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin-block: var(--space-5);
  font-size: var(--type-caption-size);
  letter-spacing: var(--type-overline-track);
  text-transform: var(--type-overline-transform);
  color: var(--color-on-surface-faint);
}
.auth-divider::before,
.auth-divider::after { content: ''; flex: 1 1 auto; border-top: var(--border-width) solid var(--color-border); }
.auth-footer { margin-top: var(--space-5); text-align: center; font-size: var(--type-body-2-size); color: var(--color-on-surface-muted); }
.auth-legal { margin-top: var(--space-4); text-align: center; font-size: var(--type-caption-size); color: var(--color-on-surface-faint); }
/* Two-panel variant: the pitch on one side, the form on the other. */
.auth-split { display: grid; grid-template-columns: 1fr 1fr; min-height: 520px; }
.auth-aside {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: var(--space-8);
  padding: var(--space-8);
  /* The ground that carries text, not the identity blue — see the note on
     --color-primary-ground. */
  background: var(--color-primary-ground);
  color: var(--color-on-primary);
}
/* White at reduced alpha rather than the page color at reduced alpha, for
   the reason given on .band-brand above. */
.auth-aside .page-lede,
.auth-aside .page-eyebrow,
.auth-aside .text-muted { color: #fff; opacity: .95; }
@media (max-width: 1024px) {
  .auth-split { grid-template-columns: 1fr; }
  .auth-aside { display: none; }
}

/* --- Consent banner ----------------------------------------------------- */
/* Pinned to the foot of the viewport, under toasts but over everything else. */
.consent {
  position: fixed;
  inset-inline: 0;
  bottom: 0;
  z-index: var(--layer-overlay);
  display: flex;
  align-items: center;
  gap: var(--space-5);
  padding: var(--space-4) var(--space-5);
  background: var(--color-surface);
  border-top: var(--border-width) solid var(--color-border);
  box-shadow: var(--shadow-16);
  transform: translateY(100%);
  transition: transform var(--duration-slow) var(--ease-decelerate);
}
.consent[data-open="true"] { transform: none; }
.consent-text { flex: 1 1 auto; font-size: var(--type-body-2-size); color: var(--color-on-surface-muted); max-width: var(--size-readable); }
.consent-actions { flex: 0 0 auto; display: flex; flex-wrap: wrap; gap: var(--space-2); }
/* The same banner shown in place, for documenting it without pinning it. */
.consent-inline {
  position: static;
  transform: none;
  box-shadow: none;
  border: var(--border-width) solid var(--color-border);
  border-radius: var(--radius-md);
}
@media (max-width: 720px) {
  .consent { flex-direction: column; align-items: stretch; gap: var(--space-3); }
}

/* Board ------------------------------------------------------------------------ */
/* Columns of cards, where moving a card between columns is the edit. The whole
   board scrolls sideways and each column scrolls on its own, which is the only
   arrangement that survives both six columns and forty cards.

   Dragging is the obvious gesture and the one nobody can rely on: it needs a
   pointer, a steady hand and a screen tall enough to show source and target at
   once. The script gives every card a keyboard route to the same move, and the
   move is announced, because a card silently changing column is a change
   nobody watching the announcements would know about. */
.board {
  display: flex;
  gap: var(--space-4);
  align-items: flex-start;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  padding-bottom: var(--space-3);
  /* Scroll padding keeps a column from stopping flush against the edge when
     it is scrolled into view by the keyboard. */
  scroll-padding-inline: var(--space-4);
}
.board-column {
  flex: none;
  width: var(--board-column-width, 280px);
  max-height: var(--board-height, 560px);
  display: flex;
  flex-direction: column;
  background: var(--color-surface-sunken);
  border-radius: var(--radius-md);
  border: var(--border-width) solid var(--color-border);
}
.board-column-header {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-3) var(--space-2);
  flex: none;
}
.board-column-title {
  font-size: var(--type-subtitle-2-size);
  font-weight: var(--weight-medium);
  letter-spacing: var(--type-subtitle-2-track);
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.board-column-count {
  flex: none;
  min-width: 20px;
  padding: 0 6px;
  border-radius: var(--radius-pill);
  background: rgba(var(--rgb-on-surface), .10);
  color: var(--color-on-surface-muted);
  font-size: var(--type-caption-size);
  font-variant-numeric: tabular-nums;
  text-align: center;
}
/* A column at or over its limit, which is the whole point of a WIP column. */
.board-column[data-over-limit="true"] .board-column-count {
  background: var(--color-warning-soft);
  color: var(--color-on-warning-soft);
}
.board-column-accent {
  width: 3px;
  align-self: stretch;
  border-radius: var(--radius-pill);
  background: var(--accent, var(--color-primary));
  flex: none;
}

.board-list {
  list-style: none;
  margin: 0;
  padding: var(--space-2) var(--space-3) var(--space-3);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  overflow-y: auto;
  overscroll-behavior-y: contain;
  flex: 1;
  min-height: 72px;
}
/* The drop target has to stay visible while the list is empty, or the last
   card out of a column takes the target with it. */
.board-list:empty::after,
.board-list[data-empty="true"]::after {
  content: attr(data-empty-text);
  display: grid;
  place-items: center;
  min-height: 56px;
  border: var(--border-width) dashed var(--color-border-strong);
  border-radius: var(--radius-sm);
  color: var(--color-on-surface-faint);
  font-size: var(--type-caption-size);
}

/* The one card in the kit that keeps a shadow, and only while it is in the
   air: a board card is picked up and moved, so "lifted" is a literal state
   it is genuinely in rather than a decoration. At rest it separates by fill
   like every other card, and hovering it does not lift it — pointing at
   something is not picking it up. */
.board-card {
  background: var(--color-surface);
  border-radius: var(--radius-sm);
  padding: var(--space-3);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  cursor: grab;
  /* Touch panning must not start a scroll when the gesture is a drag. */
  touch-action: none;
  transition: background-color var(--duration-fast) var(--ease-standard),
              box-shadow var(--duration-fast) var(--ease-standard),
              opacity var(--duration-fast) var(--ease-standard);
}
.board-card:hover { background: var(--color-surface-sunken); }
.board-card:focus-visible {
  outline: var(--focus-ring-width) solid var(--focus-ring-color);
  outline-offset: var(--focus-ring-offset);
}
.board-card-title { font-size: var(--type-body-2-size); line-height: 1.4; }
.board-card-meta {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--type-caption-size);
  color: var(--color-on-surface-muted);
}
.board-card-meta .avatar { margin-inline-start: auto; }

/* Dragged with no lift to show — data-drag-proxy="off". With one, the card
   itself is in the air and .is-lifted dresses it. */
.board-card.is-dragging:not(.is-lifted) {
  opacity: .45;
  box-shadow: var(--shadow-8);
  cursor: grabbing;
}
.board-card.is-grabbed {
  box-shadow: var(--shadow-8);
  outline: var(--border-width-strong) solid var(--color-primary);
  outline-offset: 1px;
}
.board-card.is-over       { box-shadow: 0 -3px 0 0 var(--color-primary), var(--shadow-1); }
.board-card.is-over-below { box-shadow: 0  3px 0 0 var(--color-primary), var(--shadow-1); }
.board-list.is-target {
  background: rgba(var(--rgb-primary), var(--state-selected));
  border-radius: var(--radius-sm);
}

/* The column the keyboard will move a grabbed card into. */
.board-column.is-keyboard-target { border-color: var(--color-primary); }

@media (max-width: 720px) {
  /* Columns wide enough to read on a phone leave the board scrolling sideways
     with two visible at a time, which is better than four unreadable ones. */
  .board { --board-column-width: 82vw; }
}

/* ===================================================== 18 Marketing blocks */
/* Landing-page sections: content-heavy, full-width and composed of the
   components above, so they sit apart from the application patterns. */

.band { padding-block: var(--space-9); }
.band-tight { padding-block: var(--space-7); }
.band-tint { background: var(--color-surface-sunken); }
.band-brand { background: var(--color-primary-ground); color: var(--color-on-primary); }
.band-head { max-width: var(--size-readable); margin-bottom: var(--space-7); }
.band-head-center { margin-inline: auto; text-align: center; }

/* Hero with the copy on one side and an illustration on the other. */
.hero-split { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-8); align-items: center; }
.hero-art { border-radius: var(--radius-lg); overflow: hidden; }
@media (max-width: 1024px) {
  .hero-split { grid-template-columns: 1fr; gap: var(--space-6); }
}

/* Feature grid ------------------------------------------------------------ */
.feature-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(260px, 100%), 1fr));
  gap: var(--space-6);
}
.feature { display: flex; flex-direction: column; gap: var(--space-2); }
.feature-icon {
  display: grid;
  place-items: center;
  width: 48px;
  height: 48px;
  margin-bottom: var(--space-2);
  border-radius: var(--radius-md);
  background: var(--color-primary-soft);
  color: var(--color-primary-text);
}
.feature-title { font-family: var(--font-display); font-size: var(--type-heading-3-size); font-weight: var(--weight-semi); }
.feature-text { font-size: var(--type-body-2-size); color: var(--color-on-surface-muted); }
.feature-row { display: flex; gap: var(--space-4); align-items: flex-start; }
.feature-row .feature-icon { flex: 0 0 auto; margin-bottom: 0; }

/* Pricing ---------------------------------------------------------------- */
.price-toggle { display: flex; justify-content: center; margin-bottom: var(--space-6); }
/* A tier that drops onto its own row reads as an afterthought rather than an
   option, so the track is narrow enough to keep three abreast on a laptop and
   the whole set stacks together below that. */
.pricing {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(200px, 100%), 1fr));
  gap: var(--space-4);
  align-items: stretch;
}
.price-card {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-6);
  background: var(--color-surface);
  border: 0;
  border-radius: var(--radius-lg);
}
/* The recommended plan is marked by the accent tint, not by an outline and a
   lift. Three cards in a row already read as a set; the tinted one reads as
   the chosen member of it, which is the whole message. */
.price-card-featured { background: var(--color-primary-soft); }
.price-badge { position: absolute; top: calc(var(--space-3) * -1); inset-inline-start: var(--space-5); }
/* On the featured tier the card is already the accent tint, so the tinted
   badge vanished into it. There it goes solid — the one place a badge is
   loud, and it is the thing the whole card is pointing at. */
.price-card-featured .price-badge { background: var(--color-primary-ground); color: var(--color-on-primary); }
.price-name { font-family: var(--font-display); font-size: var(--type-heading-3-size); font-weight: var(--weight-semi); }
.price-amount {
  display: flex;
  align-items: baseline;
  /* The period runs on past a narrow card otherwise, and clipping the word
     "month" off a price is the one thing a pricing table must not do. */
  flex-wrap: wrap;
  gap: var(--space-1);
  font-family: var(--font-display);
  font-size: var(--type-display-3-size);
  font-weight: var(--weight-semi);
  line-height: 1;
  font-variant-numeric: tabular-nums;
}
/* For a tier priced by conversation rather than by number. */
.price-amount-text { font-size: var(--type-heading-1-size); }
.price-currency { align-self: flex-start; font-size: var(--type-heading-2-size); }
.price-period { font-size: var(--type-body-2-size); font-weight: var(--weight-regular); color: var(--color-on-surface-muted); }
.price-note { font-size: var(--type-caption-size); color: var(--color-on-surface-muted); }
.price-features { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-2); }
.price-features li { display: flex; align-items: flex-start; gap: var(--space-2); font-size: var(--type-body-2-size); }
.price-features .icon { flex: 0 0 auto; color: var(--color-success); }
.price-features .is-absent,
.price-features .is-absent .icon { color: var(--color-on-surface-faint); }
/* Pushes the call to action to the foot of every card, however tall. */
.price-card > .button { margin-top: auto; }

/* Testimonials ----------------------------------------------------------- */
.testimonial-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(280px, 100%), 1fr));
  gap: var(--space-4);
}
.testimonial {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  padding: var(--space-5);
  background: var(--color-surface);
  border-radius: var(--radius-lg);
}
.testimonial-quote { flex: 1 1 auto; font-size: var(--type-body-size); line-height: 1.6; }
.testimonial-quote::before { content: '\201C'; }
.testimonial-quote::after  { content: '\201D'; }
.testimonial-author { display: flex; align-items: center; gap: var(--space-3); }
.testimonial-name { font-size: var(--type-subtitle-2-size); font-weight: var(--weight-medium); }
.testimonial-role { font-size: var(--type-caption-size); color: var(--color-on-surface-muted); }
.testimonial-lead { font-family: var(--font-display); font-size: var(--type-heading-3-size); font-weight: var(--weight-semi); line-height: 1.5; }

/* A row of customer marks, grayed back so they do not compete with the copy. */
.logo-wall { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: var(--space-7); }
.logo-wall-item {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--type-heading-3-size);
  font-weight: var(--weight-medium);
  color: var(--color-on-surface-faint);
  filter: grayscale(1);
  opacity: .7;
  transition: opacity var(--duration-base) var(--ease-standard);
}
.logo-wall-item:hover { opacity: 1; }

/* Statistic band --------------------------------------------------------- */
.stat-band {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(180px, 100%), 1fr));
  gap: var(--space-5);
  text-align: center;
}
.stat-band .stat-value { font-size: var(--type-display-3-size); font-weight: var(--weight-semi); }
/* White, not --rgb-surface. The surface channel is near-black in the dark
   theme, so "the page color at 78%" put dark gray text on a saturated blue
   band — legible in light, invisible in dark. The brand ground is a dark
   saturated blue in BOTH themes, so the text on it does not flip. */
.band-brand .stat-band .stat-label { color: #fff; opacity: .95; }

/* Frequently asked questions -------------------------------------------- */
/* Built on .panel-group, at a reading measure and without the card around it. */
.faq { max-width: var(--size-readable); margin-inline: auto; }
.faq .panel { background: transparent; box-shadow: none; border-bottom: var(--border-width) solid var(--color-divider); }
/* An FAQ drops the card, and with it the inline padding — which is fine, the
   measure is doing that job. What it must not drop is the padding in the
   other direction, and that is what went wrong: zeroing padding-inline left
   the block padding at the card's own value, so a question sat 16px from the
   hairline above it with the answer starting immediately underneath. At a
   reading measure, with every row the full width of the column, that is not
   enough air for the eye to find where one question ends.

   So the block padding grows instead of shrinking, the answer gets a gap of
   its own above it, and it is set at full body size rather than the smaller
   body-2 a dense panel uses. This is the one place in the kit where a panel
   is the primary content of the section rather than a way to pack a lot of
   it into a small space. */
.faq .panel-summary {
  font-size: var(--type-subtitle-size);
  line-height: var(--type-subtitle-line);
  padding: var(--space-5) 0;
  gap: var(--space-5);
}
.faq .panel-content {
  padding: 0 0 var(--space-6);
  max-width: 64ch;
  font-size: var(--type-body-size);
  line-height: var(--type-body-line);
}
/* The chevron is further from the text now, so it needs to stay with it. */
.faq .panel-summary::after { margin-inline-start: auto; }

/* Call to action --------------------------------------------------------- */
.cta {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-8) var(--space-5);
  text-align: center;
  border-radius: var(--radius-lg);
  background: var(--color-primary-ground);
  color: var(--color-on-primary);
}
.cta-title { font-family: var(--font-display); font-size: var(--type-heading-1-size); font-weight: var(--weight-semi); max-width: 24ch; }
.cta-text { color: #fff; opacity: .95; max-width: 52ch; }
.cta-actions { display: flex; flex-wrap: wrap; gap: var(--space-3); justify-content: center; margin-top: var(--space-2); }
/* On the brand ground the filled button inverts so it still reads as primary. */
/* On the brand ground the fills invert: white becomes the loud one, and the
   neutral control becomes a wash of white rather than one of the page fills,
   which are all the wrong side of the accent here. */
.cta .button-filled { background: #fff; color: var(--color-primary-strong); }
.cta .button-filled:hover { background: rgba(255, 255, 255, .88); }
.cta .button-outlined { background: rgba(255, 255, 255, .16); color: #fff; }
.cta .button-outlined:hover { background: rgba(255, 255, 255, .26); }
.cta-split { flex-direction: row; justify-content: space-between; align-items: center; text-align: start; }
.cta-split .cta-title { max-width: 40ch; }
@media (max-width: 720px) {
  .cta-split { flex-direction: column; text-align: center; }
}

/* Footer with columns of links ------------------------------------------- */
.site-footer { padding-block: var(--space-8) var(--space-6); background: var(--color-surface); }
.footer-columns {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(160px, 100%), 1fr));
  gap: var(--space-6);
}
.footer-column-title {
  margin-bottom: var(--space-3);
  font-size: var(--type-overline-size);
  letter-spacing: var(--type-overline-track);
  text-transform: var(--type-overline-transform);
  font-weight: var(--weight-medium);
  color: var(--color-on-surface-muted);
}
.footer-links { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-2); }
.footer-links a { font-size: var(--type-body-2-size); color: var(--color-on-surface-muted); }
.footer-links a:hover { color: var(--color-on-surface); }
.footer-bottom {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  margin-top: var(--space-7);
  padding-top: var(--space-5);
  border-top: var(--border-width) solid var(--color-divider);
  font-size: var(--type-caption-size);
  color: var(--color-on-surface-muted);
}

/* ====================================================== 19 Showcase chrome */
/* Styles for the demo pages themselves, not part of the component set. */
.page { display: flex; flex-direction: column; min-height: 100vh; }
.page-body { flex: 1 1 auto; }

.page-hero {
  padding-block: var(--space-9) var(--space-7);
  background: var(--color-surface);
}
.page-eyebrow {
  font-size: var(--type-overline-size);
  letter-spacing: var(--type-overline-track);
  text-transform: var(--type-overline-transform);
  color: var(--color-primary-text);
  font-weight: var(--weight-semi);
}
.page-lede {
  max-width: var(--size-readable);
  color: var(--color-on-surface-muted);
  font-size: var(--type-subtitle-size);
  line-height: 1.6;
}

/* Each demo lives in a labeled block with an anchor. */
.demo { scroll-margin-top: calc(var(--size-topbar) + var(--space-5)); }
.demo-header {
  display: flex;
  align-items: baseline;
  gap: var(--space-3);
  margin-bottom: var(--space-2);
  padding-bottom: var(--space-3);
  border-bottom: var(--border-width) solid var(--color-divider);
}
.demo-title {
  font-family: var(--font-display);
  font-size: var(--type-heading-2-size);
  font-weight: var(--type-heading-2-weight);
  letter-spacing: var(--type-heading-2-track);
}
.demo-note {
  color: var(--color-on-surface-muted);
  font-size: var(--type-body-2-size);
  max-width: var(--size-readable);
  margin-bottom: var(--space-4);
}
/* The stage is a card: it is what every demo sits on, so it is the reference
   for how a component is supposed to look against --color-surface. */
.demo-stage {
  isolation: isolate;
  padding: var(--space-5);
  background: var(--color-surface);
  border: 0;
  border-radius: var(--radius-lg);
}
.demo-stage-sunken { background: var(--color-surface-sunken); }
/* For a demo that has to stand in for the page itself rather than for a card
   on it — chiefly the app bar, which is the page color, and so is invisible
   on any stage that is not. */
.demo-stage-page { background: var(--color-background); }
.demo-stage-flush { padding: 0; overflow: hidden; }
.demo-label {
  font-size: var(--type-overline-size);
  letter-spacing: var(--type-overline-track);
  text-transform: var(--type-overline-transform);
  color: var(--color-on-surface-faint);
  font-weight: var(--weight-semi);
  margin-bottom: var(--space-2);
}
.demo-row { display: flex; flex-wrap: wrap; gap: var(--space-3); align-items: center; }

/* A box the reader can drag narrower, for showing what a component does when
   its own width changes rather than the window's. Native resize does the work;
   the handle is the browser's. */
/* One of two places a dashed border survives, and for the same reason in
   both: it marks something that is scaffolding rather than part of the
   design — here, a box the reader is invited to drag. */
.resize-box {
  resize: horizontal;
  overflow: auto;
  width: 100%;
  max-width: 100%;
  padding: var(--space-3);
  border: var(--border-width) dashed var(--color-border-loud);
  border-radius: var(--radius-sm);
}
/* Resize draws its handle in the corner the text flows towards. */
[dir="rtl"] .resize-box { direction: rtl; }
.demo-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(220px, 100%), 1fr));
  gap: var(--space-4);
}

/* In-page table of contents down the side of a showcase page. */
.toc { font-size: var(--type-body-2-size); }
.toc ul { list-style: none; }
.toc a {
  display: block;
  padding: var(--space-1) var(--space-3);
  border-left: var(--border-width-strong) solid var(--color-border);
  color: var(--color-on-surface-muted);
}
.toc a:hover { color: var(--color-on-surface); border-left-color: var(--color-border-loud); text-decoration: none; }
.toc a.is-active { color: var(--color-primary-text); border-left-color: var(--color-primary); font-weight: var(--weight-medium); }

.page-footer {
  padding-block: var(--space-6);
  border-top: var(--border-width) solid var(--color-divider);
  color: var(--color-on-surface-muted);
  font-size: var(--type-body-2-size);
  background: var(--color-background);
}

/* Prev / next links at the foot of each showcase page. */
.pager { display: flex; gap: var(--space-4); justify-content: space-between; }
.pager-link {
  flex: 1 1 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding: var(--space-4);
  border: 0;
  border-radius: var(--radius-md);
  background: var(--color-surface);
  color: var(--color-on-surface);
  transition: background-color var(--duration-fast) var(--ease-standard);
}
.pager-link:hover { background: var(--color-surface-sunken); text-decoration: none; }
.pager-next { text-align: right; align-items: flex-end; }
.pager-link[hidden] { display: none; }

/* Index page cards. */
.index-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(280px, 100%), 1fr));
  gap: var(--space-4);
}
/* No lift and no shadow on hover. The 2px rise plus a growing shadow is the
   grow-on-hover this language does not do: twelve of them in a grid turn a
   page into a field of twitching tiles. The fill answers instead. */
.index-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-5);
  background: var(--color-surface);
  border-radius: var(--radius-lg);
  color: var(--color-on-surface);
  position: relative;
  overflow: hidden;
  transition: background-color var(--duration-fast) var(--ease-standard);
}
.index-card:hover { background: var(--color-surface-sunken); text-decoration: none; }
.index-card .icon { color: var(--color-primary-text); }
.index-card-count {
  font-size: var(--type-caption-size);
  color: var(--color-on-surface-faint);
  margin-top: auto;
}

/* ================================== 20 Print, reduced motion, forced colors */
/* The app resolves the OS preference and its own Motion setting into
   html[data-motion]; without that attribute the OS preference applies. */
:root[data-motion="reduce"] { scroll-behavior: auto; }
:root[data-motion="reduce"] *, :root[data-motion="reduce"] *::before, :root[data-motion="reduce"] *::after {
  animation-duration: .01ms !important;
  animation-iteration-count: 1 !important;
  transition-duration: .01ms !important;
  scroll-behavior: auto !important;
}
@media (prefers-reduced-motion: reduce) {
  :root:not([data-motion]) { scroll-behavior: auto; }
  :root:not([data-motion]) *, :root:not([data-motion]) *::before, :root:not([data-motion]) *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
}

@media print {
  .topbar, .drawer, .scrim, .toast-region, .skip-link,
  .page-footer, .pager, .toc, .button-float,
  .consent, .speed-dial, .popover, .combobox-popup, .chat-composer,
  .palette-scrim, .lightbox, .toolbar, .editor > .toolbar,
  .scrollbar { display: none !important; }
  body { background: #fff; color: #000; }
  .card, .demo-stage, .table-wrap,
  .widget, .price-card, .testimonial, .settings-list,
  .board-card, .editor { box-shadow: none; border: 1px solid #ccc; }
  /* A scrolling region prints as whatever happens to be in view otherwise. */
  .chat, .chat-log, .virtual-list, .listbox, .schedule, .scroll-box,
  .editor-surface, .board-column, .board-list {
    height: auto !important;
    max-height: none !important;
    overflow: visible !important;
  }
  /* The board prints as stacked columns; six of them side by side on A4 are
     unreadable, and a horizontal scroller prints only what was in view. */
  .board { display: block; overflow: visible !important; }
  .board-column { width: auto; break-inside: avoid; margin-bottom: var(--space-4); }
  /* Columns fill column-by-column, which a printed page breaks across sheets
     in an order nobody can follow. */
  .masonry { columns: auto; }
  a::after { content: ' (' attr(href) ')'; font-size: .8em; color: #555; }
  .demo { break-inside: avoid; }
}

/* Windows High Contrast and other forced-color modes replace every color
   the tokens set, so anything the language expressed only as a background or
   a shadow disappears. These rules restore the same information using the
   system palette and real borders, which forced-color modes never override. */
@media (forced-colors: active) {
  /* Borders are the only reliable separator left once backgrounds are gone. */
  .card, .popover, .menu, .dialog, .sheet, .drawer, .combobox-popup,
  .toast, .tooltip, .calendar, .price-card, .testimonial, .widget,
  .settings-list, .filter-panel, .chat, .virtual-list, .consent,
  .toolbar, .editor, .palette, .board-column, .board-card {
    border: 1px solid CanvasText;
  }

  /* Elevation is pure shadow, which does not render here at all. */
  .elevation-1, .elevation-2, .elevation-3, .elevation-4, .elevation-6,
  .elevation-8, .elevation-12, .elevation-16, .elevation-24 {
    border: 1px solid CanvasText;
  }

  /* A lifted row is distinguished by its shadow, so here it is distinguished
     by nothing at all. The outline is the whole cue, and the gap it came from
     keeps the border it needs to stay visible. */
  .is-lifted {
    outline: var(--border-width-strong) solid Highlight;
    outline-offset: 1px;
  }
  .drag-gap { border-color: CanvasText; }

  /* State layers are translucent fills, so selection has to be redrawn. */
  .listbox-option[aria-selected="true"],
  .nav-item[aria-current="page"],
  .menu-item:hover,
  .listbox-option.is-active,
  .time-option[aria-selected="true"],
  .calendar-day.is-selected,
  .palette-item.is-active,
  .toolbar-button[aria-pressed="true"],
  .toolbar-button[aria-checked="true"],
  .toolbar-button.is-active {
    background: Highlight;
    color: HighlightText;
    forced-color-adjust: none;
  }
  .calendar-day.is-today { outline: 1px solid CanvasText; outline-offset: -1px; }
  /* A swatch is a block of color and nothing else, so forced colors leave
     the whole palette as identical empty squares. There is no honest fix —
     the mode exists to discard exactly this information — so the row says
     what it is in words instead, and the hex field beside it stays the way
     a color is chosen here. */
  .color-swatches::before {
    content: 'Swatch colors are not shown in this display mode. Type a hex value instead.';
    grid-column: 1 / -1;
    font-size: var(--type-caption-size);
  }
  .color-swatch, .color-preview, .color-trigger-swatch { forced-color-adjust: none; }
  .calendar-day.is-in-range { background: Highlight; color: HighlightText; forced-color-adjust: none; }

  /* The thumb is a block of color, which is what this mode exists to discard,
     so it is redrawn in system colors with a gap around it — without the
     border it merges into any surface that happens to be ButtonText too. */
  .scrollbar-thumb {
    forced-color-adjust: none;
    background-color: ButtonText;
    background-image: none;
    /* The inset border is deliberately left as it is: transparent, and the
       width that holds the gutter-wide hit area open. The track behind it is
       transparent too, so Canvas shows through the inset on its own and the
       thumb reads as a ButtonText bar on Canvas with nothing drawn to
       separate them. An earlier version set border-color here and it never
       applied — .scrollbar-y > .scrollbar-thumb outranks a lone class on
       specificity, which is easy to miss in a block this far from the rule
       it is trying to override. */
  }
  .scrollbar:hover > .scrollbar-thumb,
  .scrollbar.is-dragging > .scrollbar-thumb {
    background-color: Highlight;
    background-image: none;
  }

  /* Progress, meters and switches draw their value as a colored fill. */
  .progress-bar, .meter-fill, .switch-track { forced-color-adjust: none; background: Highlight; }
  .switch-thumb { forced-color-adjust: none; background: Canvas; border: 1px solid CanvasText; }
  .progress-linear, .meter { border: 1px solid CanvasText; }
  /* A strength bar is four colored segments and nothing else, so the score
     has to survive as a count of filled segments rather than as a hue. */
  .strength-segment { border: 1px solid CanvasText; }
  .strength-segment.is-on { forced-color-adjust: none; background: Highlight; }
  /* A conic gradient is a background image here and simply does not paint, so
     the figure is replaced by the number already inside it. */
  .donut, .gauge::before { display: none; }
  .donut-figure, .gauge-figure { border: 1px solid CanvasText; padding: var(--space-3); min-width: 0; }
  .donut-center, .gauge-center { position: static; }
  .chart-legend-swatch { border: 1px solid CanvasText; }

  /* Status is carried by color alone in these, so give each one an edge. */
  .alert, .banner, .tag, .chip, .badge, .status-dot, .schedule-event, .month-event {
    border: 1px solid CanvasText;
  }

  /* Every control that gave up its border for a fill. In this language that
     is most of them — a field, a search box, a dropzone, the neutral button,
     a filter chip and a selectable card are all a block of color and nothing
     else, so forced colors leaves them as empty space with a label floating
     in it. They get the edge back, and the fields get their resting, hover
     and focus fills reset to Canvas so the three states stop being three
     identical boxes. Focus is then carried by the ring alone, which is the
     one signal forced colors guarantees. */
  .input, .textarea, .select, .input-group, .search, .file-drop, .otp-slot,
  .number-input, .tag-input, .fieldset, .choice-chip, .choice-card,
  .button-outlined, .button-tonal, .segmented, .list-bordered, .kbd,
  .code-block, .file-item, .color-input, .card-outlined, .chip-outlined,
  .color-preview, .color-swatch, .color-trigger-swatch {
    border: 1px solid CanvasText;
  }
  .input, .textarea, .select, .input-group, .search, .otp-slot, .tag-input,
  .number-input, .file-drop {
    background: Canvas;
  }
  .input:focus, .textarea:focus, .select:focus, .otp-slot:focus,
  .input-group:focus-within, .search:focus-within, .tag-input:focus-within,
  .number-input:focus-within { background: Canvas; }

  /* The segmented group marks its current cell with accent text, which is a
     color this mode discards. */
  .segmented > .button[aria-pressed="true"],
  .segmented > .button.is-active {
    background: Highlight;
    color: HighlightText;
    forced-color-adjust: none;
  }

  /* An invalid field says so with a wash of the status hue, which also goes. */
  .input[aria-invalid="true"], .textarea[aria-invalid="true"],
  .select[aria-invalid="true"], .input.is-invalid {
    border: 2px solid CanvasText;
  }

  /* The shared state layer is a translucent shade over the control. This
     mode replaces the color it is painted in, which leaves an opaque block
     sitting on top of the label; hover and press are the system's business
     here anyway. */
  .button::before { display: none; }

  /* A disabled control must still read as disabled. */
  .button:disabled, .input:disabled, .listbox-option[aria-disabled="true"],
  .number-step:disabled, .calendar-day[disabled], .month-picker-cell:disabled { color: GrayText; }

  /* Keep the focus ring visible; the token color is ignored here. */
  :focus-visible { outline: 2px solid Highlight; outline-offset: 2px; }

  /* A skeleton is a block of fill, and forced colors discard fills — so
     without this it collapses to nothing and the placeholder disappears. */
  .skeleton { border: 1px dashed GrayText; background: Canvas; }
}

/* ======================================== 21 Right-to-left (dir="rtl") */
/* Most of the kit already mirrors on its own: the layout is built from
   logical properties (padding-inline, inset-inline-start, border-inline-end),
   and those follow the document's writing mode without being told to. What
   remains here is the three things logical properties cannot fix.

   One: the handful of places that genuinely needed a physical side, nearly all
   of them absolutely positioned or transformed. A transform is always
   physical — translateX(-101%) slides a drawer off the left edge whatever the
   direction — so every sliding surface needs its own mirrored rule.

   Two: icons that point. An arrow meaning "onward" points right in English
   and left in Arabic or Hebrew, so the glyph has to be flipped. An icon that
   depicts an object must not be: a mirrored clock, printer or magnifier reads
   as a mistake, not as a translation. Only the directional set is flipped
   here, and .icon-directional opts an icon in by hand.

   Three: content that stays left-to-right inside a right-to-left page. Code,
   file paths, phone numbers and version strings are not translated and must
   not be reordered; they get dir="ltr" so the bidi algorithm leaves them be.

   To try any page in this kit right to left, set dir="rtl" on <html>. */

/* --- Surfaces that slide ---------------------------------------------- */
[dir="rtl"] .drawer { left: auto; right: 0; transform: translateX(101%); }
[dir="rtl"] .drawer[data-open="true"] { transform: translateX(0); }
[dir="rtl"] .drawer-end { right: auto; left: 0; transform: translateX(-101%); }
[dir="rtl"] .drawer-end[data-open="true"] { transform: translateX(0); }
[dir="rtl"] .drawer-docked { border-inline-end: 0; border-inline-start: var(--border-width) solid var(--color-border); }

/* The side sheet is anchored to the end edge, which is the left one here. */
[dir="rtl"] .sheet-side { right: auto; left: 0; transform: translateX(-101%); }
[dir="rtl"] .sheet-side[data-open="true"] { transform: translateX(0); }

/* --- Fixed and absolutely positioned chrome --------------------------- */
[dir="rtl"] .skip-link { left: auto; right: var(--space-2); }
[dir="rtl"] .toast-region { left: auto; right: var(--space-2); }
[dir="rtl"] .field-float > .field-label { left: auto; right: var(--space-4); }
[dir="rtl"] .code-copy { right: auto; left: var(--space-2); }
[dir="rtl"] .carousel-prev { left: auto; right: var(--space-2); }
[dir="rtl"] .carousel-next { right: auto; left: var(--space-2); }
[dir="rtl"] .timeline-item::before { left: auto; right: 11px; border-left: 0; border-right: var(--border-width-strong) solid var(--color-border); }
[dir="rtl"] .stepper-vertical .step:not(:last-child)::before {
  left: auto; right: 11px;
  border-left: 0;
  border-right: var(--border-width) solid var(--color-border);
}

/* The native select chevron is a background image, and background-position
   has no logical form. */
[dir="rtl"] .select {
  padding-right: var(--space-4);
  padding-left: var(--space-8);
  background-position: left var(--space-3) center;
}
[dir="rtl"] .select-multiple { padding-left: var(--space-4); }

/* --- Physical sides that should have been logical --------------------- */
[dir="rtl"] .divider-inset { margin-left: 0; margin-right: var(--space-9); }
[dir="rtl"] .divider-vertical { border-left: 0; border-right: var(--border-width) solid var(--color-border); }
[dir="rtl"] .rail { border-right: 0; border-left: var(--border-width) solid var(--color-border); }
[dir="rtl"] .topbar-title { margin-right: 0; margin-left: var(--space-4); }
[dir="rtl"] .alert { border-left: 0; border-right: 4px solid var(--color-info); }
[dir="rtl"] .alert-success { border-right-color: var(--color-success); }
[dir="rtl"] .alert-warning { border-right-color: var(--color-warning); }
[dir="rtl"] .alert-danger  { border-right-color: var(--color-danger); }
[dir="rtl"] .blockquote { border-left: 0; border-right: 4px solid var(--color-primary); }
[dir="rtl"] .toc a { border-left: 0; border-right: var(--border-width-strong) solid var(--color-border); }
[dir="rtl"] .toc a:hover,
[dir="rtl"] .toc a.is-active { border-right-color: var(--color-primary); }
/* The tree draws its indent guide with a physical left border and pads the
   rows to clear it, so all three sides move together. */
[dir="rtl"] .tree ul {
  padding-left: 0;
  padding-right: var(--space-5);
  border-left: 0;
  border-right: var(--border-width) solid var(--color-border);
  margin-left: 0;
  margin-right: var(--space-3);
}
[dir="rtl"] .tree-leaf { padding-left: var(--space-2); padding-right: calc(var(--space-2) + var(--size-icon-sm)); }
[dir="rtl"] .segmented > .button + .button { border-left: 0; border-right: var(--border-width) solid var(--color-border); }
[dir="rtl"] .chip-avatar { margin-left: 0; margin-right: calc(var(--space-3) * -1 + 4px); }
[dir="rtl"] .chip-remove { margin-right: 0; margin-left: -4px; }
[dir="rtl"] .avatar-stack > .avatar + .avatar { margin-left: 0; margin-right: -12px; }
[dir="rtl"] .panel-summary::after,
[dir="rtl"] .menu-item-shortcut,
[dir="rtl"] .field-counter { margin-left: 0; margin-right: auto; }
[dir="rtl"] .table th,
[dir="rtl"] .table td,
[dir="rtl"] .table caption,
[dir="rtl"] .list-item { text-align: right; }
[dir="rtl"] .slider-value,
[dir="rtl"] .pager-next { text-align: left; }

/* --- Icons that point -------------------------------------------------- */
/* Flipped by name, because the glyph is chosen in the markup as a ligature
   and there is no attribute saying which ones are directional. */
[dir="rtl"] .icon-directional,
[dir="rtl"] .link-arrow .icon,
[dir="rtl"] .breadcrumbs > li:not(:last-child)::after,
[dir="rtl"] .pager-link .icon,
[dir="rtl"] .carousel-control .icon,
[dir="rtl"] .pagination-item .icon,
[dir="rtl"] .tree-item > summary::before,
[dir="rtl"] .lightbox-step .icon {
  transform: scaleX(-1);
}
/* The tree chevron already rotates when the branch is open, and a transform
   replaces rather than composes, so the open state needs both in one value.
   Mirrored then rotated by -90° points the glyph down, same as upright and
   rotated by 90° does in a left-to-right page. */
[dir="rtl"] .tree-item[open] > summary::before { transform: scaleX(-1) rotate(-90deg); }
/* An icon that depicts a thing, not a direction, must stay as drawn. */
[dir="rtl"] .icon-nondirectional { transform: none; }

/* --- Content that stays left-to-right --------------------------------- */
/* Code, identifiers and numbers are not translated, so the bidi algorithm
   must not reorder them. Setting the direction on the element itself is more
   reliable than a unicode-bidi override on the text inside it. */
[dir="rtl"] .code-block,
[dir="rtl"] .code-inline,
[dir="rtl"] .kbd,
[dir="rtl"] .swatch-value,
[dir="rtl"] .input-masked,
[dir="rtl"] .editor-surface pre {
  direction: ltr;
  text-align: left;
}
/* A number read as a quantity stays in logical order either way, but it must
   not pick up surrounding right-to-left punctuation. */
[dir="rtl"] .stat-value,
[dir="rtl"] .donut-value,
[dir="rtl"] .gauge-value { unicode-bidi: isolate; }

/* --- Components added alongside this section -------------------------- */
[dir="rtl"] .board { scroll-padding-inline: var(--space-4); }
[dir="rtl"] .board-card.is-over       { box-shadow: 0 -3px 0 0 var(--color-primary), var(--shadow-1); }
[dir="rtl"] .board-card.is-over-below { box-shadow: 0  3px 0 0 var(--color-primary), var(--shadow-1); }
[dir="rtl"] .palette-trigger .kbd { margin-inline-start: auto; }
