/* ============================================================
 * ds-base.css — DS Design System document baseline (Blazor)
 * ============================================================
 * The generated theme CSS defines custom properties only; nothing applies
 * them to the document. Without this sheet every element outside an
 * explicitly token-bound component inherits the browser default (Times
 * serif, UA chrome) — the React side gets the same baseline from MUI
 * CssBaseline + dsTheme.
 *
 * Load order (see the consumer README): after the generated theme CSS,
 * alongside kendo-bridge.css, in EVERY document that renders components
 * (for BlazingStory: index.html AND iframe.html).
 *
 * The host page must also load the brand webfonts the tokens reference:
 * Inter (starter/aurora) and Roboto (harbor) — Geist (meadow) is
 * self-hosted below and ships with this RCL (assets/fonts/, SIL OFL 1.1).
 *
 * Everything below the @font-face block is the CANONICAL DS baseline,
 * embedded verbatim from dist/base/ds-base.css (#96) and locked by
 * __tests__/ds-baseline.test.js. To change a baseline rule, edit the
 * canonical file and re-paste — never fork this copy.
 * ============================================================ */

/* ------------------------------------------------------------
 * Geist (meadow brand font) — self-hosted, SIL OFL 1.1.
 * Source: vercel/geist-font (geist@1.7.2 npm package).
 * Weights match the meadow weight tokens: regular 400, medium 500,
 * bold 600 (Geist's bold is 600, not 700 — see tokens/palette/meadow.json).
 * URLs are relative to this file (css/utilities/) and resolve to
 * _content/Ds.DesignSystem.Blazor/assets/fonts/ in consuming apps.
 * ------------------------------------------------------------ */

@font-face {
  font-family: "Geist";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("../../assets/fonts/Geist-Regular.woff2") format("woff2");
}

@font-face {
  font-family: "Geist";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("../../assets/fonts/Geist-Medium.woff2") format("woff2");
}

@font-face {
  font-family: "Geist";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("../../assets/fonts/Geist-SemiBold.woff2") format("woff2");
}

html {
  box-sizing: border-box;
  accent-color: var(--ds-color-primary); /* #172 */
  color-scheme: light; /* issue 218 (upstream TOK-22): native controls follow the DS mode */
}

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

body {
  margin: 0;
  font-family: var(--ds-typography-body-font-family);
  font-size: var(--ds-typography-body-font-size);
  font-weight: var(--ds-typography-body-font-weight);
  line-height: var(--ds-typography-body-line-height);
  color: var(--ds-color-onSurface);
  background-color: var(--ds-color-pageBackground);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  transition: background-color 150ms ease, color 150ms ease;
}

/* ------------------------------------------------------------
 * color-scheme dark flip (issue 218, upstream TOK-22) — data-theme is the
 * same attribute the theme CSS switches on; light lives on html above.
 * ------------------------------------------------------------ */
[data-theme="dark"] {
  color-scheme: dark;
}

/* ------------------------------------------------------------
 * .ds-scrollbar — opt-in themed scrollbars (issue 218, upstream TOK-32).
 * Standard scrollbar-color first (Firefox + Chromium 121+); WebKit
 * pseudo-elements as the fallback for older Chromium/Safari. Opt-in by
 * class — the DS never restyles the document scrollbar uninvited
 * (Mud themes scrollbars by default; we deliberately don't).
 * ------------------------------------------------------------ */
.ds-scrollbar {
  scrollbar-width: thin;
  scrollbar-color: var(--ds-color-outlineVariant) var(--ds-color-surfaceVariant);
}

.ds-scrollbar::-webkit-scrollbar {
  width: 10px;
  height: 10px;
}

.ds-scrollbar::-webkit-scrollbar-track {
  background: var(--ds-color-surfaceVariant);
}

.ds-scrollbar::-webkit-scrollbar-thumb {
  background: var(--ds-color-outlineVariant);
  border-radius: 5px;
  border: 2px solid var(--ds-color-surfaceVariant);
}

.ds-scrollbar::-webkit-scrollbar-thumb:hover {
  background: var(--ds-color-outline);
}

/* ------------------------------------------------------------
 * Text selection (#172) — a token-bound ::selection so highlighted text
 * reads as "this brand" instead of the browser's default blue, in both
 * modes and every brand. selectionBackground/selectionText are core color
 * tokens aliasing primarySubtle/onPrimarySubtle (tokens/core/color.json),
 * so they re-tone with brand + mode with no extra wiring here.
 * ------------------------------------------------------------ */
::selection {
  background: var(--ds-color-selectionBackground);
  color: var(--ds-color-selectionText);
}

/* ------------------------------------------------------------
 * <mark> highlight (GH-173) — the native HTML highlight element had no DS
 * styling at all (browser default: opaque yellow, no dark-mode awareness).
 * Token-bound so search-hit / "new" / diff highlights read as "this brand"
 * in both modes, the same job ::selection does for text selection.
 * core.color.accent/onAccent are the brand accent knob (brand.accent.500 /
 * brand.accent.onAccent) — additive only, nothing rendered <mark> before.
 * ------------------------------------------------------------ */
mark {
  background-color: var(--ds-color-accent);
  color: var(--ds-color-onAccent);
  border-radius: var(--ds-radius-sm, 2px);
  padding: 0 0.15em;
}

/* ------------------------------------------------------------
 * Reduced-motion global safety-net (A11Y-19, ADR-016 amended by ADR-019).
 *
 * Per-component prefers-reduced-motion guards leak: the A11Y-19 sweep found
 * ~24 animating component CSS files with NO guard, and every new component
 * re-opens the gap. This blanket reset is the system-wide floor — it neutralises
 * EVERY animation/transition (incl. Telerik/vendor and any consumer markup) when
 * the user asks for reduced motion. Explicit per-component guards remain as
 * defense-in-depth where they already express intent; this catches the rest.
 *
 * 0.01ms (not 0) keeps animationend/transitionend events firing so JS that waits
 * on them doesn't hang. The standard a11y-community reset (Andy Bell et al.).
 * ------------------------------------------------------------ */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ------------------------------------------------------------
 * data-ds-motion knob (#172) — lets a consumer override the OS
 * prefers-reduced-motion signal explicitly, independent of it:
 *   [data-ds-motion="never"]  forces the reduced-motion floor above
 *                             regardless of OS preference (e.g. an in-app
 *                             "reduce motion" toggle that isn't tied to the
 *                             OS setting).
 *   [data-ds-motion="always"] opts OUT of the floor even when the OS asks
 *                             for reduced motion (only for animation that
 *                             has been reviewed for vestibular safety).
 * Default (attribute absent on any ancestor): unchanged — respect
 * prefers-reduced-motion, as above.
 * ------------------------------------------------------------ */
[data-ds-motion="never"],
[data-ds-motion="never"] *,
[data-ds-motion="never"] *::before,
[data-ds-motion="never"] *::after {
  animation-duration: 0.01ms !important;
  animation-iteration-count: 1 !important;
  transition-duration: 0.01ms !important;
  scroll-behavior: auto !important;
}

@media (prefers-reduced-motion: reduce) {
  [data-ds-motion="always"],
  [data-ds-motion="always"] *,
  [data-ds-motion="always"] *::before,
  [data-ds-motion="always"] *::after {
    animation-duration: revert !important;
    animation-iteration-count: revert !important;
    transition-duration: revert !important;
    scroll-behavior: revert !important;
  }
}

/* ------------------------------------------------------------
 * Print baseline (GH-921, UI sweep 7 — print cluster).
 *
 * Zero @media print rules existed anywhere in the repo: nav/app-bar chrome
 * printed full-size on every page, a dark-mode screen printed dark (near-
 * illegible and ink-heavy), and even light-mode screens printed the
 * pageBackground token's off-white instead of a print-safe white. This is
 * the same systemic-floor shape the reduced-motion block above already
 * models (ADR-016/019) — one baseline default, no component or consumer
 * escape hatch required — but for print nothing existed yet.
 * ------------------------------------------------------------ */
@media print {
  /* 1) Ink and page tokens go light regardless of data-theme — a dark
   * screen must not print dark. Re-declares the handful of surface,
   * onSurface and pageBackground token roles this file (and most component
   * surfaces) consume, to their Starter-light values — the DS's own
   * reference brand, since print is "readable on paper", not "this brand's
   * exact art direction". pageBackground goes to true white, not its own
   * (already off-white) light value: the page itself should be blank paper.
   * !important on every custom property: a brand's own dark override (e.g.
   * `[data-theme="dark"][data-brand="takt"]`, dist/generated/ds-*-dark.css)
   * is MORE specific than the plain `[data-theme="dark"]` here and would
   * otherwise win on specificity regardless of this file's later source
   * order — confirmed live on the Takt Betalinger --dark story, which still
   * printed its brand dark pageBackground without `!important`.
   */
  :root,
  [data-theme="dark"] {
    color-scheme: light !important;
    --ds-color-surface: #FFFFFF !important;
    --ds-color-surfaceVariant: #F8FAFC !important;
    --ds-color-surfaceContainer: #F1F5F9 !important;
    --ds-color-surfaceContainerLow: #F8FAFC !important;
    --ds-color-surfaceContainerHigh: #E2E8F0 !important;
    --ds-color-surfaceContainerHighest: #CBD5E1 !important;
    --ds-color-surfaceAlt: #F8FAFC !important;
    --ds-color-onSurface: #0F172A !important;
    --ds-color-onSurfaceVariant: #475569 !important;
    --ds-color-onSurfaceSubtle: #64748B !important;
    --ds-color-outline: #64748B !important;
    --ds-color-outlineVariant: #E2E8F0 !important;
    --ds-color-pageBackground: #FFFFFF !important;
  }

  /* 2) Chrome that has no meaning on paper. Literal ds-c-* markers (GH-864)
   * so this is one rule for all three legs instead of a shell-specific hook.
   * GH-1178 adds __floating: the slot is position:fixed, and a fixed element
   * repeats on EVERY printed page, stamping the FAB over the content.
   */
  .ds-c-app-shell__sidebar,
  .ds-c-app-shell__topbar,
  .ds-c-app-shell__toggle,
  .ds-c-app-shell__floating,
  .ds-c-app-shell__scrim,
  .ds-c-app-shell__bottomNav,
  .ds-c-app-shell__skip,
  .ds-c-appBar,
  .ds-c-topNav,
  .ds-c-side-nav,
  .ds-c-toast,
  .ds-c-toast-region,
  .ds-c-snackbar,
  .ds-c-drawer,
  .ds-c-drawer-backdrop,
  .ds-c-dialog__scrim {
    display: none !important;
  }

  /* 3) Content takes the full page — the shell's viewport-height scroll
   * containers exist for the screen, not the page; left alone they clip
   * everything past the first screenful instead of letting print paginate.
   */
  .ds-c-app-shell {
    display: block !important;
    height: auto !important;
    width: 100% !important;
  }

  .ds-c-app-shell__body {
    display: block !important;
    overflow: visible !important;
  }

  .ds-c-app-shell__main {
    width: 100% !important;
    max-width: 100% !important;
    height: auto !important;
    overflow: visible !important;
    padding: 0 !important;
  }

  main {
    width: 100%;
    max-width: 100%;
  }

  /* 4) Never split a card, table row or kanban card across a page break. */
  .ds-c-card,
  .ds-c-kanban__card,
  .ds-c-table tr,
  .ds-c-table-pager {
    break-inside: avoid;
  }

  /* 5) Tables/grids: nothing should clip — the screen's scroll viewport has
   * no meaning on paper, so let content run to its natural size and let
   * print paginate it.
   */
  .ds-c-table-wrapper,
  .ds-c-dataGrid {
    overflow: visible !important;
    max-height: none !important;
    max-width: 100% !important;
  }

  /* 6) Links print their own text, not a synthesized URL suffix. */
  a[href]::after,
  a[href]::before {
    content: '' !important;
  }

  /* 7) Status color still carries meaning on paper — badges keep their
   * background/dot color instead of the browser's ink-saving default of
   * dropping backgrounds under print.
   */
  .ds-c-badge,
  .ds-c-status-badge,
  .ds-c-status-badge__dot {
    print-color-adjust: exact;
    -webkit-print-color-adjust: exact;
  }

  /* 8) Elevation is a screen affordance with no meaning on paper — it just
   * prints as a gray smudge around the element.
   */
  *, *::before, *::after {
    box-shadow: none !important;
    text-shadow: none !important;
  }
}

/* ------------------------------------------------------------
 * data-ds-motion knob (#172) — lets a consumer override the OS
 * prefers-reduced-motion signal explicitly, independent of it:
 *   [data-ds-motion="never"]  forces the reduced-motion floor above
 *                             regardless of OS preference (e.g. an in-app
 *                             "reduce motion" toggle that isn't tied to the
 *                             OS setting).
 *   [data-ds-motion="always"] opts OUT of the floor even when the OS asks
 *                             for reduced motion (only for animation that
 *                             has been reviewed for vestibular safety).
 * Default (attribute absent on any ancestor): unchanged — respect
 * prefers-reduced-motion, as above.
 * ------------------------------------------------------------ */
[data-ds-motion="never"],
[data-ds-motion="never"] *,
[data-ds-motion="never"] *::before,
[data-ds-motion="never"] *::after {
  animation-duration: 0.01ms !important;
  animation-iteration-count: 1 !important;
  transition-duration: 0.01ms !important;
  scroll-behavior: auto !important;
}

@media (prefers-reduced-motion: reduce) {
  [data-ds-motion="always"],
  [data-ds-motion="always"] *,
  [data-ds-motion="always"] *::before,
  [data-ds-motion="always"] *::after {
    animation-duration: revert !important;
    animation-iteration-count: revert !important;
    transition-duration: revert !important;
    scroll-behavior: revert !important;
  }
}

/* ============================================================
 * prefers-contrast: more — text/border floor (GH-1521)
 * ============================================================
 * MEASURED (2026-09-08): repo-wide grep for "prefers-contrast" returned 0
 * hits before this block; a Playwright render sweep (16 stories x 2
 * viewports x prefers-contrast:more) found 0 changed computed colours on
 * any `.ds-c-*` box — the signal was never handled at any layer, on any
 * leg. Filed as GH-1521.
 *
 * SHAPE: a token layer, not component CSS — the same way dark mode is
 * `[data-theme="dark"]` rather than a hundred component overrides. Every
 * value below is an EXISTING core role already shipped in tokens/core/
 * color.json for every brand; nothing here is a new colour.
 *
 * `npm run check:contrastsize`'s own GATE findings (every text pair, plus
 * the curated core outline/focusRing non-text pairs) are ALREADY 0 across
 * all 6 brands x 2 modes at plain AA (verified live on this branch,
 * 2026-09-08 — see JOURNAL.md). So this floor's job is not "fix a
 * failure" — it is "give the user who explicitly asked for MORE contrast
 * something stronger than the AA floor everyone else already gets", the
 * same way dark mode gives a user who asks for it something different
 * from what everyone else gets.
 *
 * Two roles move, both verified with scripts/lib/color.js's own contrast()
 * against dist/generated/ds-<brand>-<mode>.css's resolved values, STRICTLY
 * stronger against core.surface in every brand x mode this repo ships,
 * except one documented exception:
 *   - onSurfaceSubtle -> onSurfaceVariant (secondary/tertiary ink converge
 *     on the stronger of the two existing ink tiers). Measured +2.0 to
 *     +6.2:1 across all 12 brand x mode combinations (unchanged, never
 *     weaker, on takt/dark, where the two tokens already share one value).
 *   - outline -> outlineStrong (the default border/input outline takes the
 *     brand's own "emphasized outline" role). Measured stronger in 11 of
 *     12 brand x mode combinations; takt/dark is the one exception — its
 *     outlineStrong (#948CB0) measures 5.25:1 against surface, WEAKER than
 *     its plain outline (#B2AACF) at 7.56:1. Excluded below (higher-
 *     specificity attribute selector) rather than shipped as a regression.
 *     Left as its own follow-up, out of this row's scope: takt's dark
 *     outlineStrong role looks like it may not actually be stronger than
 *     outline — see QUESTIONS 417.
 *
 * DELIBERATELY NOT DONE (recorded as open decisions, QUESTIONS 417 —
 * shipping the safest defensible subset rather than inventing a palette):
 *   - Link ink was NOT swapped onto its hover/active shade. Every link role
 *     already clears AA (4.63:1-15.62:1 measured); moving the resting
 *     state onto the hover shade would erase the resting/hover distinction
 *     — a genuine interaction-semantics change this row does not have the
 *     authority to make unasked.
 *   - outlineVariant (hairline dividers) was left alone. WCAG 1.4.11 does
 *     not reach decorative separators — the same carve-out check-
 *     contrast-at-size.mjs's own "NON-TEXT GATING SCOPE" comment documents
 *     for its 932 advisory (non-gating) findings — and boosting it would
 *     turn every hairline into a visible border, a visual-weight decision
 *     this row is not making unilaterally.
 *   - Focus-ring width/colour was left untouched. CORE_NONTEXT_PAIRS
 *     already gates focusRing/focusRingInverse at 3:1 and both pass on
 *     every brand x mode today; a width-only bump would not raise a
 *     MEASURED ratio (a self-comparison is not a verdict) and risks
 *     colliding with the box-shadow rings several components already draw.
 *   - No component-level (tokens/components/*.json) role was touched.
 *     Every one of those ~111 files' colour tokens is baked to a literal
 *     hex at build time (confirmed against dist/generated/*.css — a plain
 *     value, never a var() reference), so this floor's reach is exactly:
 *     dist/base/ds-base.css itself, plus the ~365 component CSS files
 *     across all three legs that were ALREADY reading
 *     var(--ds-color-onSurfaceSubtle) / var(--ds-color-outline) directly
 *     rather than through their own component token (grep, 2026-09-08).
 *     Reaching the component-token layer too is a real design decision —
 *     which of ~111 files' roles shift, and by how much — this row does
 *     not have the authority to make unasked; recorded as QUESTIONS 417.
 *
 * `!important` ON EVERY CUSTOM PROPERTY BELOW, for the exact reason the
 * print block above already documents: a brand's own theme file scopes its
 * DARK-mode values to `[data-theme="dark"][data-brand="X"]` (two attribute
 * selectors, specificity 0,2,0) — MORE specific than the plain `:root`
 * here (0,1,0) — and would otherwise win regardless of this file's later
 * source order. Caught live: an un-important first draft of this block
 * measured correctly against the generated CSS text but rendered as NO
 * CHANGE for 5 of 6 brands' dark mode in `scripts/check-prefers-
 * contrast.mjs` (aurora/degoma/harbor/meadow/takt dark all failed;
 * starter/dark and every LIGHT file passed, because starter's dark file
 * and every brand's light file scope at plain `[data-theme="dark"]` /
 * `[data-brand="X"]`, one attribute selector, 0,1,0 — a tie this file's
 * later source order wins without needing `!important`). Rule 22 (never
 * green by text) is why the rendered check exists at all; the text-level
 * ratio math below was correct the whole time.
 *
 * Mirrors: React's dsTheme.ts MuiCssBaseline overrides (React is the one
 * leg that does not load this file directly — same reason the forced-
 * colors floor below is mirrored there). Blazor and Angular pick this
 * block up automatically (Blazor via the tokens:build mirror step, Angular
 * via its direct `styles` load of this file).
 * ------------------------------------------------------------ */
@media (prefers-contrast: more) {
  :root {
    --ds-color-onSurfaceSubtle: var(--ds-color-onSurfaceVariant) !important;
    --ds-color-outline: var(--ds-color-outlineStrong) !important;
  }

  /* takt/dark exception — see header comment. Higher specificity than the
   * plain `:root` rule above (two attribute selectors beat one pseudo-
   * class) AND `!important`, so this wins regardless of source order.
   * #B2AACF is not a new colour: it is takt/dark's own EXISTING
   * `--ds-color-outline` value (dist/generated/ds-takt-dark.css), restated
   * so this brand keeps its already-stronger default instead of trading
   * down to outlineStrong. */
  [data-brand="takt"][data-theme="dark"] {
    --ds-color-outline: #B2AACF !important;
  }
}

/* ============================================================
 * Windows High Contrast / forced-colors floor (GH-918, GH-919)
 * ============================================================
 * DELIBERATELY LAST IN THIS FILE. Everything above is the pre-existing
 * baseline; this section is a self-contained block so parallel additions
 * (e.g. the @media print block, GH-921) merge without touching it.
 *
 * WHY. Windows High Contrast Mode (and any UA honouring `forced-colors`)
 * REPLACES every author colour with the user's system palette. An element
 * whose only visual boundary is `background-color` therefore loses its
 * boundary entirely: a card, a chip, a nav item and the page behind them all
 * flatten to `Canvas`. The UI sweep measured 9-45 such "background-only, no
 * border" interactive elements per screen across the Takt and Degoma screens
 * on every leg (GH-919), because forced-colors support was a per-component
 * opt-in that only 16 of ~210 components had ever taken (GH-918: zero of them
 * on Angular at the time it was filed).
 *
 * This block is to forced-colors what the reduced-motion reset above is to
 * motion: the SYSTEM-WIDE FLOOR (ADR-016 amended by ADR-019). Per-component
 * `@media (forced-colors: active)` rules stay as defence-in-depth where they
 * already express intent - they load after this file on every leg and are
 * written at >= this block's specificity, so they still win. This catches
 * everything else, including vendor and consumer markup that carries the
 * documented `ds-c-*` class markers (GH-864 made those markers literal and
 * unhashed on React too, which is what lets a document-level sheet reach
 * React components at all).
 *
 * RULES OF THE ROAD for anything added here:
 *  - Boundaries are `border`, never `box-shadow` - shadows are not painted in
 *    forced-colors mode. Focus is `outline`, never a box-shadow-only ring.
 *  - `border-box` is set at the top of this file, so adding a border in
 *    forced-colors mode does not move any outer layout.
 *  - `forced-color-adjust: none` is a LAST RESORT and only where meaning is
 *    carried by colour AND a text equivalent exists next to it (a legend, a
 *    label). It is INHERITED, which is why the `auto` default below sits on
 *    `html` and not on `*` - a universal `auto` would break every component
 *    that opts a subtree out.
 *  - Prefer marker classes over element selectors, so a consumer's own markup
 *    is not repainted.
 *
 * Mirrors: the Blazor RCL copy of this file, dsTheme.ts's MuiCssBaseline
 * overrides (React), and the Angular Storybook's direct load - all locked by
 * __tests__/ds-baseline.test.js. Component coverage is locked by
 * __tests__/forced-colors-coverage.test.js.
 * ------------------------------------------------------------ */
@media (forced-colors: active) {
  /* ---- System-colour aliases -----------------------------------------
   * Named seams so component CSS never has to remember WHICH system keyword
   * means "selected" or "disabled". These are NOT themable design tokens -
   * the values are CSS system colours the OS supplies, and a brand cannot
   * override them. They exist for readability and for one-place correction
   * if a keyword is ever deprecated (`ButtonFace`/`ButtonText` already
   * absorbed the old `-ms-*` set). Declared and consumed inside this file,
   * so check:tokenusage sees them as an assigned local channel, not a
   * phantom manifest token.
   * -------------------------------------------------------------------- */
  :root {
    --ds-fc-text: CanvasText;
    --ds-fc-canvas: Canvas;
    --ds-fc-buttonText: ButtonText;
    --ds-fc-buttonFace: ButtonFace;
    --ds-fc-selected: Highlight;
    --ds-fc-onSelected: HighlightText;
    --ds-fc-link: LinkText;
    --ds-fc-disabled: GrayText;
    --ds-fc-hairline: 1px;
    --ds-fc-focusWidth: 2px;
  }

  /* The document default: let the UA force colours. Stated explicitly so the
   * exceptions below read as exceptions. On `html` (not `*`) because the
   * property inherits - see the note above. */
  html {
    forced-color-adjust: auto;
  }

  /* ---- Focus -----------------------------------------------------------
   * A box-shadow focus ring is invisible in forced-colors mode, and several
   * DS components use one. An outline is always painted, so the floor draws
   * one on every focusable element; components that already draw an outline
   * simply overpaint it with the same geometry.
   * -------------------------------------------------------------------- */
  :focus-visible {
    /* `!important` for the same reason the reduced-motion reset above uses it:
     * a FLOOR that any component can defeat is not a floor. Several components
     * legitimately write `outline: none` and draw their ring with box-shadow —
     * which forced-colors does not paint at all, leaving keyboard users with
     * no focus indicator whatsoever. The offset is deliberately NOT important,
     * so a component can still tune where the ring sits. */
    outline: var(--ds-fc-focusWidth) solid var(--ds-fc-selected) !important;
    outline-offset: var(--ds-fc-hairline);
  }

  /* ---- Elevated / floating surfaces ------------------------------------
   * These float over the page and are separated from it by a shadow, which
   * forced-colors does not paint. Without a border they merge into whatever
   * they cover.
   * -------------------------------------------------------------------- */
  .ds-c-popover,
  .ds-c-tooltip,
  .ds-c-hoverCard,
  .ds-c-menu,
  .ds-c-context-menu,
  .ds-c-moreMenu,
  .ds-c-navHeadingMenu,
  .ds-c-listbox,
  .ds-c-dialog,
  .ds-c-popover-dialog,
  .ds-c-drawer,
  .ds-c-entityDrawer,
  .ds-c-bottom-sheet,
  .ds-c-full-screen-modal,
  .ds-c-modal-chrome-shell,
  .ds-c-lightbox,
  .ds-c-snackbar,
  .ds-c-toast,
  .ds-c-toast-region,
  .ds-c-cmdk,
  .ds-c-window,
  .ds-c-exit-prompt,
  .ds-c-floating-toolbar,
  .ds-c-color-picker,
  .ds-c-color-gradient,
  .ds-c-color-palette,
  .ds-c-datetime-picker,
  .ds-c-date-range-picker,
  .ds-c-timepicker,
  .ds-c-time-clock,
  .ds-c-digital-clock,
  .ds-c-typeahead,
  .ds-c-comboBox,
  .ds-c-multiselect,
  .ds-c-speed-dial,
  .ds-c-overlay,
  .ds-c-drawer-backdrop,
  .ds-c-app-shell__skip,
  .ds-c-loading-overlay {
    border: var(--ds-fc-hairline) solid var(--ds-fc-text);
    background-color: var(--ds-fc-canvas);
  }

  /* ---- Bounded surfaces (cards, panels, banners) -----------------------
   * The single largest contributor to the sweep's "background-only, no
   * border" count: every card-shaped container in the system separates
   * itself from the page with a fill and (sometimes) a shadow.
   * -------------------------------------------------------------------- */
  .ds-c-card,
  .ds-c-selectableCard,
  .ds-c-statCard,
  .ds-c-contactCard,
  .ds-c-module-card,
  .ds-c-paper,
  .ds-c-tile,
  .ds-c-panel-bar,
  .ds-c-panel-bar-item,
  .ds-c-alert,
  .ds-c-info-banner,
  .ds-c-empty-state,
  .ds-c-blockquote,
  .ds-c-citation,
  .ds-c-code-block,
  .ds-c-kbd,
  .ds-c-kbd-group,
  .ds-c-media,
  .ds-c-mediaTheme,
  .ds-c-thumbnail,
  .ds-c-image,
  .ds-c-chat-message,
  .ds-c-chat-system,
  .ds-c-chat-toolcall,
  .ds-c-chat-toolcalls,
  .ds-c-chat-composer,
  .ds-c-chat-tokenized,
  .ds-c-chat-dictation,
  .ds-c-checklist,
  .ds-c-metadataList,
  .ds-c-definitionList,
  .ds-c-form-section,
  .ds-c-formRow,
  .ds-c-collapsibleGroup,
  .ds-c-accordion,
  .ds-c-accordion-item,
  .ds-c-collapse,
  .ds-c-module-sidebar,
  .ds-c-side-nav,
  .ds-c-toolbar,
  .ds-c-bulkActionBar,
  .ds-c-unsavedChangesBar,
  .ds-c-signature,
  .ds-c-upload,
  .ds-c-transferList,
  .ds-c-treeList,
  .ds-c-treeView,
  .ds-c-kanban,
  .ds-c-calendar,
  .ds-c-month-cal,
  .ds-c-carousel,
  .ds-c-image-list,
  .ds-c-splitter,
  .ds-c-table-wrapper,
  .ds-c-timeline,
  .ds-c-wizard,
  .ds-c-qrcode,
  .ds-c-barcode,
  .ds-c-highlighter,
  .ds-c-outline,
  .ds-c-powerSearch,
  .ds-c-overflowList,
  .ds-c-validation-summary,
  .ds-c-validation-message,
  .ds-c-loader,
  .ds-c-skeleton,
  .ds-c-spinner,
  .ds-c-circular-progress {
    border: var(--ds-fc-hairline) solid var(--ds-fc-text);
  }

  /* ---- Pills: chips, badges, tags, avatars -----------------------------
   * A pill is a fill and a radius; forced-colors leaves only the radius.
   * -------------------------------------------------------------------- */
  .ds-c-chip,
  .ds-c-chip-set,
  .ds-c-badge,
  .ds-c-badge-pill,
  .ds-c-tag,
  .ds-c-user-chip,
  .ds-c-avatar,
  .ds-c-avatar-wrap,
  .ds-c-avatar-group,
  .ds-c-arealogo,
  .ds-c-brand-mark,
  .ds-c-logo,
  .ds-c-timestamp,
  .ds-c-item,
  .ds-c-list-item,
  .ds-c-menu-item,
  .ds-c-tokenizer,
  .ds-c-relevance-toggle {
    border: var(--ds-fc-hairline) solid var(--ds-fc-text);
  }

  /* ---- Status badge: meaning is carried by colour ----------------------
   * The one place the DS deliberately opts OUT of forced colours. A status
   * badge's hue IS its meaning; flattening every status to the same
   * Canvas/CanvasText pair makes "Betalt" and "Restance" identical at a
   * glance. The badge always renders its own text label, and its
   * container/on-container pair is contrast-checked by palette:check, so it
   * is a self-consistent island: `forced-color-adjust: none` keeps that
   * island, and the CanvasText border re-anchors it to the system palette so
   * it still reads as a deliberate object rather than a rendering artefact.
   * The dot is treated the same way in the component CSS on all three legs.
   * -------------------------------------------------------------------- */
  .ds-c-status-badge {
    forced-color-adjust: none;
    border: var(--ds-fc-hairline) solid var(--ds-fc-text);
  }

  /* Meaning-carrying dots, same reasoning one level down: the dot's hue IS the
   * signal (it sits beside a text label that says the same thing), so it keeps
   * its fill and takes a system-colour ring instead of being flattened to one
   * indistinguishable grey. `outline`, not `border`: the dot is a fixed,
   * token-sized box and a border would eat a quarter of it. */
  .ds-c-badge-pill__dot,
  .ds-c-status-badge__dot {
    forced-color-adjust: none;
    outline: var(--ds-fc-hairline) solid var(--ds-fc-text);
  }

  /* ---- Buttons and button-shaped controls ------------------------------
   * DsButton carries its own rule (it has to special-case the text variant);
   * everything else button-shaped is bounded here.
   * -------------------------------------------------------------------- */
  .ds-c-fab,
  .ds-c-split-button,
  .ds-c-dropdown-button,
  .ds-c-button-group,
  .ds-c-mobile-nav-toggle,
  .ds-c-scroll-to-top,
  .ds-c-navIcon,
  .ds-c-pagination,
  .ds-c-paginator,
  .ds-c-table-pager,
  .ds-c-table-columns {
    border: var(--ds-fc-hairline) solid var(--ds-fc-buttonText);
  }

  /* ---- Specificity bump ------------------------------------------------
   * A bare class selector from THIS file loses to a component's own CSS in
   * three routine situations, all of which were measured live on the Takt and
   * Degoma screens before this rule existed:
   *   - Blazor scoped CSS compiles every component rule with a `[b-…]`
   *     attribute (`.ds-c-card` → 0,1,1), so a card whose brand sets
   *     `--ds-card-borderWidth: 0` stayed borderless on the Degoma screens;
   *   - Angular's emulated encapsulation does the same with `[_ngcontent-…]`,
   *     which is how DsFab's own `border: none` survived the floor;
   *   - Angular Material / MUI paint `border: none` on their own
   *     `.mdc-fab` / `.mat-mdc-button-base` classes from a stylesheet
   *     injected AFTER this one, winning the equal-specificity tie on order.
   * And a CONSUMER can zero the boundary through the token itself — the
   * Degoma recipe cards set `--ds-card-borderWidth: 0px`, so DsCard's own
   * `border: var(--ds-card-borderWidth) …` computed to nothing.
   *
   * `!important` here, for the same reason the reduced-motion reset above
   * uses it: a floor a later stylesheet can silently defeat is not a floor,
   * and no amount of class-repetition wins reliably when the competing
   * selector is itself attribute-qualified. Scoped DELIBERATELY NARROW —
   * only the components measured as still-bare on the Takt/Degoma screens —
   * because `!important` would also beat a per-component forced-colors rule.
   * NONE of the components listed here has one today; if one grows a
   * forced-colors `border` rule of its own, drop it from this list rather
   * than fighting it. */
  .ds-c-card,
  .ds-c-selectableCard,
  .ds-c-statCard,
  .ds-c-contactCard,
  .ds-c-module-card,
  .ds-c-paper,
  .ds-c-tile {
    border: var(--ds-fc-hairline) solid var(--ds-fc-text) !important;
  }

  .ds-c-fab,
  .ds-c-mobile-nav-toggle,
  .ds-c-scroll-to-top,
  .ds-c-split-button,
  .ds-c-dropdown-button,
  .ds-c-navIcon {
    border: var(--ds-fc-hairline) solid var(--ds-fc-buttonText) !important;
  }

  /* ---- Fields and native controls --------------------------------------
   * The DS replaces the UA control border with its own token-bound one, so
   * in forced-colors mode the control's outline is whatever the DS painted -
   * which is flattened away. The generic element floor is what actually
   * moves the sweep number; the marker-class selectors ahead of it document
   * (and pin) which DS field families it is standing in for.
   * -------------------------------------------------------------------- */
  .ds-c-field input,
  .ds-c-field select,
  .ds-c-field textarea,
  .ds-c-form-field input,
  .ds-c-form-field select,
  .ds-c-form-field textarea,
  .ds-c-form-section-stack input,
  .ds-c-floatingLabel input,
  .ds-c-textfield__input,
  .ds-c-textfield__select,
  .ds-c-select__input,
  .ds-c-numberinput input,
  .ds-c-masked-input input,
  .ds-c-date-input input,
  .ds-c-time-input input,
  .ds-c-datetime input,
  .ds-c-datetime-range input,
  .ds-c-date-range-input input,
  .ds-c-time-range input,
  .ds-c-textarea-autosize,
  .ds-c-checkbox__control,
  .ds-c-checkbox-list input,
  .ds-c-radio-group input,
  .ds-c-rating input,
  input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="hidden"]),
  select,
  textarea {
    border: var(--ds-fc-hairline) solid var(--ds-fc-buttonText);
  }

  /* ---- Links read as links --------------------------------------------
   * The DS paints link ink from brand tokens, which forced-colors flattens
   * to CanvasText - making a link indistinguishable from body text. LinkText
   * is the system's own "this is a link" colour.
   * -------------------------------------------------------------------- */
  .ds-c-link,
  .ds-c-breadcrumb a[href],
  .ds-c-markdown a[href],
  .ds-c-prose a[href],
  .ds-c-code a[href] {
    color: var(--ds-fc-link);
  }

  /* ---- Current / selected / pressed ------------------------------------
   * Navigation "you are here", selected rows, pressed toggles: all of them
   * are a background tint in the DS, i.e. exactly the signal forced-colors
   * removes. `outline` (not border) so nothing reflows, inset so it hugs the
   * item, `Highlight` because that is the system's own selection colour.
   * -------------------------------------------------------------------- */
  .ds-c-side-nav__item--active,
  .ds-c-tabs__tab--active,
  .ds-c-tab-bar__tab--active,
  .ds-c-topNav__item--active,
  .ds-c-navigation__item--active,
  .ds-c-bottom-navigation__item--active,
  .ds-c-sortable-table__row--selected,
  .ds-c-table__row--selected,
  .ds-c-tabs-scroller [aria-selected="true"],
  .ds-c-tabs-overflow [aria-selected="true"],
  [aria-current]:not([aria-current="false"]),
  [aria-selected="true"],
  [aria-pressed="true"] {
    outline: var(--ds-fc-focusWidth) solid var(--ds-fc-selected);
    outline-offset: calc(-1 * var(--ds-fc-focusWidth));
  }

  /* ---- Disabled --------------------------------------------------------
   * GrayText is the only colour a forced-colors palette guarantees reads as
   * "inert". Without this, a disabled control is indistinguishable from an
   * enabled one once its muted fill is flattened.
   * -------------------------------------------------------------------- */
  [disabled],
  [aria-disabled="true"],
  .ds-is-disabled {
    color: var(--ds-fc-disabled);
    border-color: var(--ds-fc-disabled);
  }

  /* ---- Data visualisation ----------------------------------------------
   * Charts are the one family where flattening destroys the data, not just
   * the chrome: every series fill collapses to one system colour, so a
   * five-series chart becomes one shape. GH-919 called for either
   * pattern-fills or a text legend.
   *
   * The DS answer is the legend. A chart that renders a legend (a `ul`/`ol`
   * sibling of the plot inside the chart root - the shape every Own chart
   * emits on React and Blazor alike) becomes a self-consistent island:
   * `forced-color-adjust: none` on the whole root keeps series hues AND the
   * legend swatches that name them in text, and the root paints its own
   * DS surface/ink pair underneath so nothing depends on the system
   * background. A chart with NO legend keeps the system flattening - colour
   * that nothing decodes is not worth an opt-out - and gets per-shape
   * CanvasText strokes so adjacent series/segments stay separable.
   * -------------------------------------------------------------------- */
  .ds-c-chart,
  .ds-c-spark-line,
  .ds-c-heatmap,
  .ds-c-treemap,
  .ds-c-funnel,
  .ds-c-pyramid,
  .ds-c-sankey,
  .ds-c-progress-summary {
    border: var(--ds-fc-hairline) solid var(--ds-fc-text);
  }

  .ds-c-chart:has(> ul),
  .ds-c-chart:has(> ol) {
    forced-color-adjust: none;
    background-color: var(--ds-color-surface);
    color: var(--ds-color-onSurface);
  }

  .ds-c-chart:not(:has(> ul)):not(:has(> ol)) svg rect,
  .ds-c-chart:not(:has(> ul)):not(:has(> ol)) svg path,
  .ds-c-chart:not(:has(> ul)):not(:has(> ol)) svg circle,
  .ds-c-chart:not(:has(> ul)):not(:has(> ol)) svg polygon,
  .ds-c-spark-line svg path,
  .ds-c-heatmap svg rect,
  .ds-c-treemap svg rect,
  .ds-c-funnel svg path,
  .ds-c-pyramid svg path,
  .ds-c-sankey svg path {
    stroke: var(--ds-fc-text);
    stroke-width: var(--ds-fc-hairline);
  }

  /* ---- Icons -----------------------------------------------------------
   * An icon drawn with a literal `fill`/`stroke` attribute keeps that value
   * under forced colours only if something opts it out; bound to
   * `currentColor` it follows the text colour the system chose, which is
   * what an icon should do. This is the floor for the DS icon set and for
   * any inline SVG a consumer drops inside a DS icon slot; a decorative
   * illustration that must keep its own palette opts out per-component, as
   * the charts above do.
   * -------------------------------------------------------------------- */
  .ds-c-icon svg,
  .ds-c-navIcon svg,
  .ds-c-side-nav__icon svg {
    fill: currentColor;
    stroke: currentColor;
  }
}
