/*!
 * @deeptrack/css — the framework-free CSS collection.
 *
 * GENERATED FILE — do not edit.
 * Authored in packages/css/src/ and produced by packages/css/tools/build.js. Bundles
 * @deeptrack/tokens' custom properties so this file is offline-complete: one <link>, no
 * further request, no build step.
 */

/*!
 * Deeptrack design tokens.
 *
 * GENERATED FILE — do not edit.
 * Authored in tokens/ and produced by `pnpm build:tokens`.
 */

:root {
  --dt-neutral-50: #f6f4f5;
  --dt-neutral-100: #e9eaea;
  --dt-neutral-200: #d7d9da;
  --dt-neutral-300: #c1c5c6;
  --dt-neutral-400: #a7acae;
  --dt-neutral-500: #8a9093;
  --dt-neutral-600: #747a7d;
  --dt-neutral-700: #565c5f;
  --dt-neutral-800: #404447;
  --dt-neutral-900: #2b2e30;
  --dt-neutral-950: #1b1d1f;
  --dt-blue-50: #f2f5fa;
  --dt-blue-100: #e3ebf4;
  --dt-blue-200: #cadbef;
  --dt-blue-300: #abc7ea;
  --dt-blue-400: #85aee0;
  --dt-blue-500: #6092ce;
  --dt-blue-600: #396eab;
  --dt-blue-700: #2b5c95;
  --dt-blue-800: #194475;
  --dt-blue-900: #002855;
  --dt-blue-950: #041d3a;
  --dt-sky-50: #f1f6f9;
  --dt-sky-100: #e2ecf1;
  --dt-sky-200: #c8dde9;
  --dt-sky-300: #a6cadf;
  --dt-sky-400: #7eb3d0;
  --dt-sky-500: #4f93b6;
  --dt-sky-600: #327da2;
  --dt-sky-700: #166384;
  --dt-sky-800: #084a65;
  --dt-sky-900: #043246;
  --dt-sky-950: #02202e;
  --dt-teal-50: #eff7f8;
  --dt-teal-100: #dfedef;
  --dt-teal-200: #c1e0e4;
  --dt-teal-300: #98cfd7;
  --dt-teal-400: #65bac6;
  --dt-teal-500: #0496a0;
  --dt-teal-600: #117b8b;
  --dt-teal-700: #0f6771;
  --dt-teal-800: #094d55;
  --dt-teal-900: #04353a;
  --dt-teal-950: #022226;
  --dt-amber-50: #fef3e5;
  --dt-amber-100: #fce6ca;
  --dt-amber-200: #fdd196;
  --dt-amber-300: #f8a815;
  --dt-amber-400: #e29a20;
  --dt-amber-500: #be8119;
  --dt-amber-600: #9b6912;
  --dt-amber-700: #7a510c;
  --dt-amber-800: #5c3c07;
  --dt-amber-900: #402803;
  --dt-amber-950: #2a1901;
  --dt-yellow-50: #fbf5e4;
  --dt-yellow-100: #f6e9c8;
  --dt-yellow-200: #f3d78c;
  --dt-yellow-300: #f3c400;
  --dt-yellow-400: #daaa08;
  --dt-yellow-500: #ad8a19;
  --dt-yellow-600: #8e7113;
  --dt-yellow-700: #6f580c;
  --dt-yellow-800: #534107;
  --dt-yellow-900: #392c03;
  --dt-yellow-950: #251c01;
  --dt-green-50: #ebfaed;
  --dt-green-100: #d6f3db;
  --dt-green-200: #acebb8;
  --dt-green-300: #6ae189;
  --dt-green-400: #26cb64;
  --dt-green-500: #1eab53;
  --dt-green-600: #168b42;
  --dt-green-700: #0f6d33;
  --dt-green-800: #095224;
  --dt-green-900: #043817;
  --dt-green-950: #02240c;
  --dt-red-50: #fff2f0;
  --dt-red-100: #fee3df;
  --dt-red-200: #fdcbc5;
  --dt-red-300: #fcaca3;
  --dt-red-400: #fb8276;
  --dt-red-500: #fa3f3a;
  --dt-red-600: #d61a20;
  --dt-red-700: #a91217;
  --dt-red-800: #810b0f;
  --dt-red-900: #5b0507;
  --dt-red-950: #3d0203;
  --dt-font-sans: "Exo", "Exo 2", system-ui, -apple-system, "Segoe UI", sans-serif;
  --dt-font-mono: "JetBrains Mono", ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  --dt-weight-regular: 400;
  --dt-weight-medium: 500;
  --dt-weight-semibold: 600;
  --dt-weight-bold: 700;
  --dt-text-2xs: 11px;
  --dt-text-xs: 12px;
  --dt-text-sm: 14px;
  --dt-text-base: 16px;
  --dt-text-lg: 18px;
  --dt-text-xl: 20px;
  --dt-text-2xl: 24px;
  --dt-text-3xl: 30px;
  --dt-text-4xl: 36px;
  --dt-text-5xl: 48px;
  --dt-text-6xl: 60px;
  --dt-text-7xl: 72px;
  --dt-leading-2xs: 14px;
  --dt-leading-xs: 18px;
  --dt-leading-sm: 20px;
  --dt-leading-base: 24px;
  --dt-leading-lg: 28px;
  --dt-leading-xl: 30px;
  --dt-leading-2xl: 32px;
  --dt-leading-3xl: 38px;
  --dt-leading-4xl: 44px;
  --dt-leading-5xl: 60px;
  --dt-leading-6xl: 72px;
  --dt-leading-7xl: 90px;
  --dt-tracking-tight: -0.02em;
  --dt-tracking-normal: 0em;
  --dt-tracking-wide: 0.02em;
  --dt-tracking-wider: 0.06em;
  --dt-space-0: 0px;
  --dt-space-0_5: 2px;
  --dt-space-1: 4px;
  --dt-space-1_5: 6px;
  --dt-space-2: 8px;
  --dt-space-2_5: 10px;
  --dt-space-3: 12px;
  --dt-space-4: 16px;
  --dt-space-5: 20px;
  --dt-space-6: 24px;
  --dt-space-8: 32px;
  --dt-space-10: 40px;
  --dt-space-12: 48px;
  --dt-space-14: 56px;
  --dt-space-16: 64px;
  --dt-space-20: 80px;
  --dt-space-24: 96px;
  --dt-radius-none: 0px;
  --dt-radius-xs: 2px;
  --dt-radius-sm: 3px;
  --dt-radius-md: 4px;
  --dt-radius-lg: 6px;
  --dt-radius-xl: 8px;
  --dt-radius-2xl: 12px;
  --dt-radius-full: 9999px;
  --dt-elevation-none: none;
  --dt-elevation-xs: 0px 1px 2px 0px rgba(27, 29, 31, 0.05);
  --dt-elevation-sm: 0px 1px 3px 0px rgba(27, 29, 31, 0.1), 0px 1px 2px -1px rgba(27, 29, 31, 0.06);
  --dt-elevation-md: 0px 4px 8px -2px rgba(27, 29, 31, 0.1), 0px 2px 4px -2px rgba(27, 29, 31, 0.06);
  --dt-elevation-lg: 0px 12px 16px -4px rgba(27, 29, 31, 0.1), 0px 4px 6px -2px rgba(27, 29, 31, 0.05);
  --dt-elevation-xl: 0px 20px 24px -4px rgba(27, 29, 31, 0.12), 0px 8px 8px -4px rgba(27, 29, 31, 0.06);
  --dt-z-base: 0;
  --dt-z-raised: 10;
  --dt-z-dropdown: 1000;
  --dt-z-sticky: 1100;
  --dt-z-overlay: 1200;
  --dt-z-modal: 1300;
  --dt-z-popover: 1400;
  --dt-z-toast: 1500;
  --dt-z-tooltip: 1600;
  --dt-duration-instant: 0ms;
  --dt-duration-fast: 150ms;
  --dt-duration-base: 200ms;
  --dt-duration-slow: 300ms;
  --dt-duration-slower: 500ms;
  --dt-ease-standard: cubic-bezier(0.2, 0, 0, 1);
  --dt-ease-decelerate: cubic-bezier(0, 0, 0, 1);
  --dt-ease-accelerate: cubic-bezier(0.3, 0, 1, 1);
  --dt-focus-width: 2px;
  --dt-focus-offset: 2px;
  --dt-focus-halo-width: 4px;
  --dt-focus-halo-alpha: 0.12;

  /* Light mode is the default. */
  --dt-background: #f6f4f5;
  --dt-surface: #ffffff;
  --dt-surface-sunken: #e9eaea;
  --dt-text: #1b1d1f;
  --dt-text-muted: #565c5f;
  --dt-border: #d7d9da;
  --dt-border-strong: #747a7d;
  --dt-focus-ring: #396eab;
  --dt-fill-blue: #002855;
  --dt-fill-blue-soft: #396eab;
  --dt-fill-sky: #4f93b6;
  --dt-fill-teal: #0496a0;
  --dt-fill-amber: #f8a815;
  --dt-fill-yellow: #f3c400;
  --dt-on-fill-blue: #ffffff;
  --dt-on-fill-blue-soft: #ffffff;
  --dt-on-fill-sky: #1b1d1f;
  --dt-on-fill-teal: #1b1d1f;
  --dt-on-fill-amber: #1b1d1f;
  --dt-on-fill-yellow: #1b1d1f;
  --dt-text-blue: #002855;
  --dt-text-sky: #166384;
  --dt-text-teal: #117b8b;
  --dt-text-amber: #5c3c07;
  --dt-text-yellow: #534107;
  --dt-success-surface: #ebfaed;
  --dt-success-border: #6ae189;
  --dt-success-text: #095224;
  --dt-success-solid: #0f6d33;
  --dt-success-on-solid: #ffffff;
  --dt-warning-surface: #fef3e5;
  --dt-warning-border: #f8a815;
  --dt-warning-text: #5c3c07;
  --dt-warning-solid: #7a510c;
  --dt-warning-on-solid: #ffffff;
  --dt-error-surface: #fff2f0;
  --dt-error-border: #fcaca3;
  --dt-error-text: #810b0f;
  --dt-error-solid: #a91217;
  --dt-error-on-solid: #ffffff;
  --dt-info-surface: #f2f5fa;
  --dt-info-border: #abc7ea;
  --dt-info-text: #194475;
  --dt-info-solid: #2b5c95;
  --dt-info-on-solid: #ffffff;
  --dt-chart-1: #396eab;
  --dt-chart-2: #5c3c07;
  --dt-chart-3: #194475;
  --dt-chart-4: #be8119;
  --dt-chart-5: #4f93b6;
  --dt-chart-6: #9b6912;
  --dt-chart-other: #747a7d;
  --dt-chart-seq-1: #cadbef;
  --dt-chart-seq-2: #abc7ea;
  --dt-chart-seq-3: #85aee0;
  --dt-chart-seq-4: #6092ce;
  --dt-chart-seq-5: #396eab;
  --dt-chart-seq-6: #2b5c95;
  --dt-chart-seq-7: #194475;
  --dt-chart-seq-8: #002855;
  --dt-chart-div-1: #7a510c;
  --dt-chart-div-2: #be8119;
  --dt-chart-div-3: #f8a815;
  --dt-chart-div-4: #e9eaea;
  --dt-chart-div-5: #abc7ea;
  --dt-chart-div-6: #6092ce;
  --dt-chart-div-7: #2b5c95;
}

/*
 * Pin light on a subtree, too — a light panel inside a page that is dark by preference. Without
 * this, a subtree could be forced dark but never back to light, and would inherit the page's
 * dark values whatever its attribute said.
 */
[data-dt-theme='light'] {
  --dt-background: #f6f4f5;
  --dt-surface: #ffffff;
  --dt-surface-sunken: #e9eaea;
  --dt-text: #1b1d1f;
  --dt-text-muted: #565c5f;
  --dt-border: #d7d9da;
  --dt-border-strong: #747a7d;
  --dt-focus-ring: #396eab;
  --dt-fill-blue: #002855;
  --dt-fill-blue-soft: #396eab;
  --dt-fill-sky: #4f93b6;
  --dt-fill-teal: #0496a0;
  --dt-fill-amber: #f8a815;
  --dt-fill-yellow: #f3c400;
  --dt-on-fill-blue: #ffffff;
  --dt-on-fill-blue-soft: #ffffff;
  --dt-on-fill-sky: #1b1d1f;
  --dt-on-fill-teal: #1b1d1f;
  --dt-on-fill-amber: #1b1d1f;
  --dt-on-fill-yellow: #1b1d1f;
  --dt-text-blue: #002855;
  --dt-text-sky: #166384;
  --dt-text-teal: #117b8b;
  --dt-text-amber: #5c3c07;
  --dt-text-yellow: #534107;
  --dt-success-surface: #ebfaed;
  --dt-success-border: #6ae189;
  --dt-success-text: #095224;
  --dt-success-solid: #0f6d33;
  --dt-success-on-solid: #ffffff;
  --dt-warning-surface: #fef3e5;
  --dt-warning-border: #f8a815;
  --dt-warning-text: #5c3c07;
  --dt-warning-solid: #7a510c;
  --dt-warning-on-solid: #ffffff;
  --dt-error-surface: #fff2f0;
  --dt-error-border: #fcaca3;
  --dt-error-text: #810b0f;
  --dt-error-solid: #a91217;
  --dt-error-on-solid: #ffffff;
  --dt-info-surface: #f2f5fa;
  --dt-info-border: #abc7ea;
  --dt-info-text: #194475;
  --dt-info-solid: #2b5c95;
  --dt-info-on-solid: #ffffff;
  --dt-chart-1: #396eab;
  --dt-chart-2: #5c3c07;
  --dt-chart-3: #194475;
  --dt-chart-4: #be8119;
  --dt-chart-5: #4f93b6;
  --dt-chart-6: #9b6912;
  --dt-chart-other: #747a7d;
  --dt-chart-seq-1: #cadbef;
  --dt-chart-seq-2: #abc7ea;
  --dt-chart-seq-3: #85aee0;
  --dt-chart-seq-4: #6092ce;
  --dt-chart-seq-5: #396eab;
  --dt-chart-seq-6: #2b5c95;
  --dt-chart-seq-7: #194475;
  --dt-chart-seq-8: #002855;
  --dt-chart-div-1: #7a510c;
  --dt-chart-div-2: #be8119;
  --dt-chart-div-3: #f8a815;
  --dt-chart-div-4: #e9eaea;
  --dt-chart-div-5: #abc7ea;
  --dt-chart-div-6: #6092ce;
  --dt-chart-div-7: #2b5c95;
}

/* Opt in explicitly. */
[data-dt-theme='dark'] {
  --dt-background: #1b1d1f;
  --dt-surface: #2b2e30;
  --dt-surface-sunken: #1b1d1f;
  --dt-text: #f6f4f5;
  --dt-text-muted: #c1c5c6;
  --dt-border: #404447;
  --dt-border-strong: #747a7d;
  --dt-focus-ring: #7eb3d0;
  --dt-fill-blue: #396eab;
  --dt-fill-blue-soft: #194475;
  --dt-fill-sky: #4f93b6;
  --dt-fill-teal: #0496a0;
  --dt-fill-amber: #f8a815;
  --dt-fill-yellow: #f3c400;
  --dt-on-fill-blue: #ffffff;
  --dt-on-fill-blue-soft: #ffffff;
  --dt-on-fill-sky: #1b1d1f;
  --dt-on-fill-teal: #1b1d1f;
  --dt-on-fill-amber: #1b1d1f;
  --dt-on-fill-yellow: #1b1d1f;
  --dt-text-blue: #abc7ea;
  --dt-text-sky: #a6cadf;
  --dt-text-teal: #98cfd7;
  --dt-text-amber: #f8a815;
  --dt-text-yellow: #f3c400;
  --dt-success-surface: #02240c;
  --dt-success-border: #095224;
  --dt-success-text: #6ae189;
  --dt-success-solid: #26cb64;
  --dt-success-on-solid: #1b1d1f;
  --dt-warning-surface: #2a1901;
  --dt-warning-border: #5c3c07;
  --dt-warning-text: #f8a815;
  --dt-warning-solid: #e29a20;
  --dt-warning-on-solid: #1b1d1f;
  --dt-error-surface: #3d0203;
  --dt-error-border: #810b0f;
  --dt-error-text: #fcaca3;
  --dt-error-solid: #fb8276;
  --dt-error-on-solid: #1b1d1f;
  --dt-info-surface: #041d3a;
  --dt-info-border: #194475;
  --dt-info-text: #abc7ea;
  --dt-info-solid: #85aee0;
  --dt-info-on-solid: #1b1d1f;
  --dt-chart-1: #85aee0;
  --dt-chart-2: #f8a815;
  --dt-chart-3: #cadbef;
  --dt-chart-4: #fdd196;
  --dt-chart-5: #0496a0;
  --dt-chart-6: #be8119;
  --dt-chart-other: #a7acae;
  --dt-chart-seq-1: #194475;
  --dt-chart-seq-2: #2b5c95;
  --dt-chart-seq-3: #396eab;
  --dt-chart-seq-4: #6092ce;
  --dt-chart-seq-5: #85aee0;
  --dt-chart-seq-6: #abc7ea;
  --dt-chart-seq-7: #cadbef;
  --dt-chart-seq-8: #e3ebf4;
  --dt-chart-div-1: #f8a815;
  --dt-chart-div-2: #be8119;
  --dt-chart-div-3: #7a510c;
  --dt-chart-div-4: #404447;
  --dt-chart-div-5: #2b5c95;
  --dt-chart-div-6: #6092ce;
  --dt-chart-div-7: #abc7ea;
}

/* Or follow the platform, unless the page has explicitly asked for light. */
@media (prefers-color-scheme: dark) {
  :root:not([data-dt-theme='light']) {
    --dt-background: #1b1d1f;
    --dt-surface: #2b2e30;
    --dt-surface-sunken: #1b1d1f;
    --dt-text: #f6f4f5;
    --dt-text-muted: #c1c5c6;
    --dt-border: #404447;
    --dt-border-strong: #747a7d;
    --dt-focus-ring: #7eb3d0;
    --dt-fill-blue: #396eab;
    --dt-fill-blue-soft: #194475;
    --dt-fill-sky: #4f93b6;
    --dt-fill-teal: #0496a0;
    --dt-fill-amber: #f8a815;
    --dt-fill-yellow: #f3c400;
    --dt-on-fill-blue: #ffffff;
    --dt-on-fill-blue-soft: #ffffff;
    --dt-on-fill-sky: #1b1d1f;
    --dt-on-fill-teal: #1b1d1f;
    --dt-on-fill-amber: #1b1d1f;
    --dt-on-fill-yellow: #1b1d1f;
    --dt-text-blue: #abc7ea;
    --dt-text-sky: #a6cadf;
    --dt-text-teal: #98cfd7;
    --dt-text-amber: #f8a815;
    --dt-text-yellow: #f3c400;
    --dt-success-surface: #02240c;
    --dt-success-border: #095224;
    --dt-success-text: #6ae189;
    --dt-success-solid: #26cb64;
    --dt-success-on-solid: #1b1d1f;
    --dt-warning-surface: #2a1901;
    --dt-warning-border: #5c3c07;
    --dt-warning-text: #f8a815;
    --dt-warning-solid: #e29a20;
    --dt-warning-on-solid: #1b1d1f;
    --dt-error-surface: #3d0203;
    --dt-error-border: #810b0f;
    --dt-error-text: #fcaca3;
    --dt-error-solid: #fb8276;
    --dt-error-on-solid: #1b1d1f;
    --dt-info-surface: #041d3a;
    --dt-info-border: #194475;
    --dt-info-text: #abc7ea;
    --dt-info-solid: #85aee0;
    --dt-info-on-solid: #1b1d1f;
    --dt-chart-1: #85aee0;
    --dt-chart-2: #f8a815;
    --dt-chart-3: #cadbef;
    --dt-chart-4: #fdd196;
    --dt-chart-5: #0496a0;
    --dt-chart-6: #be8119;
    --dt-chart-other: #a7acae;
    --dt-chart-seq-1: #194475;
    --dt-chart-seq-2: #2b5c95;
    --dt-chart-seq-3: #396eab;
    --dt-chart-seq-4: #6092ce;
    --dt-chart-seq-5: #85aee0;
    --dt-chart-seq-6: #abc7ea;
    --dt-chart-seq-7: #cadbef;
    --dt-chart-seq-8: #e3ebf4;
    --dt-chart-div-1: #f8a815;
    --dt-chart-div-2: #be8119;
    --dt-chart-div-3: #7a510c;
    --dt-chart-div-4: #404447;
    --dt-chart-div-5: #2b5c95;
    --dt-chart-div-6: #6092ce;
    --dt-chart-div-7: #abc7ea;
  }
}

/*
 * The reset: strips browser defaults so every base element and component starts from the same
 * baseline, on any surface — a plain report, an email client's HTML pane, a portal shell.
 *
 * Carries no token reference and no colour. It undoes the user agent; it does not brand
 * anything. Base styling (typography, colour, spacing) lives in base.css.
 */

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

* {
  margin: 0;
}

/*
 * `hidden` means hidden, whatever display a class gives the element: the behaviour script and
 * `@deeptrack/react` hide menus, panels and backdrops with the attribute, never with a class.
 */
[hidden] {
  display: none !important;
}

html {
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

body {
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

img,
picture,
video,
canvas,
svg {
  display: block;
  max-width: 100%;
}

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

button {
  background: none;
  border: none;
  cursor: pointer;
}

table {
  border-collapse: collapse;
  border-spacing: 0;
}

p,
h1,
h2,
h3,
h4,
h5,
h6 {
  overflow-wrap: break-word;
}

/*
 * `:where()` so the rule weighs nothing. As `ul[class]` it outranked every single-class
 * component rule (0,1,1 against 0,1,0) and took the padding off `.dt-menu` wherever the reset
 * was loaded — found by the MUI parity check, which renders the bundle as shipped.
 */
:where(ul[class], ol[class]) {
  list-style: none;
  padding: 0;
}

a {
  text-decoration: none;
  color: inherit;
}

@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;
  }
}

/*
 * Base elements: the bare page, styled with tokens and nothing else. Every value here is a
 * `var(--dt-*)` from `@deeptrack/tokens/css` — a page that loads base.css without the tokens
 * stylesheet renders in the browser's own defaults, never an invented colour.
 *
 * Assumes `@deeptrack/tokens/css` is already linked (the bundle at dist/deeptrackstyle.css
 * includes it; a consumer importing base.css as a module links tokens.css first).
 */

html {
  color-scheme: light dark;
}

body {
  font-family: var(--dt-font-sans);
  font-size: var(--dt-text-base);
  line-height: var(--dt-leading-base);
  font-weight: var(--dt-weight-regular);
  color: var(--dt-text);
  background-color: var(--dt-background);
}

h1,
h2,
h3,
h4,
h5,
h6 {
  font-family: var(--dt-font-sans);
  font-weight: var(--dt-weight-semibold);
  color: var(--dt-text);
}

h1 {
  font-size: var(--dt-text-4xl);
  line-height: var(--dt-leading-4xl);
}

h2 {
  font-size: var(--dt-text-3xl);
  line-height: var(--dt-leading-3xl);
}

h3 {
  font-size: var(--dt-text-2xl);
  line-height: var(--dt-leading-2xl);
}

h4 {
  font-size: var(--dt-text-xl);
  line-height: var(--dt-leading-xl);
}

h5 {
  font-size: var(--dt-text-lg);
  line-height: var(--dt-leading-lg);
}

h6 {
  font-size: var(--dt-text-base);
  line-height: var(--dt-leading-base);
  font-weight: var(--dt-weight-medium);
}

small {
  font-size: var(--dt-text-sm);
  line-height: var(--dt-leading-sm);
}

code,
pre,
kbd,
samp {
  font-family: var(--dt-font-mono);
}

a {
  color: var(--dt-text-blue);
  text-decoration: underline;
  text-underline-offset: 0.15em;
}

/* `:where()` keeps it below a component's own colour, such as a link drawn as a `.dt-button`. */
a:where(:hover) {
  color: var(--dt-fill-blue-soft);
}

hr {
  border: none;
  border-top: 1px solid var(--dt-border);
  margin: var(--dt-space-4) 0;
}

::selection {
  background-color: var(--dt-fill-blue-soft);
  color: var(--dt-on-fill-blue-soft);
}

/*
 * A single focus-visible ring, everywhere. Never `outline: none` without this replacing it —
 * the keyboard path is not optional. Two layers: a solid ring at `--dt-focus-ring`, and a soft
 * halo so the ring reads on both light and dark surfaces.
 */
:focus-visible {
  outline: var(--dt-focus-width) solid var(--dt-focus-ring);
  outline-offset: var(--dt-focus-offset);
  box-shadow: 0 0 0 var(--dt-focus-halo-width)
    rgb(from var(--dt-focus-ring) r g b / var(--dt-focus-halo-alpha));
  border-radius: var(--dt-radius-xs);
}

fieldset {
  border: 1px solid var(--dt-border);
  border-radius: var(--dt-radius-md);
  padding: var(--dt-space-4);
}

legend {
  padding: 0 var(--dt-space-1);
  font-weight: var(--dt-weight-medium);
}

label {
  font-size: var(--dt-text-sm);
  font-weight: var(--dt-weight-medium);
  color: var(--dt-text);
}

/*
 * `border-strong`, not the hairline: a form control's edge is what tells a reader where it is,
 * and WCAG 1.4.11 asks 3:1 of it. The contrast gate holds `border-strong` to that; the hairline
 * is measured as decoration and not gated.
 */
input,
textarea,
select {
  border: 1px solid var(--dt-border-strong);
  border-radius: var(--dt-radius-sm);
  padding: var(--dt-space-2) var(--dt-space-3);
  background-color: var(--dt-surface);
  color: var(--dt-text);
}

input::placeholder,
textarea::placeholder {
  color: var(--dt-text-muted);
}

input:disabled,
textarea:disabled,
select:disabled {
  background-color: var(--dt-surface-sunken);
  color: var(--dt-text-muted);
  cursor: not-allowed;
}

/*
 * Layout primitives — the four shapes a server-rendered page is built from, so a template never
 * needs a utility class to place its content:
 *
 *  - `.dt-container`: the page's measure, centred, with an inline gutter. `--narrow` for a form
 *    or prose, `--full` for a table that needs the whole width.
 *  - `.dt-stack`: children one above the other, separated by one gap and nothing else — their own
 *    block margins are cleared, so a heading and a paragraph space alike. `--sm`, `--lg`, `--xl`.
 *  - `.dt-cluster`: children side by side, wrapping when they run out of room — a toolbar, a row
 *    of badges, a form's actions. `--between` pushes the ends apart; `--end` packs them right.
 *    `--sm` and `--lg` change the gap.
 *  - `.dt-grid`: equal columns that reflow by themselves. `--cols-2`, `-3`, `-4` and `-6` set the
 *    most columns there may be (3 by default); a column is never narrower than `--dtc-grid-min`
 *    (16rem), so a phone gets one, a tablet two and a desktop the full count, with no breakpoint.
 *
 * A primitive's own settings are `--dtc-*` custom properties, never `--dt-*`: that namespace is the
 * tokens' alone, so nothing that reads it mistakes a component's setting for a design value.
 *
 * `.dt-visually-hidden` keeps a label for assistive technology while drawing nothing — the name
 * of an icon-only button, for one.
 *
 * Markup:
 *   <div class="dt-container">
 *     <div class="dt-stack">
 *       <div class="dt-cluster dt-cluster--between"><h1>Plants</h1><button …>Add</button></div>
 *       <div class="dt-grid dt-grid--cols-3">
 *         <div class="dt-card">…</div><div class="dt-card">…</div><div class="dt-card">…</div>
 *       </div>
 *     </div>
 *   </div>
 */

.dt-container {
  box-sizing: border-box;
  width: 100%;
  max-width: 80rem;
  margin-inline: auto;
  padding-inline: var(--dt-space-4);
}

.dt-container--narrow {
  max-width: 48rem;
}

.dt-container--full {
  max-width: none;
}

/* The one breakpoint: the phone ends at 48rem. It is the app frame's too (app.css). */
@media (width >= 48rem) {
  .dt-container {
    padding-inline: var(--dt-space-6);
  }
}

.dt-stack {
  display: flex;
  flex-direction: column;
  gap: var(--dt-space-4);
}

.dt-stack > * {
  margin-block: 0;
}

.dt-stack--sm {
  gap: var(--dt-space-2);
}

.dt-stack--lg {
  gap: var(--dt-space-6);
}

.dt-stack--xl {
  gap: var(--dt-space-8);
}

.dt-cluster {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--dt-space-2);
}

.dt-cluster > * {
  margin-block: 0;
}

.dt-cluster--sm {
  gap: var(--dt-space-1);
}

.dt-cluster--lg {
  gap: var(--dt-space-4);
}

.dt-cluster--between {
  justify-content: space-between;
}

.dt-cluster--end {
  justify-content: flex-end;
}

/*
 * Each column is the larger of the minimum and an equal share of the row at the column count,
 * capped at the full width. `auto-fill` then fits as many as that allows, so the count falls as
 * the container narrows, and never rises above `--dtc-grid-columns`.
 */
.dt-grid {
  --dtc-grid-columns: 3;
  --dtc-grid-min: 16rem;
  --dtc-grid-gap: var(--dt-space-4);
  display: grid;
  gap: var(--dtc-grid-gap);
  grid-template-columns: repeat(
    auto-fill,
    minmax(
      min(
        100%,
        max(
          var(--dtc-grid-min),
          (100% - (var(--dtc-grid-columns) - 1) * var(--dtc-grid-gap)) / var(--dtc-grid-columns)
        )
      ),
      1fr
    )
  );
}

.dt-grid--cols-2 {
  --dtc-grid-columns: 2;
}

.dt-grid--cols-3 {
  --dtc-grid-columns: 3;
}

.dt-grid--cols-4 {
  --dtc-grid-columns: 4;
  --dtc-grid-min: 12rem;
}

.dt-grid--cols-6 {
  --dtc-grid-columns: 6;
  --dtc-grid-min: 8rem;
}

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

/*
 * `.dt-icon` — a Material Symbols Outlined icon (`@deeptrack/assets/icons/`), drawn in the colour
 * of the text around it. `--sm` is 16 px, the default 20 px, `--lg` 24 px.
 *
 * Three ways to draw one, all offline:
 *  - Django: `{% load deeptrack %}{% icon "delete" %}` inlines it.
 *  - Any page that serves the sprite, from its own origin:
 *    `<svg class="dt-icon" aria-hidden="true"><use href="…/icons/sprite.svg#icon-delete"/></svg>`,
 *    with no `viewBox` of its own: the symbol has one, and a second one moves the icon out of view.
 *  - React: `<Icon name="delete" />` inside `<IconSprite href={spriteUrl}>`.
 *
 * An icon is decorative (`aria-hidden="true"`) unless it stands alone, when it is
 * `role="img"` with an `aria-label`. An icon-only button is named by its tooltip.
 */

.dt-icon {
  display: inline-block;
  flex: none;
  width: var(--dt-space-5);
  height: var(--dt-space-5);
  vertical-align: middle;
  fill: currentColor;
}

.dt-icon--sm {
  width: var(--dt-space-4);
  height: var(--dt-space-4);
}

.dt-icon--lg {
  width: var(--dt-space-6);
  height: var(--dt-space-6);
}

/*
 * `.dt-button` — primary, secondary and ghost variants, the four status tones, and the kinds a
 * page's actions need: `--destructive` for an action that loses something, `--link` for one that
 * reads as a link in a sentence, `--sm` for a table row, and `--icon` for an icon-only button
 * (with an `aria-label`, and a `.dt-tooltip` that says the same).
 *
 * A busy button (`aria-busy="true"`, with `aria-disabled="true"`) shows a spinner beside its
 * words and takes no further press, so an action is never sent twice: the behaviour script sets
 * it on a form's submit button (`data-dt-busy`), React's `Button` with `busy`.
 *
 * `.dt-split-button` joins a main action and the menu button beside it ("Add ▾"). No inline
 * style, no hex: every colour is a token custom property, so a button never drifts from the
 * fills validated by the contrast gate.
 *
 * Markup: `<button class="dt-button dt-button--primary">Label</button>`, or an `<a>` with the same
 * classes for a link that leads somewhere rather than acts. Status tones read
 * `dt-button--success`, `--warning`, `--error`, `--info`; never rely on the tone's colour
 * alone — pair it with a word, per the status rule.
 */

.dt-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--dt-space-2);
  font-family: var(--dt-font-sans);
  font-size: var(--dt-text-sm);
  font-weight: var(--dt-weight-medium);
  line-height: var(--dt-leading-sm);
  padding: var(--dt-space-2) var(--dt-space-4);
  border-radius: var(--dt-radius-md);
  border: 1px solid transparent;
  text-decoration: none;
  cursor: pointer;
  transition:
    background-color var(--dt-duration-fast) var(--dt-ease-standard),
    border-color var(--dt-duration-fast) var(--dt-ease-standard),
    color var(--dt-duration-fast) var(--dt-ease-standard);
}

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

/* Primary: the corporate blue fill. */
.dt-button--primary {
  background-color: var(--dt-fill-blue);
  color: var(--dt-on-fill-blue);
}

.dt-button--primary:not(:disabled):hover {
  background-color: var(--dt-fill-blue-soft);
  color: var(--dt-on-fill-blue-soft);
}

/* Secondary: outlined, surface fill. */
.dt-button--secondary {
  background-color: var(--dt-surface);
  color: var(--dt-text);
  border-color: var(--dt-border-strong);
}

.dt-button--secondary:not(:disabled):hover {
  background-color: var(--dt-surface-sunken);
}

/* Ghost: no fill, no border, for a tertiary action beside a primary one. */
.dt-button--ghost {
  background-color: transparent;
  color: var(--dt-text);
}

.dt-button--ghost:not(:disabled):hover {
  background-color: var(--dt-surface-sunken);
}

.dt-button--success {
  background-color: var(--dt-success-solid);
  color: var(--dt-success-on-solid);
}

.dt-button--warning {
  background-color: var(--dt-warning-solid);
  color: var(--dt-warning-on-solid);
}

.dt-button--error {
  background-color: var(--dt-error-solid);
  color: var(--dt-error-on-solid);
}

.dt-button--info {
  background-color: var(--dt-info-solid);
  color: var(--dt-info-on-solid);
}

/* Destructive: the action loses something — deleting a plant, wiping a TBOX. */
.dt-button--destructive {
  background-color: var(--dt-error-solid);
  color: var(--dt-error-on-solid);
}

.dt-button--destructive:not(:disabled):hover {
  background-color: var(--dt-error-text);
  color: var(--dt-error-on-solid);
}

/* Link-like: an action that sits in a sentence, drawn as a link, still a button. */
.dt-button--link {
  padding-inline: 0;
  background: none;
  color: var(--dt-text-blue);
  text-decoration: underline;
  text-underline-offset: 0.15em;
}

.dt-button--link:not(:disabled):hover {
  text-decoration-thickness: 2px;
}

/* Compact: in a table row or a dense toolbar. */
.dt-button--sm {
  gap: var(--dt-space-1);
  padding: var(--dt-space-1) var(--dt-space-2);
  font-size: var(--dt-text-xs);
  line-height: var(--dt-leading-xs);
}

/* Icon-only: square, its name in `aria-label`. */
.dt-button--icon {
  padding: var(--dt-space-2);
}

.dt-button--icon.dt-button--sm {
  padding: var(--dt-space-1);
}

/* Busy: a spinner beside the words, and no second press. */
.dt-button[aria-busy='true'] {
  cursor: progress;
}

.dt-button[aria-disabled='true']:not([aria-busy='true']) {
  cursor: not-allowed;
  opacity: 0.5;
}

.dt-button[aria-busy='true']::before {
  content: '';
  flex: none;
  width: 1em;
  height: 1em;
  border: 2px solid currentcolor;
  border-inline-end-color: transparent;
  border-radius: var(--dt-radius-full);
  animation: dt-button-spin 0.8s linear infinite;
}

@keyframes dt-button-spin {
  to {
    transform: rotate(1turn);
  }
}

/* The words a button changes ("Copied"), apart from its icon. */
.dt-button__label {
  display: inline;
}

/* A main action and its menu, drawn as one control with a rule between them. */
.dt-split-button {
  display: inline-flex;
  align-items: stretch;
}

.dt-split-button > .dt-button {
  border-start-end-radius: 0;
  border-end-end-radius: 0;
}

.dt-split-button > .dt-menu-anchor > .dt-button {
  height: 100%;
  border-start-start-radius: 0;
  border-end-start-radius: 0;
  border-inline-start-color: color-mix(in srgb, currentcolor 40%, transparent);
}

/* Forced colours: the ring in the text colour, its gap in the page's. */
@media (forced-colors: active) {
  .dt-button[aria-busy='true']::before {
    border-color: CanvasText;
    border-inline-end-color: Canvas;
  }
}

/*
 * The department focus ring on the button itself, not only through base.css's global rule — a
 * Tailwind app loads the component classes without base.css, and would otherwise fall back to
 * the browser's own outline.
 */
.dt-button:focus-visible {
  outline: var(--dt-focus-width) solid var(--dt-focus-ring);
  outline-offset: var(--dt-focus-offset);
}

/* Motion is decoration here: with reduced motion asked for, state changes are instant. */
@media (prefers-reduced-motion: reduce) {
  .dt-button {
    transition: none;
  }

  /* A spinner that stands still is still a broken ring: busy, in a shape. */
  .dt-button[aria-busy='true']::before {
    animation: none;
  }
}

/*
 * `.dt-badge` — a small status chip, seeded from the P1 report's needs: a tag beside a result
 * row that reads at a glance and photocopies in grey. The tone alone never carries the
 * status — markup supplies the word.
 *
 * Markup: `<span class="dt-badge dt-badge--success">Passed</span>`.
 */

.dt-badge {
  display: inline-flex;
  align-items: center;
  gap: var(--dt-space-1);
  font-family: var(--dt-font-sans);
  font-size: var(--dt-text-xs);
  font-weight: var(--dt-weight-medium);
  line-height: var(--dt-leading-xs);
  padding: var(--dt-space-0_5) var(--dt-space-2);
  border-radius: var(--dt-radius-full);
  border: 1px solid transparent;
}

.dt-badge--neutral {
  background-color: var(--dt-surface-sunken);
  color: var(--dt-text-muted);
  border-color: var(--dt-border);
}

.dt-badge--success {
  background-color: var(--dt-success-surface);
  color: var(--dt-success-text);
  border-color: var(--dt-success-border);
}

.dt-badge--warning {
  background-color: var(--dt-warning-surface);
  color: var(--dt-warning-text);
  border-color: var(--dt-warning-border);
}

.dt-badge--error {
  background-color: var(--dt-error-surface);
  color: var(--dt-error-text);
  border-color: var(--dt-error-border);
}

.dt-badge--info {
  background-color: var(--dt-info-surface);
  color: var(--dt-info-text);
  border-color: var(--dt-info-border);
}

/*
 * `.dt-alert` — a callout block: the report's result/warning callouts and a portal's inline
 * notice are the same component. Carries an icon slot (`.dt-alert__icon`) so the tone is never
 * colour-only — an icon or a leading word goes with it.
 *
 * Markup:
 *   <div class="dt-alert dt-alert--warning">
 *     <strong>Warning —</strong> the message.
 *   </div>
 *
 * A dismissible alert ends with a dismiss button. The behaviour script
 * (`@deeptrack/css/behaviour.js`) removes the alert when it is pressed, after a cancelable
 * `dt:dismiss` event on the alert, and moves focus to the next control after it, so the
 * keyboard reader carries on from where the alert was:
 *   <div class="dt-alert dt-alert--info">
 *     <div class="dt-alert__body"><strong>Information —</strong> the message.</div>
 *     <button class="dt-alert__dismiss" type="button" aria-label="Dismiss" data-dt-dismiss>×</button>
 *   </div>
 *
 * `.dt-alert__actions`, inside the body, holds what the reader can do: the retry of a region
 * that failed to load (`components/region.css`).
 */

.dt-alert {
  display: flex;
  align-items: flex-start;
  gap: var(--dt-space-2);
  font-family: var(--dt-font-sans);
  font-size: var(--dt-text-sm);
  line-height: var(--dt-leading-sm);
  padding: var(--dt-space-3) var(--dt-space-4);
  border-radius: var(--dt-radius-md);
  border: 1px solid transparent;
  border-left-width: 4px;
}

.dt-alert__icon {
  flex: none;
  width: var(--dt-text-lg);
  height: var(--dt-text-lg);
}

.dt-alert--neutral {
  background-color: var(--dt-surface-sunken);
  color: var(--dt-text);
  border-color: var(--dt-border);
}

.dt-alert--success {
  background-color: var(--dt-success-surface);
  color: var(--dt-success-text);
  border-color: var(--dt-success-border);
}

.dt-alert--warning {
  background-color: var(--dt-warning-surface);
  color: var(--dt-warning-text);
  border-color: var(--dt-warning-border);
}

.dt-alert--error {
  background-color: var(--dt-error-surface);
  color: var(--dt-error-text);
  border-color: var(--dt-error-border);
}

.dt-alert--info {
  background-color: var(--dt-info-surface);
  color: var(--dt-info-text);
  border-color: var(--dt-info-border);
}

.dt-alert__body {
  flex: 1;
  min-width: 0;
}

/* What the reader can do about it — a region that failed to load offers "Try again" here. */
.dt-alert__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--dt-space-2);
  margin-block-start: var(--dt-space-3);
}

.dt-alert__dismiss {
  display: inline-flex;
  flex: none;
  align-items: center;
  justify-content: center;
  width: var(--dt-space-6);
  height: var(--dt-space-6);
  margin: calc(var(--dt-space-0_5) * -1) calc(var(--dt-space-1) * -1) 0 auto;
  padding: 0;
  border: 0;
  border-radius: var(--dt-radius-md);
  background-color: transparent;
  color: inherit;
  font-family: inherit;
  font-size: var(--dt-text-lg);
  line-height: 1;
  cursor: pointer;
}

.dt-alert__dismiss:hover {
  background-color: var(--dt-surface-sunken);
}

.dt-alert__dismiss:focus-visible {
  outline: var(--dt-focus-width) solid var(--dt-focus-ring);
  outline-offset: calc(var(--dt-focus-width) * -1);
}

/*
 * `.dt-card` — a surface panel: the report's stat tile and a portal's content panel are the
 * same shape. `.dt-card__header` and `.dt-card__body` are optional structure, not required
 * classes — a card with neither still renders correctly.
 *
 * Markup:
 *   <div class="dt-card">
 *     <div class="dt-card__header"><h3>Title</h3></div>
 *     <div class="dt-card__body">Content.</div>
 *   </div>
 */

.dt-card {
  background-color: var(--dt-surface);
  border: 1px solid var(--dt-border);
  border-radius: var(--dt-radius-lg);
  box-shadow: var(--dt-elevation-xs);
  overflow: hidden;
}

.dt-card__header {
  padding: var(--dt-space-4);
  border-bottom: 1px solid var(--dt-border);
}

.dt-card__title {
  margin: 0;
  font-family: var(--dt-font-sans);
  font-size: var(--dt-text-lg);
  line-height: var(--dt-leading-lg);
  font-weight: var(--dt-weight-semibold);
  color: var(--dt-text);
}

.dt-card__body {
  padding: var(--dt-space-4);
}

.dt-card__footer {
  padding: var(--dt-space-4);
  border-top: 1px solid var(--dt-border);
  background-color: var(--dt-surface-sunken);
}

/* A report stat tile: a card whose body is a single figure and its label. */
.dt-tile {
  background-color: var(--dt-surface);
  border: 1px solid var(--dt-border);
  border-radius: var(--dt-radius-lg);
  padding: var(--dt-space-4);
}

.dt-tile__value {
  font-family: var(--dt-font-sans);
  font-size: var(--dt-text-3xl);
  line-height: var(--dt-leading-3xl);
  font-weight: var(--dt-weight-semibold);
  color: var(--dt-text);
}

.dt-tile__label {
  font-family: var(--dt-font-sans);
  font-size: var(--dt-text-sm);
  line-height: var(--dt-leading-sm);
  color: var(--dt-text-muted);
}

/*
 * `.dt-table` — the replacement for the report table renderer's known defects (see
 * `.context/existing_surfaces.md`): the old zebra stripe was `#000000`, saved from
 * black-on-black only by an invalid `nth-child(uneven)` selector that no engine matches. Here
 * the stripe is a real, valid `nth-child(even)` rule using `--dt-surface-sunken`, so it is
 * visibly a stripe rather than an accident of a selector nobody could execute.
 *
 * Markup: a plain `<table class="dt-table">` — `<thead>`/`<tbody>` as usual, no wrapper markup
 * required, and no nested `<html>` document (the failure mode `get_pandas_html()` had).
 */

.dt-table {
  width: 100%;
  font-family: var(--dt-font-sans);
  font-size: var(--dt-text-sm);
  line-height: var(--dt-leading-sm);
  color: var(--dt-text);
}

.dt-table caption {
  text-align: left;
  font-weight: var(--dt-weight-medium);
  padding-bottom: var(--dt-space-2);
}

.dt-table th,
.dt-table td {
  padding: var(--dt-space-2) var(--dt-space-3);
  text-align: left;
  border-bottom: 1px solid var(--dt-border);
}

.dt-table thead th {
  font-weight: var(--dt-weight-semibold);
  color: var(--dt-text-muted);
  border-bottom: 2px solid var(--dt-border-strong);
}

.dt-table tbody tr:nth-child(even) {
  background-color: var(--dt-surface-sunken);
}

.dt-table tbody tr:last-child td {
  border-bottom: none;
}

/* A row that must not split across a printed page break — the pagination fix for Phase 8. */
.dt-table tr {
  break-inside: avoid;
}

/* Numbers align on their digits: an end-aligned column, header included. */
.dt-table .dt-table__cell--end {
  text-align: end;
  font-variant-numeric: tabular-nums;
}

/*
 * `.dt-data-table` — a table of records the server sorts, filters and pages: PTO's lists of
 * plants, TBOXes and devices. It wraps a `.dt-table` with a filter bar above it and a
 * pagination bar below, and keeps its whole state in the URL (`behaviour/table.js`): every sort
 * is a link, the filters and the page size are `GET` forms, so the server answers each change,
 * Back undoes it, a view can be shared, and nothing needs a script.
 *
 * - **Sorting**: a sortable column's heading is a link (`.dt-data-table__sort`) to the other
 *   direction, the current one marked by `aria-sort` on its `<th>` and drawn as an arrow.
 * - **The filter bar** (`.dt-filter-bar`) is the page's own fields in a `GET` form, with Apply
 *   and Clear filters. It also holds a Sort by select (`.dt-data-table__sort-field`), shown only
 *   where the headings are not: on a phone.
 * - **Pagination** (`.dt-pagination`): the rows shown in words ("Rows 26–50 of 312"), Previous,
 *   the pages around the current one (`aria-current="page"`), Next, and a page-size form.
 * - **The header sticks** to the top of the table's own scroll box (`.dt-data-table__scroll`),
 *   which is `--dtc-data-table-height` tall at most (70vh) and scrolls both ways. It is a named
 *   region that takes focus, so a keyboard can scroll it.
 * - **Dense** (`--dense`) halves the cells' padding, for a long list read at a desk.
 * - **Its actions** (the toolbar's tools, the bulk bar, the row's controls): a row's details open
 *   by its disclosure button (`.dt-data-table__expand`, `data-dt-disclosure`), into the
 *   `.dt-data-table__detail` row after it; the rows' checkboxes post their keys with the
 *   `.dt-bulk-bar` form, which says the count in words once the script has counted; a row's
 *   actions (`.dt-data-table__actions`) are buttons, or a menu past two; the column chooser
 *   (`.dt-column-chooser`) is a `<details>` holding a `GET` form; the export is a link.
 * - **On a phone** — the table narrower than 48rem, the one breakpoint, of its own width — each
 *   row is a card of label and value pairs, the labels from each cell's `data-label`. The parts
 *   carry their table roles (`role="table"`, `"row"`, `"cell"`…), which some screen readers drop
 *   from a table drawn as blocks.
 *
 * It is a size container, so it takes its width from its parent and none from its content: put
 * it in a block, not in a flex row or an inline-block, where it would shrink to nothing.
 *
 * Markup (`{% data_table %}` writes it; the README's "Data table" has the whole of it):
 *   <div class="dt-data-table">
 *     <form class="dt-filter-bar" method="get" role="search" aria-label="Filter TBOXes">
 *       <div class="dt-filter-bar__fields">…the page's fields…
 *         <div class="dt-field dt-data-table__sort-field">…<select name="sort">…</select></div>
 *       </div>
 *       <div class="dt-filter-bar__actions">
 *         <button class="dt-button dt-button--primary dt-button--sm" type="submit">Apply</button>
 *         <a class="dt-button dt-button--link dt-button--sm" href="?sort=name">Clear filters</a>
 *       </div>
 *     </form>
 *     <div class="dt-data-table__scroll" role="region" aria-labelledby="tboxes" tabindex="0">
 *       <table class="dt-table dt-data-table__table">
 *         <caption id="tboxes">TBOXes</caption>
 *         <thead><tr>
 *           <th scope="col" aria-sort="ascending">
 *             <a class="dt-data-table__sort" href="?sort=-name">Name <svg class="dt-icon">…</svg></a>
 *           </th>
 *           <th scope="col">Serial</th>
 *         </tr></thead>
 *         <tbody><tr><td data-label="Name">TBOX 04</td><td data-label="Serial">T-0412</td></tr></tbody>
 *       </table>
 *     </div>
 *     <nav class="dt-pagination" aria-label="Pages of TBOXes">…</nav>
 *   </div>
 */

.dt-data-table {
  container: dt-data-table / inline-size;
  display: flex;
  flex-direction: column;
  gap: var(--dt-space-3);
  font-family: var(--dt-font-sans);
}

/* --- The filter bar ---------------------------------------------------------------------- */

.dt-filter-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--dt-space-3) var(--dt-space-4);
}

.dt-filter-bar__fields {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--dt-space-3) var(--dt-space-4);
  flex: 1 1 auto;
}

/* The fields of the page's form, which Django draws in a stack, sit in a row here. */
.dt-filter-bar__fields > .dt-stack {
  flex-direction: row;
  flex-wrap: wrap;
  align-items: flex-end;
}

/* A field of the bar keeps its label and control and drops the form's spacing below it. */
.dt-filter-bar__fields .dt-field {
  margin: 0;
  min-inline-size: 12rem;
}

.dt-filter-bar__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--dt-space-2);
}

/*
 * Sort by is for a phone, where the headings are not drawn; it is still sent from a desktop. A
 * table with no filters has a bar with Sort by alone (`--sort-only`), which only a phone shows.
 */
.dt-filter-bar .dt-data-table__sort-field,
.dt-data-table .dt-filter-bar--sort-only {
  display: none;
}

/* --- The table --------------------------------------------------------------------------- */

.dt-data-table__scroll {
  overflow: auto;
  max-block-size: var(--dtc-data-table-height, 70vh);
  border: 1px solid var(--dt-border);
  border-radius: var(--dt-radius-md);
  background-color: var(--dt-surface);
}

.dt-data-table__scroll:focus-visible {
  outline: var(--dt-focus-width) solid var(--dt-focus-ring);
  outline-offset: var(--dt-focus-offset);
}

.dt-data-table__table caption {
  padding: var(--dt-space-3) var(--dt-space-3) var(--dt-space-2);
}

.dt-data-table__table thead th {
  position: sticky;
  inset-block-start: 0;
  z-index: 1;
  background-color: var(--dt-surface);
  white-space: nowrap;
  /* A collapsed border stays with the table as it scrolls; this rule travels with the heading. */
  border-bottom: none;
  box-shadow: inset 0 -2px 0 var(--dt-border-strong);
}

/* Rows group by `<tbody>` so a row's detail (17.2) stripes with it: the stripe follows groups. */
.dt-data-table__table tbody tr:nth-child(even) {
  background-color: transparent;
}

.dt-data-table__table tbody:nth-of-type(even) {
  background-color: var(--dt-surface-sunken);
}

/*
 * A row and its detail are one group, and the rule is the group's: a cell's own rule would
 * follow whichever row is last, the detail's even while it is hidden.
 */
.dt-data-table__table {
  border-collapse: collapse;
}

.dt-data-table__table tbody td {
  border-bottom: none;
}

.dt-data-table__table tbody {
  border-bottom: 1px solid var(--dt-border);
}

.dt-data-table__table tbody:last-of-type {
  border-bottom: none;
}

.dt-data-table__sort {
  display: inline-flex;
  align-items: center;
  gap: var(--dt-space-1);
  color: inherit;
  font-weight: inherit;
  text-decoration: none;
}

.dt-data-table__sort:hover {
  color: var(--dt-text);
  text-decoration: underline;
}

.dt-data-table__sort:focus-visible {
  outline: var(--dt-focus-width) solid var(--dt-focus-ring);
  outline-offset: var(--dt-focus-offset);
  border-radius: var(--dt-radius-sm);
}

/* An unsorted column's arrows say that it sorts, in the heading's muted colour. */
.dt-data-table__sort-icon {
  flex: none;
}

th[aria-sort] {
  color: var(--dt-text);
}

.dt-data-table__table .dt-table__cell--end .dt-data-table__sort {
  flex-direction: row-reverse;
}

/* The one row of an empty table holds the empty state. */
.dt-data-table__empty {
  text-align: center;
}

.dt-data-table--dense .dt-data-table__table th,
.dt-data-table--dense .dt-data-table__table td {
  padding: var(--dt-space-1) var(--dt-space-2);
}

/* --- Table actions (roadmap 17.2) ------------------------------------------------------- */

/* The filter bar, and on its far side the column chooser and the export. */
.dt-data-table__toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--dt-space-3) var(--dt-space-4);
}

.dt-data-table__toolbar > .dt-filter-bar {
  flex: 1 1 auto;
}

.dt-data-table__tools {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: var(--dt-space-2);
  margin-inline-start: auto;
}

/* A native `<details>`: it opens with no script; the script closes it on Escape or outside. */
.dt-column-chooser {
  position: relative;
}

.dt-column-chooser > summary {
  list-style: none;
}

.dt-column-chooser > summary::-webkit-details-marker {
  display: none;
}

.dt-column-chooser__panel {
  position: absolute;
  inset-inline-end: 0;
  inset-block-start: calc(100% + var(--dt-space-1));
  z-index: 3;
  display: flex;
  flex-direction: column;
  gap: var(--dt-space-3);
  min-inline-size: 14rem;
  padding: var(--dt-space-3);
  border: 1px solid var(--dt-border);
  border-radius: var(--dt-radius-md);
  background-color: var(--dt-surface);
  box-shadow: var(--dt-elevation-lg);
}

.dt-column-chooser__panel .dt-fieldset {
  display: flex;
  flex-direction: column;
  gap: var(--dt-space-2);
  margin: 0;
}

/* The selection's count in words, and what can be done to the rows. */
.dt-bulk-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--dt-space-2) var(--dt-space-4);
  padding: var(--dt-space-2) var(--dt-space-3);
  border: 1px solid var(--dt-border);
  border-radius: var(--dt-radius-md);
  background-color: var(--dt-surface-sunken);
  font-family: var(--dt-font-sans);
  font-size: var(--dt-text-sm);
}

.dt-bulk-bar__status {
  margin: 0;
  color: var(--dt-text);
  font-weight: var(--dt-weight-medium);
}

.dt-bulk-bar__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--dt-space-2);
}

/* The cells of the row's own controls: as narrow as what they hold. */
.dt-data-table__table .dt-data-table__control {
  inline-size: 1%;
  white-space: nowrap;
  vertical-align: middle;
}

.dt-data-table__actions {
  text-align: end;
}

.dt-data-table__actions > * + * {
  margin-inline-start: var(--dt-space-1);
}

/* The expand button's arrow turns down while its detail is open: its state is also the name. */
.dt-data-table__expand svg {
  transition: rotate var(--dt-duration-fast) var(--dt-ease-standard);
}

.dt-data-table__expand[aria-expanded='true'] svg {
  rotate: 90deg;
}

@media (prefers-reduced-motion: reduce) {
  .dt-data-table__expand svg {
    transition: none;
  }
}

.dt-data-table__table .dt-data-table__detail td {
  padding-block: var(--dt-space-3);
  border-top: 1px solid var(--dt-border);
  background-color: var(--dt-surface);
}

/* Select all, which the script shows once it can count. */
.dt-bulk-bar__all {
  font-weight: var(--dt-weight-medium);
}

/* --- Pagination -------------------------------------------------------------------------- */

.dt-pagination {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--dt-space-3) var(--dt-space-4);
  font-family: var(--dt-font-sans);
  font-size: var(--dt-text-sm);
  line-height: var(--dt-leading-sm);
  color: var(--dt-text);
}

.dt-pagination__summary {
  margin: 0;
  color: var(--dt-text-muted);
  font-variant-numeric: tabular-nums;
}

.dt-pagination__pages {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--dt-space-1);
  margin: 0;
  padding: 0;
  list-style: none;
}

.dt-pagination__link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-inline-size: var(--dt-space-8);
  min-block-size: var(--dt-space-8);
  padding: 0 var(--dt-space-2);
  border: 1px solid transparent;
  border-radius: var(--dt-radius-sm);
  color: var(--dt-text-blue);
  font-variant-numeric: tabular-nums;
  text-decoration: none;
}

a.dt-pagination__link:hover {
  border-color: var(--dt-border);
  text-decoration: underline;
}

.dt-pagination__link:focus-visible {
  outline: var(--dt-focus-width) solid var(--dt-focus-ring);
  outline-offset: var(--dt-focus-offset);
}

/* The current page is a word and a box, not a colour alone. */
.dt-pagination__link[aria-current='page'] {
  border-color: var(--dt-border-strong);
  background-color: var(--dt-surface-sunken);
  color: var(--dt-text);
  font-weight: var(--dt-weight-semibold);
}

.dt-pagination__link[aria-disabled='true'] {
  color: var(--dt-text-muted);
}

.dt-pagination__gap {
  padding: 0 var(--dt-space-1);
  color: var(--dt-text-muted);
}

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

.dt-pagination__size .dt-select {
  inline-size: auto;
}

/* --- On a phone: each row a card --------------------------------------------------------- */

@container dt-data-table (width < 48rem) {
  .dt-filter-bar .dt-data-table__sort-field,
  .dt-data-table .dt-filter-bar--sort-only {
    display: flex;
  }

  .dt-data-table__scroll {
    max-block-size: none;
    border: none;
    background-color: transparent;
  }

  .dt-data-table__table,
  .dt-data-table__table tbody,
  .dt-data-table__table tr {
    display: block;
  }

  .dt-data-table__table caption {
    display: block;
    padding-inline: 0;
  }

  /* The headings are read by a screen reader still; their sort is the filter bar's select. */
  .dt-data-table__table thead {
    position: absolute;
    inline-size: 1px;
    block-size: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }

  .dt-data-table__table tbody,
  .dt-data-table__table tbody:nth-of-type(even),
  .dt-data-table__table tbody:last-of-type {
    margin-block-end: var(--dt-space-3);
    border: 1px solid var(--dt-border);
    border-radius: var(--dt-radius-md);
    background-color: var(--dt-surface);
  }

  /* A card's cells are divided by a rule above each but the first. */
  .dt-data-table .dt-data-table__table tbody tr td {
    display: grid;
    grid-template-columns: minmax(6rem, 35%) 1fr;
    gap: var(--dt-space-3);
    border-top: none;
    border-bottom: none;
    text-align: start;
  }

  .dt-data-table .dt-data-table__table tbody tr td + td:not(.dt-data-table__control) {
    border-top: 1px solid var(--dt-border);
  }

  .dt-data-table__table td::before {
    content: attr(data-label);
    color: var(--dt-text-muted);
    font-weight: var(--dt-weight-semibold);
  }

  /* The row's own controls head its card, with no label; its detail spans the card. */
  .dt-data-table .dt-data-table__table tbody tr td.dt-data-table__control {
    display: inline-flex;
    inline-size: auto;
  }

  /* The cell after the controls starts the card's values, under a rule. */
  .dt-data-table
    .dt-data-table__table
    tbody
    tr
    td.dt-data-table__control
    + td:not(.dt-data-table__control) {
    border-top: 1px solid var(--dt-border);
  }

  .dt-data-table .dt-data-table__table tbody tr td.dt-data-table__actions {
    display: flex;
    justify-content: flex-start;
  }

  .dt-data-table .dt-data-table__table tbody tr.dt-data-table__detail td {
    display: block;
    border-top: 1px solid var(--dt-border);
  }

  .dt-data-table__table td.dt-data-table__control::before,
  .dt-data-table__table tr.dt-data-table__detail td::before {
    content: none;
  }

  .dt-data-table__tools {
    margin-inline-start: 0;
  }

  /* The empty state's cell has no heading of its own, and spans the card. */
  .dt-data-table .dt-data-table__table td.dt-data-table__empty {
    display: block;
    text-align: center;
  }

  .dt-data-table .dt-data-table__table td.dt-data-table__empty::before {
    content: none;
  }
}

/*
 * `.dt-chart` — a chart a page draws itself, as SVG and HTML: a pie (`.dt-pie-chart`), bars
 * (`.dt-bar-chart`) and a gauge (`.dt-gauge`). The geometry and the words come from
 * `behaviour/chart.js` (and its Python copy), so the Django tags and `@deeptrack/react` draw the
 * same thing; see that module for why the system draws its own rather than loading a library.
 *
 * - **Colour by slot**: `dt-chart__mark--1` … `--6` and `--other`, from the chart tokens, which
 *   switch with the mode. A slot is an item's place in the list the page gives — every category
 *   the chart can show, in a fixed order — so a missing one repaints nothing.
 * - **Never colour alone**: each item's words are drawn beside it (the pie's legend, the bar's
 *   value, the gauge's value and state). The legend's swatch has the slot's marker shape, so a
 *   grey photocopy still tells the legend's entries apart; the slices, in the legend's order, are
 *   told apart by its words.
 * - **Drill-down is a link**: a slice's legend entry or a bar is an `<a>` to the filtered list,
 *   which a keyboard reaches. The pie's SVG is hidden from assistive technology and its slices
 *   are out of the tab order: the legend holds the same links and words. A slice carries its
 *   words in `data-dt-tip`, which the behaviour script shows by the pointer.
 * - **No inline style**: lengths and arcs are SVG attributes, so a strict Content-Security-Policy
 *   allows every chart.
 *
 * Markup (the Django tags write it; the README's "Charts" has each):
 *   <figure class="dt-chart dt-pie-chart" data-dt-chart>
 *     <figcaption class="dt-chart__title">Devices by state</figcaption>
 *     <div class="dt-pie-chart__body">
 *       <div class="dt-pie-chart__figure">
 *         <svg class="dt-pie-chart__plot" viewBox="0 0 100 100" aria-hidden="true">
 *           <a href="/devices/?state=online" tabindex="-1"><path class="dt-pie-chart__slice
 *              dt-chart__mark--1" d="…" data-dt-tip="Online: 42, 61%"><title>…</title></path></a>
 *         </svg>
 *       </div>
 *       <ul class="dt-chart__legend">
 *         <li class="dt-chart__key"><span class="dt-chart__swatch dt-chart__mark--1"></span>
 *           <a class="dt-chart__label" href="/devices/?state=online">Online</a>
 *           <span class="dt-chart__value">42, 61%</span></li>
 *       </ul>
 *     </div>
 *   </figure>
 */

.dt-chart {
  /* The tooltip is placed against the chart. */
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--dt-space-3);
  margin: 0;
  font-family: var(--dt-font-sans);
  font-size: var(--dt-text-sm);
  line-height: var(--dt-leading-sm);
  color: var(--dt-text);
}

.dt-chart__title {
  font-weight: var(--dt-weight-semibold);
}

/* --- The slots --------------------------------------------------------------------------- */

.dt-chart__mark--1 {
  fill: var(--dt-chart-1);
  background-color: var(--dt-chart-1);
}

.dt-chart__mark--2 {
  fill: var(--dt-chart-2);
  background-color: var(--dt-chart-2);
}

.dt-chart__mark--3 {
  fill: var(--dt-chart-3);
  background-color: var(--dt-chart-3);
}

.dt-chart__mark--4 {
  fill: var(--dt-chart-4);
  background-color: var(--dt-chart-4);
}

.dt-chart__mark--5 {
  fill: var(--dt-chart-5);
  background-color: var(--dt-chart-5);
}

.dt-chart__mark--6 {
  fill: var(--dt-chart-6);
  background-color: var(--dt-chart-6);
}

.dt-chart__mark--other {
  fill: var(--dt-chart-other);
  background-color: var(--dt-chart-other);
}

/* --- The legend -------------------------------------------------------------------------- */

.dt-chart__legend {
  display: flex;
  flex-direction: column;
  gap: var(--dt-space-2);
  margin: 0;
  padding: 0;
  list-style: none;
}

.dt-chart__key {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: var(--dt-space-2);
}

.dt-chart__label {
  color: var(--dt-text);
}

a.dt-chart__label {
  color: var(--dt-text-blue);
}

.dt-chart__value {
  color: var(--dt-text-muted);
  font-variant-numeric: tabular-nums;
  text-align: end;
}

/*
 * The swatch has the slot's marker shape, as `tokens/chart.yaml` gives it: circle, square,
 * diamond, triangle, open circle, open square. `chart.test.js` holds the two to each other.
 */
.dt-chart__swatch {
  inline-size: var(--dt-space-3);
  block-size: var(--dt-space-3);
}

.dt-chart__swatch.dt-chart__mark--1,
.dt-chart__swatch.dt-chart__mark--other {
  border-radius: var(--dt-radius-full);
}

.dt-chart__swatch.dt-chart__mark--3 {
  clip-path: polygon(50% 0, 100% 50%, 50% 100%, 0 50%);
}

.dt-chart__swatch.dt-chart__mark--4 {
  clip-path: polygon(50% 0, 100% 100%, 0 100%);
}

.dt-chart__swatch.dt-chart__mark--5 {
  border: 3px solid var(--dt-chart-5);
  border-radius: var(--dt-radius-full);
  background-color: transparent;
}

.dt-chart__swatch.dt-chart__mark--6 {
  border: 3px solid var(--dt-chart-6);
  background-color: transparent;
}

.dt-chart__empty {
  margin: 0;
  color: var(--dt-text-muted);
}

/* The words of the slice under the pointer, placed by the behaviour script. */
.dt-chart__tooltip {
  position: absolute;
  z-index: 4;
  padding: var(--dt-space-1) var(--dt-space-2);
  border-radius: var(--dt-radius-sm);
  background-color: var(--dt-text);
  color: var(--dt-surface);
  font-size: var(--dt-text-xs);
  white-space: nowrap;
  pointer-events: none;
}

/* --- The pie ----------------------------------------------------------------------------- */

/*
 * The pie beside its legend while both fit, the legend under it when not: no container query,
 * whose containment would collapse a chart in a parent that sizes to its content.
 */
.dt-pie-chart__body {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 10rem), 1fr));
  align-items: center;
  gap: var(--dt-space-4);
}

.dt-pie-chart__figure {
  position: relative;
  inline-size: 100%;
  max-inline-size: 12rem;
  justify-self: center;
}

.dt-pie-chart__plot {
  display: block;
  inline-size: 100%;
  overflow: visible;
}

.dt-pie-chart__slice {
  stroke: var(--dt-surface);
  stroke-width: 0.6;
  /* A whole donut is two circles: the inner one is its hole. */
  fill-rule: evenodd;
}

/* A slice that drills down shows a pointer: its keyboard path is the legend's link. */
.dt-pie-chart__plot a {
  cursor: pointer;
}

.dt-pie-chart__plot a:hover .dt-pie-chart__slice,
.dt-pie-chart__slice:hover {
  stroke: var(--dt-text);
}

/* A donut's total, in the hole. */
.dt-pie-chart__total {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  margin: 0;
  color: var(--dt-text-muted);
  font-size: var(--dt-text-xs);
  text-align: center;
  pointer-events: none;
}

.dt-pie-chart__total-value {
  color: var(--dt-text);
  font-size: var(--dt-text-xl);
  font-weight: var(--dt-weight-semibold);
  line-height: var(--dt-leading-xl);
  font-variant-numeric: tabular-nums;
}

/* --- Bars -------------------------------------------------------------------------------- */

.dt-bar-chart__bars {
  display: flex;
  flex-direction: column;
  gap: var(--dt-space-2);
  margin: 0;
  padding: 0;
  list-style: none;
}

.dt-bar-chart__row {
  display: grid;
  grid-template-columns: minmax(6rem, 30%) 1fr minmax(3rem, auto);
  align-items: center;
  gap: var(--dt-space-3);
  color: var(--dt-text);
  text-decoration: none;
}

/* Lets a long label shrink to its ellipsis rather than widen the list. */
.dt-bar-chart__item {
  min-inline-size: 0;
}

a.dt-bar-chart__row .dt-bar-chart__label {
  color: var(--dt-text-blue);
  text-decoration: underline;
  text-underline-offset: 0.15em;
}

a.dt-bar-chart__row:hover .dt-bar-chart__track {
  outline: 1px solid var(--dt-border-strong);
}

a.dt-bar-chart__row:focus-visible {
  outline: var(--dt-focus-width) solid var(--dt-focus-ring);
  outline-offset: var(--dt-focus-offset);
  border-radius: var(--dt-radius-sm);
}

.dt-bar-chart__label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The bar's length is the SVG's own `width`, out of a 100-wide view: no inline style. */
.dt-bar-chart__track {
  display: block;
  inline-size: 100%;
  block-size: var(--dt-space-4);
  border-radius: var(--dt-radius-sm);
  background-color: var(--dt-surface-sunken);
}

/* Square ends, on whole pixels: a bar is read by its length. */
.dt-bar-chart__bar {
  shape-rendering: crispEdges;
}

.dt-bar-chart__value {
  font-variant-numeric: tabular-nums;
  text-align: end;
}

/* --- The gauge --------------------------------------------------------------------------- */

.dt-gauge {
  align-items: center;
  max-inline-size: 16rem;
  text-align: center;
}

.dt-gauge__dial {
  position: relative;
  inline-size: 100%;
}

.dt-gauge__dial svg {
  display: block;
  inline-size: 100%;
  overflow: visible;
}

.dt-gauge__track {
  fill: var(--dt-surface-sunken);
  stroke: var(--dt-border);
  stroke-width: 0.5;
}

/* The value's arc, a hair apart from its track. */
.dt-gauge__bar {
  stroke: var(--dt-surface);
  stroke-width: 0.5;
}

.dt-gauge__bar--success {
  fill: var(--dt-success-solid);
}

.dt-gauge__bar--warning {
  fill: var(--dt-warning-solid);
}

.dt-gauge__bar--error {
  fill: var(--dt-error-solid);
}

.dt-gauge__bar--info {
  fill: var(--dt-info-solid);
}

.dt-gauge__bar--neutral {
  fill: var(--dt-text-muted);
}

/* Where each threshold starts, across the ring. */
.dt-gauge__tick {
  stroke: var(--dt-text);
  stroke-width: 0.8;
}

.dt-gauge__value {
  position: absolute;
  inset-inline: 0;
  inset-block-end: 0;
  margin: 0;
  font-size: var(--dt-text-xl);
  font-weight: var(--dt-weight-semibold);
  line-height: var(--dt-leading-xl);
  font-variant-numeric: tabular-nums;
}

.dt-gauge__label {
  font-weight: var(--dt-weight-semibold);
}

/* The state in a word, with the tone's text colour: never the arc's colour alone. */
.dt-gauge__state {
  margin: 0;
  font-weight: var(--dt-weight-semibold);
}

.dt-gauge__state--success {
  color: var(--dt-success-text);
}

.dt-gauge__state--warning {
  color: var(--dt-warning-text);
}

.dt-gauge__state--error {
  color: var(--dt-error-text);
}

.dt-gauge__state--info {
  color: var(--dt-info-text);
}

.dt-gauge__scale {
  display: flex;
  justify-content: space-between;
  inline-size: 100%;
  margin: 0;
  color: var(--dt-text-muted);
  font-size: var(--dt-text-xs);
  font-variant-numeric: tabular-nums;
}

@media (forced-colors: active) {
  .dt-pie-chart__slice,
  .dt-bar-chart__bar,
  .dt-gauge__bar--success,
  .dt-gauge__bar--warning,
  .dt-gauge__bar--error,
  .dt-gauge__bar--info,
  .dt-gauge__bar--neutral {
    fill: CanvasText;
  }

  /* The slices stay apart by their edges, the swatches by their shapes. */
  .dt-pie-chart__slice {
    stroke: Canvas;
  }

  .dt-chart__swatch {
    background-color: CanvasText;
  }
}

/*
 * `.dt-time-series` — series over a range of time, and `.dt-range-picker`, the range a view shows.
 * The geometry and the words come from `behaviour/time.js` (and its Python copy), so the Django
 * tags and `@deeptrack/react` draw the same chart.
 *
 * - **No label is scaled and no style is inline.** The plot is an SVG stretched to its box
 *   (`preserveAspectRatio="none"`), its lines keeping their width (`vector-effect`). The value
 *   axis is HTML beside it, its labels evenly spaced because the axis starts and ends on a tick.
 *   The time axis is SVG text placed by percentage, which an SVG with no view box reads against
 *   its own width.
 * - **A series is its slot's colour and dash** (`dt-chart__line--1` … `--6`), from the chart
 *   tokens, so a grey photocopy tells lines apart; the legend says each one's latest value.
 * - **The data is a table under the chart** (`.dt-chart__data`, a `<details>`), its accessible
 *   form. The behaviour script reads it to draw a line at the sample under the pointer
 *   (`.dt-time-series__cursor`) and say every value there.
 * - **The range** is links for the presets (`aria-current` on the one shown) and a `GET` form of
 *   two `datetime-local` inputs for a custom one, kept in the URL; the range shown is in words,
 *   with its time zone, which the chart's axis names too.
 *
 * Markup: the README's "Time series" has it.
 */

/* --- The lines' slots -------------------------------------------------------------------- */

.dt-chart__line--1,
.dt-chart__line--2,
.dt-chart__line--3,
.dt-chart__line--4,
.dt-chart__line--5,
.dt-chart__line--6 {
  fill: none;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* Each dash is the token's, in multiples of the 2 px line. */
.dt-chart__line--1 {
  stroke: var(--dt-chart-1);
}

.dt-chart__line--2 {
  stroke: var(--dt-chart-2);
  stroke-dasharray: 6 3;
}

.dt-chart__line--3 {
  stroke: var(--dt-chart-3);
  stroke-dasharray: 2 3;
}

.dt-chart__line--4 {
  stroke: var(--dt-chart-4);
  stroke-dasharray: 9 3 2 3;
}

.dt-chart__line--5 {
  stroke: var(--dt-chart-5);
  stroke-dasharray: 12 4;
}

.dt-chart__line--6 {
  stroke: var(--dt-chart-6);
  stroke-dasharray: 1 4;
}

/* --- The chart --------------------------------------------------------------------------- */

/* The series side by side: few, and each says its latest value. */
.dt-time-series__legend {
  flex-direction: row;
  flex-wrap: wrap;
  gap: var(--dt-space-2) var(--dt-space-6);
}

.dt-time-series__swatch {
  inline-size: var(--dt-space-6);
  block-size: var(--dt-space-1);
  overflow: visible;
}

.dt-time-series__frame {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--dt-space-2);
}

/* The value axis: zero-height labels, spread from the top line to the bottom one. */
.dt-time-series__y {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  block-size: var(--dtc-time-series-height, 14rem);
  color: var(--dt-text-muted);
  font-size: var(--dt-text-xs);
  font-variant-numeric: tabular-nums;
  text-align: end;
}

.dt-time-series__y > span {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  block-size: 0;
}

.dt-time-series__area {
  min-inline-size: 0;
}

.dt-time-series__plot {
  display: block;
  inline-size: 100%;
  block-size: var(--dtc-time-series-height, 14rem);
  overflow: visible;
  border-bottom: 1px solid var(--dt-border-strong);
}

.dt-time-series__plot line,
.dt-time-series__plot polyline {
  vector-effect: non-scaling-stroke;
}

.dt-time-series__grid line {
  stroke: var(--dt-border);
  stroke-width: 1;
}

/* A lone point, a zero-length line with a round cap, so a stretched plot draws a dot. */
.dt-time-series__series .dt-time-series__dot {
  stroke-width: 6;
  stroke-dasharray: none;
}

.dt-time-series__cursor {
  stroke: var(--dt-text);
  stroke-width: 1;
}

/* The time axis: text by percentage, in an SVG with no view box, so it is never scaled. */
.dt-time-series__x {
  display: block;
  inline-size: 100%;
  block-size: var(--dt-space-6);
  overflow: visible;
  fill: var(--dt-text-muted);
  font-family: var(--dt-font-sans);
  font-size: var(--dt-text-xs);
}

.dt-time-series__zone {
  margin: 0;
  color: var(--dt-text-muted);
  font-size: var(--dt-text-xs);
}

.dt-chart__data > summary {
  color: var(--dt-text-blue);
  cursor: pointer;
}

.dt-chart__data > summary:focus-visible {
  outline: var(--dt-focus-width) solid var(--dt-focus-ring);
  outline-offset: var(--dt-focus-offset);
  border-radius: var(--dt-radius-sm);
}

.dt-chart__data .dt-table {
  margin-block-start: var(--dt-space-2);
}

/* --- The range picker -------------------------------------------------------------------- */

.dt-range-picker {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: var(--dt-space-2) var(--dt-space-3);
  font-family: var(--dt-font-sans);
  font-size: var(--dt-text-sm);
}

.dt-range-picker__presets {
  display: flex;
  flex-wrap: wrap;
  margin: 0;
  padding: 0;
  list-style: none;
  border: 1px solid var(--dt-border-strong);
  border-radius: var(--dt-radius-md);
  overflow: hidden;
}

.dt-range-picker__presets li + li {
  border-inline-start: 1px solid var(--dt-border-strong);
}

.dt-range-picker__preset {
  display: block;
  padding: var(--dt-space-1) var(--dt-space-3);
  color: var(--dt-text);
  text-decoration: none;
}

.dt-range-picker__preset:hover {
  background-color: var(--dt-surface-sunken);
  text-decoration: underline;
}

.dt-range-picker__preset:focus-visible {
  outline: var(--dt-focus-width) solid var(--dt-focus-ring);
  outline-offset: calc(var(--dt-focus-width) * -1);
}

/* The range shown: filled and bold, its words saying so too (`aria-current`). */
.dt-range-picker__preset[aria-current='true'] {
  background-color: var(--dt-text-blue);
  color: var(--dt-surface);
  font-weight: var(--dt-weight-semibold);
}

.dt-range-picker__custom {
  position: relative;
}

.dt-range-picker__custom > summary {
  list-style: none;
}

.dt-range-picker__custom > summary::-webkit-details-marker {
  display: none;
}

.dt-range-picker__form {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--dt-space-2) var(--dt-space-3);
  margin-block-start: var(--dt-space-2);
}

.dt-range-picker__current {
  flex-basis: 100%;
  margin: 0;
  color: var(--dt-text-muted);
  font-size: var(--dt-text-xs);
}

@media (forced-colors: active) {
  .dt-chart__line--1,
  .dt-chart__line--2,
  .dt-chart__line--3,
  .dt-chart__line--4,
  .dt-chart__line--5,
  .dt-chart__line--6 {
    stroke: CanvasText;
  }

  .dt-range-picker__preset[aria-current='true'] {
    background-color: Highlight;
    color: HighlightText;
  }
}

/*
 * `.dt-map` — a map of places: Leaflet, self-hosted, over the readable list of them the server
 * renders (`behaviour/map.js`). The list is the map's accessible form, and what a page without
 * the script, or without its tiles, still reads.
 *
 * - **The view** (`.dt-map__view`) is hidden until the script mounts Leaflet in it, and is a named
 *   region a keyboard can pan and zoom. `--dtc-map-height` sets its height (20rem).
 * - **A marker** is its place's tone in a shape — a dot for success, a diamond for a warning, a
 *   square for an error, a ring for information, a small dot for neutral — and its words are its
 *   name and its popup's; the list says them too.
 * - **Without its tiles** (`.dt-map--no-tiles`) the ground is plain, and the status says why.
 * - **Leaflet's controls, attribution and popups** take the system's look here, in both modes. In
 *   dark mode the tiles are inverted, so a light street map does not glare from a dark page.
 *
 * Markup (`{% map %}` writes it; the README's "Map" has it):
 *   <figure class="dt-map" data-dt-map data-dt-map-tiles="https://…/{z}/{x}/{y}.png"
 *           data-dt-map-attribution="© OpenStreetMap contributors">
 *     <figcaption class="dt-map__title">TBOXes on Lerma</figcaption>
 *     <div class="dt-map__view" role="region" aria-label="Map: TBOXes on Lerma" tabindex="0" hidden></div>
 *     <p class="dt-map__status" role="status"></p>
 *     <ul class="dt-map__places">
 *       <li class="dt-map__place" data-dt-lat="37.38" data-dt-lng="-5.98" data-dt-tone="error">
 *         <a href="/tboxes/4/">TBOX 04</a> <span class="dt-map__state dt-map__state--error">Fault</span>
 *         <span class="dt-map__where">37.380, −5.980</span></li>
 *     </ul>
 *   </figure>
 */

.dt-map {
  display: flex;
  flex-direction: column;
  gap: var(--dt-space-3);
  margin: 0;
  font-family: var(--dt-font-sans);
  font-size: var(--dt-text-sm);
  line-height: var(--dt-leading-sm);
  color: var(--dt-text);
}

.dt-map__title {
  font-weight: var(--dt-weight-semibold);
}

.dt-map__view {
  block-size: var(--dtc-map-height, 20rem);
  border: 1px solid var(--dt-border);
  border-radius: var(--dt-radius-md);
  background-color: var(--dt-surface-sunken);
  overflow: hidden;
}

.dt-map__view:focus-visible {
  outline: var(--dt-focus-width) solid var(--dt-focus-ring);
  outline-offset: var(--dt-focus-offset);
}

.dt-map__status {
  margin: 0;
  color: var(--dt-text-muted);
}

/* Empty, it takes no room, but stays a live region a screen reader is listening to. */
.dt-map__status:empty {
  position: absolute;
  inline-size: 1px;
  block-size: 1px;
  overflow: hidden;
  clip-path: inset(50%);
}

/* --- The list ---------------------------------------------------------------------------- */

.dt-map__places {
  display: flex;
  flex-direction: column;
  gap: var(--dt-space-1);
  margin: 0;
  padding: 0;
  list-style: none;
}

.dt-map__place {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--dt-space-1) var(--dt-space-2);
}

.dt-map__label {
  font-weight: var(--dt-weight-medium);
}

.dt-map__where {
  color: var(--dt-text-muted);
  font-size: var(--dt-text-xs);
  font-variant-numeric: tabular-nums;
}

.dt-map__state {
  font-weight: var(--dt-weight-semibold);
}

.dt-map__state--success {
  color: var(--dt-success-text);
}

.dt-map__state--warning {
  color: var(--dt-warning-text);
}

.dt-map__state--error {
  color: var(--dt-error-text);
}

.dt-map__state--info {
  color: var(--dt-info-text);
}

/* --- Markers ----------------------------------------------------------------------------- */

/* Leaflet's own `.leaflet-div-icon` draws a white box: the marker is its pin alone. */
.dt-map .dt-map__marker {
  display: grid;
  place-items: center;
  border: none;
  background: transparent;
}

.dt-map__pin {
  display: block;
  inline-size: var(--dt-space-4);
  block-size: var(--dt-space-4);
  border: 2px solid var(--dt-surface);
  border-radius: var(--dt-radius-full);
  background-color: var(--dt-text-muted);
  box-shadow: var(--dt-elevation-sm);
}

.dt-map__marker--success .dt-map__pin {
  background-color: var(--dt-success-solid);
}

.dt-map__marker--warning .dt-map__pin {
  border-radius: var(--dt-radius-sm);
  rotate: 45deg;
  background-color: var(--dt-warning-solid);
}

.dt-map__marker--error .dt-map__pin {
  border-radius: var(--dt-radius-sm);
  background-color: var(--dt-error-solid);
}

.dt-map__marker--info .dt-map__pin {
  border: 4px solid var(--dt-info-solid);
  background-color: var(--dt-surface);
}

.dt-map__marker--neutral .dt-map__pin {
  inline-size: var(--dt-space-3);
  block-size: var(--dt-space-3);
}

.dt-map .dt-map__marker:focus-visible {
  outline: var(--dt-focus-width) solid var(--dt-focus-ring);
  outline-offset: var(--dt-focus-offset);
  border-radius: var(--dt-radius-full);
}

.dt-map__popup {
  display: flex;
  flex-wrap: wrap;
  gap: var(--dt-space-1) var(--dt-space-2);
  font-family: var(--dt-font-sans);
}

/* --- Without its tiles ------------------------------------------------------------------- */

.dt-map--no-tiles .dt-map__view,
.dt-map--no-tiles .leaflet-container {
  background-color: var(--dt-surface-sunken);
}

/* --- Leaflet's controls, in the system's look -------------------------------------------- */

.dt-map .leaflet-container {
  font-family: var(--dt-font-sans);
  font-size: var(--dt-text-xs);
  background-color: var(--dt-surface-sunken);
}

.dt-map .leaflet-bar {
  border: 1px solid var(--dt-border-strong);
  border-radius: var(--dt-radius-md);
  box-shadow: none;
}

.dt-map .leaflet-bar a,
.dt-map .leaflet-bar a:hover {
  border-bottom: 1px solid var(--dt-border);
  background-color: var(--dt-surface);
  color: var(--dt-text);
}

.dt-map .leaflet-bar a:hover {
  background-color: var(--dt-surface-sunken);
}

.dt-map .leaflet-bar a:focus-visible {
  outline: var(--dt-focus-width) solid var(--dt-focus-ring);
  outline-offset: calc(var(--dt-focus-width) * -1);
}

.dt-map .leaflet-bar a.leaflet-disabled {
  background-color: var(--dt-surface);
  color: var(--dt-text-muted);
}

/* As specific as Leaflet's own rule, plus one: its stylesheet is loaded after this one. */
.dt-map .leaflet-container .leaflet-control-attribution {
  background-color: var(--dt-surface);
  color: var(--dt-text-muted);
}

.dt-map .leaflet-container .leaflet-control-attribution a {
  color: var(--dt-text-blue);
}

.dt-map .leaflet-popup-content-wrapper,
.dt-map .leaflet-popup-tip {
  background-color: var(--dt-surface);
  color: var(--dt-text);
  box-shadow: var(--dt-elevation-lg);
}

.dt-map .leaflet-popup-content-wrapper {
  border-radius: var(--dt-radius-md);
}

.dt-map .leaflet-popup-content a {
  color: var(--dt-text-blue);
}

.dt-map .leaflet-container a.leaflet-popup-close-button {
  color: var(--dt-text-muted);
}

/* A light street map, inverted to sit on a dark page: the markers and controls are not. */
[data-dt-theme='dark']
  .dt-map:not([data-dt-theme='light'] *, [data-dt-theme='light'])
  .leaflet-tile-pane {
  filter: invert(1) hue-rotate(180deg) brightness(0.9) contrast(0.9);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-dt-theme='light'])
    .dt-map:not([data-dt-theme='light'] *, [data-dt-theme='light'])
    .leaflet-tile-pane {
    filter: invert(1) hue-rotate(180deg) brightness(0.9) contrast(0.9);
  }
}

@media (forced-colors: active) {
  .dt-map__pin {
    border-color: Canvas;
    background-color: CanvasText;
  }
}

/*
 * `.dt-field` and the form controls — text input, textarea, select, checkbox, radio and switch.
 *
 * Every control boundary is `--dt-border-strong`, not the hairline `--dt-border`: WCAG 1.4.11
 * asks 3:1 of the edge that tells a reader where a control is, and the contrast gate holds
 * `border-strong` to that while the hairline is measured as decoration and not gated. Checked
 * and selected states are drawn in `--dt-text-blue`, a gated reading colour in both modes, so a
 * ticked box stays distinguishable from an empty one on either surface.
 *
 * An invalid field says so in words (`.dt-field__error`), wired with `aria-describedby`; the red
 * border is the second signal, never the only one.
 *
 * Markup:
 *   <div class="dt-field">
 *     <label class="dt-field__label" for="site">Site</label>
 *     <input class="dt-input" id="site" aria-describedby="site-hint" />
 *     <p class="dt-field__hint" id="site-hint">As it appears in PTO.</p>
 *   </div>
 */

.dt-field {
  display: flex;
  flex-direction: column;
  gap: var(--dt-space-1);
  font-family: var(--dt-font-sans);
}

.dt-field__label {
  font-size: var(--dt-text-sm);
  line-height: var(--dt-leading-sm);
  font-weight: var(--dt-weight-medium);
  color: var(--dt-text);
}

/* A required marker is a word for assistive technology and an asterisk for the eye. */
.dt-field__required {
  color: var(--dt-error-text);
  margin-left: var(--dt-space-0_5);
}

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

.dt-field__error {
  margin: 0;
  font-size: var(--dt-text-xs);
  line-height: var(--dt-leading-xs);
  font-weight: var(--dt-weight-medium);
  color: var(--dt-error-text);
}

.dt-input,
.dt-textarea,
.dt-select {
  width: 100%;
  font-family: var(--dt-font-sans);
  font-size: var(--dt-text-sm);
  line-height: var(--dt-leading-sm);
  color: var(--dt-text);
  background-color: var(--dt-surface);
  border: 1px solid var(--dt-border-strong);
  border-radius: var(--dt-radius-md);
  padding: var(--dt-space-2) var(--dt-space-3);
  transition: border-color var(--dt-duration-fast) var(--dt-ease-standard);
}

.dt-textarea {
  min-height: calc(var(--dt-leading-sm) * 4);
  resize: vertical;
}

.dt-input::placeholder,
.dt-textarea::placeholder {
  color: var(--dt-text-muted);
}

.dt-input:focus-visible,
.dt-textarea:focus-visible,
.dt-select:focus-visible {
  outline: var(--dt-focus-width) solid var(--dt-focus-ring);
  outline-offset: var(--dt-focus-offset);
}

.dt-input[aria-invalid='true'],
.dt-textarea[aria-invalid='true'],
.dt-select[aria-invalid='true'] {
  border-color: var(--dt-error-solid);
}

.dt-input:disabled,
.dt-textarea:disabled,
.dt-select:disabled {
  background-color: var(--dt-surface-sunken);
  color: var(--dt-text-muted);
  cursor: not-allowed;
}

/* A file input: the browser's own button, drawn as a secondary button. */
.dt-input[type='file'] {
  padding: var(--dt-space-1);
  cursor: pointer;
}

.dt-input::file-selector-button {
  margin-inline-end: var(--dt-space-3);
  padding: var(--dt-space-1) var(--dt-space-3);
  font: inherit;
  font-weight: var(--dt-weight-medium);
  color: var(--dt-text);
  background-color: var(--dt-surface-sunken);
  border: 1px solid var(--dt-border-strong);
  border-radius: var(--dt-radius-sm);
  cursor: inherit;
}

/* A list of choices shows several at once, and sizes to its options rather than one line. */
.dt-select[multiple] {
  min-height: calc(var(--dt-leading-sm) * 5);
  padding: var(--dt-space-1);
}

.dt-select[multiple] option {
  padding: var(--dt-space-1) var(--dt-space-2);
  border-radius: var(--dt-radius-sm);
}

/* Checkbox, radio and switch share one row shape: the control, then its label. */
.dt-check {
  display: inline-flex;
  align-items: center;
  gap: var(--dt-space-2);
  font-family: var(--dt-font-sans);
  font-size: var(--dt-text-sm);
  line-height: var(--dt-leading-sm);
  color: var(--dt-text);
  cursor: pointer;
}

.dt-check:has(input:disabled) {
  color: var(--dt-text-muted);
  cursor: not-allowed;
}

.dt-checkbox,
.dt-radio {
  appearance: none;
  flex: none;
  display: inline-grid;
  place-content: center;
  width: var(--dt-space-4);
  height: var(--dt-space-4);
  margin: 0;
  /* base.css pads every input, which would set a floor under this box's size. */
  padding: 0;
  background-color: var(--dt-surface);
  border: 1px solid var(--dt-border-strong);
  cursor: inherit;
}

.dt-checkbox {
  border-radius: var(--dt-radius-sm);
}

.dt-radio {
  border-radius: var(--dt-radius-full);
}

.dt-checkbox:checked,
.dt-radio:checked {
  background-color: var(--dt-text-blue);
  border-color: var(--dt-text-blue);
}

/* The tick is drawn, not an icon font: a rotated two-sided border in the surface colour. */
.dt-checkbox:checked::after {
  content: '';
  width: var(--dt-space-1_5);
  height: var(--dt-space-2_5);
  margin-top: calc(var(--dt-space-0_5) * -1);
  border: solid var(--dt-surface);
  border-width: 0 var(--dt-space-0_5) var(--dt-space-0_5) 0;
  transform: rotate(45deg);
}

/* Mixed — some of a group ticked, as a table's Select all is: a bar, not a tick. */
.dt-checkbox:indeterminate {
  background-color: var(--dt-text-blue);
  border-color: var(--dt-text-blue);
}

.dt-checkbox:indeterminate::after {
  content: '';
  width: var(--dt-space-2_5);
  height: var(--dt-space-0_5);
  background-color: var(--dt-surface);
}

.dt-radio:checked::after {
  content: '';
  width: var(--dt-space-1_5);
  height: var(--dt-space-1_5);
  border-radius: var(--dt-radius-full);
  background-color: var(--dt-surface);
}

/* The switch is a checkbox with `role="switch"`: the track, and the thumb as its ::after. */
.dt-switch {
  appearance: none;
  flex: none;
  position: relative;
  width: var(--dt-space-8);
  height: var(--dt-space-5);
  margin: 0;
  padding: 0;
  border-radius: var(--dt-radius-full);
  background-color: var(--dt-surface);
  border: 1px solid var(--dt-border-strong);
  cursor: inherit;
  transition: background-color var(--dt-duration-fast) var(--dt-ease-standard);
}

.dt-switch::after {
  content: '';
  position: absolute;
  top: var(--dt-space-0_5);
  left: var(--dt-space-0_5);
  width: calc(var(--dt-space-4) - var(--dt-space-0_5));
  height: calc(var(--dt-space-4) - var(--dt-space-0_5));
  border-radius: var(--dt-radius-full);
  background-color: var(--dt-border-strong);
  transition:
    transform var(--dt-duration-fast) var(--dt-ease-standard),
    background-color var(--dt-duration-fast) var(--dt-ease-standard);
}

.dt-switch:checked {
  background-color: var(--dt-text-blue);
  border-color: var(--dt-text-blue);
}

.dt-switch:checked::after {
  background-color: var(--dt-surface);
  transform: translateX(var(--dt-space-3));
}

.dt-checkbox:focus-visible,
.dt-radio:focus-visible,
.dt-switch:focus-visible {
  outline: var(--dt-focus-width) solid var(--dt-focus-ring);
  outline-offset: var(--dt-focus-offset);
}

.dt-checkbox:disabled,
.dt-radio:disabled,
.dt-switch:disabled {
  opacity: 0.5;
}

/* A radio group or a set of checkboxes: a fieldset with its legend as the group's label. */
.dt-fieldset {
  display: flex;
  flex-direction: column;
  gap: var(--dt-space-2);
  margin: 0;
  padding: 0;
  border: none;
  font-family: var(--dt-font-sans);
}

.dt-fieldset__legend {
  padding: 0;
  margin-bottom: var(--dt-space-1);
  font-size: var(--dt-text-sm);
  line-height: var(--dt-leading-sm);
  font-weight: var(--dt-weight-medium);
  color: var(--dt-text);
}

/* Motion is decoration here: with reduced motion asked for, state changes are instant. */
@media (prefers-reduced-motion: reduce) {
  .dt-input,
  .dt-textarea,
  .dt-select,
  .dt-switch,
  .dt-switch::after {
    transition: none;
  }
}

/*
 * Rich inputs — a value with its unit inside the input, and the searchable select.
 *
 * `.dt-input-unit` draws an input and its unit ("km", "m/s") as one box, the unit at its end in
 * muted words. The unit is not decoration: the input names it in `aria-describedby`, so a screen
 * reader hears "Max distance, 12, km".
 *
 * `.dt-combobox` wraps a native `<select>`, which works with no script. The behaviour script
 * (`behaviour/combobox.js`) hides it and draws the parts below: a text box (`role="combobox"`)
 * that filters a list (`role="listbox"`) as the reader types, the chosen options as chips when
 * the select is multiple, and a message when nothing matches. `@deeptrack/react`'s `Combobox`
 * draws the same parts. A chosen option is ticked, and the option the arrows are on is
 * highlighted, so neither rests on colour.
 *
 * Markup:
 *   <div class="dt-input-unit">
 *     <input class="dt-input" type="number" id="dist" aria-describedby="dist-unit" />
 *     <span class="dt-input-unit__unit" id="dist-unit">km</span>
 *   </div>
 *
 *   <div class="dt-combobox" data-dt-combobox data-dt-search-url="/plants/search/">
 *     <select class="dt-select" id="plant" name="plant">…</select>
 *   </div>
 *
 * Drawn by the script:
 *   <div class="dt-combobox__control">
 *     <ul class="dt-combobox__chips" aria-label="Chosen">
 *       <li class="dt-combobox__chip"><span>TBOX 04</span>
 *         <button class="dt-combobox__remove" type="button" aria-label="Remove TBOX 04">×</button></li>
 *     </ul>
 *     <input class="dt-combobox__input" role="combobox" aria-expanded="true" aria-controls="…" />
 *   </div>
 *   <div class="dt-combobox__popup">
 *     <ul class="dt-combobox__listbox" role="listbox">
 *       <li class="dt-combobox__option dt-combobox__option--active" role="option">Lerma</li>
 *       <li class="dt-combobox__option dt-combobox__option--chosen" role="option">Lorca</li>
 *     </ul>
 *     <p class="dt-combobox__message">No results</p>
 *   </div>
 *   <span class="dt-combobox__status" role="status"></span>
 */

/* --- The unit inside the input ---------------------------------------------------------------- */

.dt-input-unit {
  display: flex;
  align-items: center;
  background-color: var(--dt-surface);
  border: 1px solid var(--dt-border-strong);
  border-radius: var(--dt-radius-md);
}

.dt-input-unit > .dt-input {
  flex: 1;
  min-width: 0;
  border: 0;
  background: transparent;
}

.dt-input-unit > .dt-input:focus-visible {
  outline: none;
  /* base.css's halo too: the box around it carries the ring. */
  box-shadow: none;
}

.dt-input-unit:has(> .dt-input:focus-visible) {
  outline: var(--dt-focus-width) solid var(--dt-focus-ring);
  outline-offset: var(--dt-focus-offset);
}

.dt-input-unit:has(> .dt-input[aria-invalid='true']) {
  border-color: var(--dt-error-solid);
}

.dt-input-unit:has(> .dt-input:disabled) {
  background-color: var(--dt-surface-sunken);
}

.dt-input-unit__unit {
  flex: none;
  padding-inline-end: var(--dt-space-3);
  font-family: var(--dt-font-sans);
  font-size: var(--dt-text-sm);
  line-height: var(--dt-leading-sm);
  color: var(--dt-text-muted);
  white-space: nowrap;
}

/* --- The searchable select -------------------------------------------------------------------- */

.dt-combobox {
  position: relative;
  font-family: var(--dt-font-sans);
}

.dt-combobox__control {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--dt-space-1);
  padding: var(--dt-space-1) var(--dt-space-2);
  background-color: var(--dt-surface);
  border: 1px solid var(--dt-border-strong);
  border-radius: var(--dt-radius-md);
  cursor: text;
}

.dt-combobox__control:has(.dt-combobox__input:focus-visible) {
  outline: var(--dt-focus-width) solid var(--dt-focus-ring);
  outline-offset: var(--dt-focus-offset);
}

.dt-combobox__control:has(.dt-combobox__input[aria-invalid='true']) {
  border-color: var(--dt-error-solid);
}

.dt-combobox__control:has(.dt-combobox__input:disabled) {
  background-color: var(--dt-surface-sunken);
  cursor: not-allowed;
}

.dt-combobox__input {
  flex: 1;
  min-width: 8ch;
  margin: 0;
  padding: var(--dt-space-1);
  font-family: inherit;
  font-size: var(--dt-text-sm);
  line-height: var(--dt-leading-sm);
  color: var(--dt-text);
  background: transparent;
  border: 0;
}

.dt-combobox__input:focus-visible {
  outline: none;
  /* base.css's halo too: the box around it carries the ring. */
  box-shadow: none;
}

.dt-combobox__input:disabled {
  color: var(--dt-text-muted);
  cursor: not-allowed;
}

.dt-combobox__chips {
  display: contents;
  margin: 0;
  padding: 0;
  list-style: none;
}

.dt-combobox__chip {
  display: inline-flex;
  align-items: center;
  gap: var(--dt-space-1);
  padding-inline: var(--dt-space-2) var(--dt-space-0_5);
  font-size: var(--dt-text-sm);
  line-height: var(--dt-leading-sm);
  color: var(--dt-text);
  background-color: var(--dt-surface-sunken);
  border: 1px solid var(--dt-border);
  border-radius: var(--dt-radius-full);
}

/* 24 px, WCAG 2.2's smallest target. */
.dt-combobox__remove {
  display: inline-grid;
  place-items: center;
  width: var(--dt-space-6);
  height: var(--dt-space-6);
  padding: 0;
  font: inherit;
  color: var(--dt-text-muted);
  background: none;
  border: 0;
  border-radius: var(--dt-radius-full);
  cursor: pointer;
}

.dt-combobox__remove:hover {
  color: var(--dt-text);
  background-color: var(--dt-border);
}

.dt-combobox__remove:focus-visible {
  outline: var(--dt-focus-width) solid var(--dt-focus-ring);
  outline-offset: 0;
}

.dt-combobox__popup {
  position: absolute;
  top: calc(100% + var(--dt-space-1));
  inset-inline: 0;
  z-index: var(--dt-z-dropdown);
  padding: var(--dt-space-1);
  background-color: var(--dt-surface);
  border: 1px solid var(--dt-border);
  border-radius: var(--dt-radius-lg);
  box-shadow: var(--dt-elevation-lg);
}

/* The inline variant lays the list in the flow of a page — a documentation specimen. */
.dt-combobox--inline .dt-combobox__popup {
  position: static;
  margin-block-start: var(--dt-space-1);
}

.dt-combobox__listbox {
  max-height: 16rem;
  margin: 0;
  padding: 0;
  overflow-y: auto;
  list-style: none;
}

.dt-combobox__option {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--dt-space-3);
  padding: var(--dt-space-2) var(--dt-space-3);
  font-size: var(--dt-text-sm);
  line-height: var(--dt-leading-sm);
  color: var(--dt-text);
  border-radius: var(--dt-radius-md);
  cursor: pointer;
}

.dt-combobox__option:hover {
  background-color: var(--dt-surface-sunken);
}

/* The option the arrows are on: a fill and an inset ring, so it is not colour alone. */
.dt-combobox__option--active {
  background-color: var(--dt-surface-sunken);
  box-shadow: inset 0 0 0 var(--dt-focus-width) var(--dt-focus-ring);
}

/* A chosen option is ticked: the tick is drawn, as the checkbox's is. */
.dt-combobox__option--chosen {
  font-weight: var(--dt-weight-medium);
}

.dt-combobox__option--chosen::after {
  content: '';
  flex: none;
  width: var(--dt-space-1_5);
  height: var(--dt-space-2_5);
  margin-inline-end: var(--dt-space-1);
  border: solid var(--dt-text-blue);
  border-width: 0 var(--dt-space-0_5) var(--dt-space-0_5) 0;
  transform: rotate(45deg);
}

.dt-combobox__option[aria-disabled='true'] {
  color: var(--dt-text-muted);
  cursor: not-allowed;
}

.dt-combobox__message {
  margin: 0;
  padding: var(--dt-space-2) var(--dt-space-3);
  font-size: var(--dt-text-sm);
  line-height: var(--dt-leading-sm);
  color: var(--dt-text-muted);
}

/* The announcement is for a screen reader: the list and its message already show it. */
.dt-combobox__status {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: 0;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

@media (forced-colors: active) {
  .dt-combobox__option--active {
    outline: 2px solid Highlight;
  }

  .dt-combobox__option--chosen::after {
    border-color: CanvasText;
  }
}

/*
 * Repeatable rows — a Django formset, or `@deeptrack/react`'s `RepeatableRows`: rows of the same
 * fields that the reader adds and removes ("Add another pair").
 *
 * Each row is a `<fieldset>` named by its legend ("Pair 2"), its fields side by side where they
 * fit and stacked on a phone, and its Remove button at the end, named by the row. Without the
 * behaviour script (`behaviour/formset.js`) a Django row shows its "Remove" checkbox instead, and
 * the Add button stays hidden; the script swaps them and keeps the numbering true.
 *
 * Markup:
 *   <div class="dt-formset" data-dt-formset>
 *     <input type="hidden" name="pairs-TOTAL_FORMS" value="2" /> …the management form…
 *     <div class="dt-formset__rows">
 *       <fieldset class="dt-formset__row" data-dt-formset-row>
 *         <legend class="dt-formset__legend" id="pairs-0-legend">Pair <span data-dt-formset-number>1</span></legend>
 *         <div class="dt-formset__fields">…field groups…</div>
 *         <div class="dt-formset__actions">
 *           <label class="dt-check" data-dt-formset-delete><input type="checkbox" class="dt-checkbox" name="pairs-0-DELETE" /> Remove</label>
 *           <button class="dt-button dt-button--ghost" type="button" data-dt-formset-remove hidden
 *                   id="pairs-0-remove" aria-labelledby="pairs-0-remove pairs-0-legend">Remove</button>
 *         </div>
 *       </fieldset>
 *     </div>
 *     <template data-dt-formset-empty>…a row, with __prefix__ and __num__…</template>
 *     <button class="dt-button dt-button--secondary" type="button" data-dt-formset-add hidden>Add another pair</button>
 *     <span class="dt-formset__status" role="status"></span>
 *   </div>
 */

.dt-formset {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--dt-space-3);
  font-family: var(--dt-font-sans);
}

.dt-formset__rows {
  display: flex;
  flex-direction: column;
  gap: var(--dt-space-3);
  align-self: stretch;
}

.dt-formset__row {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--dt-space-3);
  margin: 0;
  padding: var(--dt-space-3) var(--dt-space-4) var(--dt-space-4);
  border: 1px solid var(--dt-border);
  border-radius: var(--dt-radius-md);
}

.dt-formset__legend {
  padding-inline: var(--dt-space-1);
  font-size: var(--dt-text-sm);
  line-height: var(--dt-leading-sm);
  font-weight: var(--dt-weight-medium);
  color: var(--dt-text);
}

.dt-formset__fields {
  display: flex;
  flex: 1 1 24rem;
  flex-wrap: wrap;
  gap: var(--dt-space-3);
  min-width: 0;
}

.dt-formset__fields > * {
  flex: 1 1 12rem;
  min-width: 0;
}

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

/* The announcement is for a screen reader: the rows themselves show what changed. */
.dt-formset__status {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: 0;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/*
 * The rich-text editor — `django-prose-editor` (pinned in PTO at 0.27.3), in the system's look in
 * both modes. Nothing to add to a page: the bundle carries it, and it applies wherever the
 * editor's widget is.
 *
 * The editor reads six custom properties (`--prose-editor-*`); they are set from the tokens here,
 * on the editor itself, so they follow the mode of the part of the page it sits in (a dark panel
 * on a light page included: set on the root, they would carry the root's mode). The rest makes it read as one of ours:
 * the editing area is a `.dt-textarea` in all but name, with the department's focus ring; the
 * menu bar's active button is marked by an underline as well as its fill, so its state is not a
 * colour alone; and the editor's own dialogs (a link, an image) take the system's fields and
 * buttons. The higher specificity of some selectors (`[contenteditable]`, `dialog.`) is so they
 * hold whichever stylesheet the page loads last.
 *
 * Not covered: the admin's `overrides.css`, which the admin theme (roadmap 18.6) takes over.
 */

.prose-editor,
.prose-editor-dialog {
  --prose-editor-background: var(--dt-surface);
  --prose-editor-foreground: var(--dt-text);
  --prose-editor-border-color: var(--dt-border-strong);
  --prose-editor-active-color: var(--dt-fill-blue);
  --prose-editor-disabled-color: var(--dt-surface-sunken);
  --prose-editor-typographic: var(--dt-border);
}

/* --- The editing area --------------------------------------------------------------------- */

.prose-editor .ProseMirror[contenteditable='true']:not(.prose-menubar__picker *) {
  min-height: calc(var(--dt-leading-sm) * 6);
  padding: var(--dt-space-2) var(--dt-space-3);
  font-family: var(--dt-font-sans);
  font-size: var(--dt-text-sm);
  line-height: var(--dt-leading-sm);
  border-radius: 0 0 var(--dt-radius-md) var(--dt-radius-md);
}

.prose-editor .ProseMirror[contenteditable='true']:not(.prose-menubar__picker *):focus-visible {
  outline: var(--dt-focus-width) solid var(--dt-focus-ring);
  outline-offset: var(--dt-focus-offset);
}

.prose-editor .ProseMirror[contenteditable='true']:not(.prose-menubar__picker *) > * + * {
  margin-block-start: var(--dt-space-3);
}

.prose-editor .ProseMirror[contenteditable='true']:not(.prose-menubar__picker *) a {
  color: var(--dt-text-blue);
}

.prose-editor .ProseMirror[contenteditable='true']:not(.prose-menubar__picker *) blockquote {
  margin-inline: 0;
  padding-inline-start: var(--dt-space-3);
  border-inline-start: 2px solid var(--dt-border-strong);
  color: var(--dt-text-muted);
}

.prose-editor .ProseMirror[contenteditable='true']:not(.prose-menubar__picker *) pre {
  padding: var(--dt-space-3);
  font-family: var(--dt-font-mono);
  font-size: var(--dt-text-xs);
  background-color: var(--dt-surface-sunken);
  border-radius: var(--dt-radius-md);
}

.prose-editor .ProseMirror[contenteditable='true']:not(.prose-menubar__picker *) :is(th, td) {
  padding: var(--dt-space-1) var(--dt-space-2);
  border: 1px solid var(--dt-border-strong);
}

/* The placeholder, at a contrast that reads; the editor's own is half the text's. */
.prose-editor p.is-editor-empty:first-child::before {
  opacity: 1;
  color: var(--dt-text-muted);
}

/* --- The menu bar ------------------------------------------------------------------------- */

.prose-editor .prose-menubar:not(:empty, .prose-menubar--floating) {
  gap: var(--dt-space-1);
  padding: var(--dt-space-1);
  border: 1px solid var(--dt-border-strong);
  border-block-end-color: var(--dt-border);
  border-radius: var(--dt-radius-md) var(--dt-radius-md) 0 0;
  box-shadow: none;
}

/* The bar sits on the area's top edge, which it then draws. In the app frame, it sticks under the
   frame's own bar rather than sliding beneath it. */
.prose-editor
  .prose-menubar:not(:empty, .prose-menubar--floating)
  ~ .ProseMirror[contenteditable='true'] {
  border-block-start: 0;
}

.dt-app .prose-editor .prose-menubar:not(.prose-menubar--floating) {
  top: var(--dtc-app-bar-height);
}

.prose-editor .prose-menubar__button {
  height: var(--dt-space-8);
  color: var(--dt-text);
}

.prose-editor .prose-menubar__button:hover {
  filter: none;
  background-color: var(--dt-surface-sunken);
}

/* On: filled in the corporate blue, its words in the colour that reads on it, and underlined, so
   the state is a shape too. */
.prose-editor .prose-menubar__button.active {
  color: var(--dt-on-fill-blue);
  box-shadow: inset 0 calc(var(--dt-space-0_5) * -1) 0 var(--dt-on-fill-blue);
}

.prose-editor .prose-menubar__button.active:hover {
  background-color: var(--dt-fill-blue-soft);
  color: var(--dt-on-fill-blue-soft);
}

.prose-editor .prose-menubar__button:focus-visible {
  z-index: 1;
  outline: var(--dt-focus-width) solid var(--dt-focus-ring);
  outline-offset: calc(var(--dt-focus-width) * -1);
}

/* The block-type picker: a highlighted choice reads in the text colour, on a quiet fill. */
.prose-editor .prose-menubar__picker .ProseMirror > :hover {
  color: var(--dt-text) !important;
  background: var(--dt-surface-sunken) !important;
}

/* --- The editor's own dialogs ------------------------------------------------------------- */

dialog.prose-editor-dialog {
  padding: var(--dt-space-6);
  font-family: var(--dt-font-sans);
  font-size: var(--dt-text-sm);
  border-color: var(--dt-border);
  border-radius: var(--dt-radius-xl);
  box-shadow: var(--dt-elevation-xl);
}

dialog.prose-editor-dialog::backdrop {
  background-color: rgb(from var(--dt-neutral-950) r g b / 0.5);
}

dialog.prose-editor-dialog :is(input:not([type='checkbox'], [type='radio']), select, textarea) {
  padding: var(--dt-space-2) var(--dt-space-3);
  border-radius: var(--dt-radius-md);
  color: var(--dt-text);
  background-color: var(--dt-surface);
}

dialog.prose-editor-dialog :is(input, select, textarea):focus {
  box-shadow: none;
  outline: var(--dt-focus-width) solid var(--dt-focus-ring);
  outline-offset: var(--dt-focus-offset);
}

dialog.prose-editor-dialog button {
  padding: var(--dt-space-2) var(--dt-space-4);
  font-weight: var(--dt-weight-medium);
  border-color: var(--dt-border-strong);
  border-radius: var(--dt-radius-md);
}

dialog.prose-editor-dialog button:hover {
  filter: none;
  background-color: var(--dt-surface-sunken);
}

dialog.prose-editor-dialog button:focus-visible {
  outline: var(--dt-focus-width) solid var(--dt-focus-ring);
  outline-offset: var(--dt-focus-offset);
}

@media (forced-colors: active) {
  .prose-editor .prose-menubar__button.active {
    outline: 2px solid Highlight;
    outline-offset: -2px;
  }

  .prose-editor .prose-menubar__button:focus-visible {
    outline-color: CanvasText;
  }

  .prose-editor .prose-menubar__button.disabled {
    color: GrayText;
  }
}

/*
 * `.dt-modal` — a dialog on the native `<dialog>` element, opened with `showModal()`. The
 * browser then supplies what a hand-rolled overlay gets wrong: the rest of the page is inert,
 * focus moves into the dialog and returns to the trigger on close, Escape closes it, and the
 * dialog sits in the top layer above every z-index. The backdrop is the element's own
 * `::backdrop`.
 *
 * Four uses, one element:
 *  - **Confirm** (`role="alertdialog"`): the action in words, Cancel first and focused, and the
 *    action's button in a form that POSTs it. For the most dangerous, **type to confirm**: an
 *    input `data-dt-confirm-text="TBOX 04"` keeps the `[data-dt-confirm-action]` button disabled
 *    until the reader types those words (`behaviour/confirm.js`).
 *  - **A form**: the dialog holds the `<form>`, its footer inside it.
 *  - **Information**: logs and command output in `.dt-modal__log`, with Close.
 *
 * Markup:
 *   <dialog class="dt-modal" aria-labelledby="t">
 *     <div class="dt-modal__header"><h2 class="dt-modal__title" id="t">Title</h2></div>
 *     <div class="dt-modal__body">Content.</div>
 *     <div class="dt-modal__footer"><button class="dt-button …">OK</button></div>
 *   </dialog>
 */

.dt-modal {
  width: min(100% - var(--dt-space-8), calc(var(--dt-space-24) * 6));
  max-height: calc(100% - var(--dt-space-16));
  /* The platform centres a modal dialog with `margin: auto`; the reset's `margin: 0` undid it. */
  margin: auto;
  padding: 0;
  font-family: var(--dt-font-sans);
  color: var(--dt-text);
  background-color: var(--dt-surface);
  border: 1px solid var(--dt-border);
  border-radius: var(--dt-radius-xl);
  box-shadow: var(--dt-elevation-xl);
}

.dt-modal::backdrop {
  background-color: rgb(from var(--dt-neutral-950) r g b / 0.5);
}

/* The inline variant renders a dialog in the flow of a page — a documentation specimen. */
.dt-modal--inline {
  position: static;
  display: block;
  margin: 0;
}

.dt-modal[open] {
  animation: dt-modal-in var(--dt-duration-base) var(--dt-ease-decelerate);
}

@keyframes dt-modal-in {
  from {
    opacity: 0;
    transform: translateY(var(--dt-space-2));
  }
}

.dt-modal__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--dt-space-4);
  padding: var(--dt-space-4) var(--dt-space-6);
  border-bottom: 1px solid var(--dt-border);
}

.dt-modal__title {
  margin: 0;
  font-size: var(--dt-text-lg);
  line-height: var(--dt-leading-lg);
  font-weight: var(--dt-weight-semibold);
}

.dt-modal__body {
  display: flex;
  flex-direction: column;
  gap: var(--dt-space-3);
  padding: var(--dt-space-6);
  font-size: var(--dt-text-sm);
  line-height: var(--dt-leading-sm);
}

.dt-modal__footer {
  display: flex;
  justify-content: flex-end;
  gap: var(--dt-space-2);
  padding: var(--dt-space-4) var(--dt-space-6);
  border-top: 1px solid var(--dt-border);
  background-color: var(--dt-surface-sunken);
}

/* Logs and command output: kept as written, scrolling within the dialog. */
.dt-modal__log {
  max-height: 24rem;
  margin: 0;
  padding: var(--dt-space-3) var(--dt-space-4);
  overflow: auto;
  font-family: var(--dt-font-mono);
  font-size: var(--dt-text-xs);
  line-height: var(--dt-leading-xs);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  color: var(--dt-text);
  background-color: var(--dt-surface-sunken);
  border: 1px solid var(--dt-border);
  border-radius: var(--dt-radius-md);
}

/* A form inside the dialog lays out as the dialog does: body, then its footer. */
.dt-modal > form {
  display: contents;
}

@media (prefers-reduced-motion: reduce) {
  .dt-modal[open] {
    animation: none;
  }
}

/*
 * `.dt-menu` — the popup list of a dropdown (menu button). Positioned under its trigger by a
 * `.dt-menu-anchor` wrapper; keyboard behaviour (arrows, Home/End, Escape) belongs to the
 * script driving it, and `@deeptrack/react`'s `Dropdown` supplies it.
 *
 * Markup:
 *   <div class="dt-menu-anchor">
 *     <button class="dt-button …" aria-haspopup="menu" aria-expanded="true">Actions</button>
 *     <ul class="dt-menu" role="menu">
 *       <li class="dt-menu__item" role="menuitem" tabindex="-1">Export</li>
 *       <li role="none"><a class="dt-menu__item" role="menuitem" href="/report">Report</a></li>
 *     </ul>
 *   </div>
 */

.dt-menu-anchor {
  position: relative;
  display: inline-block;
}

.dt-menu {
  position: absolute;
  top: calc(100% + var(--dt-space-1));
  left: 0;
  z-index: var(--dt-z-dropdown);
  min-width: 100%;
  margin: 0;
  padding: var(--dt-space-1);
  list-style: none;
  font-family: var(--dt-font-sans);
  background-color: var(--dt-surface);
  border: 1px solid var(--dt-border);
  border-radius: var(--dt-radius-lg);
  box-shadow: var(--dt-elevation-lg);
}

.dt-menu--end {
  left: auto;
  right: 0;
}

/* The inline variant lays the list in the flow of a page — a documentation specimen. */
.dt-menu--inline {
  position: static;
  display: inline-block;
}

.dt-menu__item {
  display: block;
  white-space: nowrap;
  padding: var(--dt-space-2) var(--dt-space-3);
  border-radius: var(--dt-radius-md);
  font-size: var(--dt-text-sm);
  line-height: var(--dt-leading-sm);
  color: var(--dt-text);
  cursor: pointer;
}

/* An item that is a link or a button looks like any other: full width, no underline, no chrome. */
a.dt-menu__item,
button.dt-menu__item {
  box-sizing: border-box;
  width: 100%;
  border: 0;
  background: none;
  font-family: inherit;
  text-align: start;
  text-decoration: none;
}

.dt-menu__item:hover,
.dt-menu__item:focus-visible {
  background-color: var(--dt-surface-sunken);
}

.dt-menu__item:focus-visible {
  outline: var(--dt-focus-width) solid var(--dt-focus-ring);
  outline-offset: calc(var(--dt-focus-width) * -1);
}

.dt-menu__item[aria-disabled='true'] {
  color: var(--dt-text-muted);
  cursor: not-allowed;
}

.dt-menu__item[aria-disabled='true']:hover {
  background-color: transparent;
}

/* The item that is the current choice (a context switcher's plant): ticked and in weight, so it
   is not colour alone. `aria-current="true"` says it to a screen reader. */
.dt-menu__item[aria-current='true'] {
  display: flex;
  align-items: center;
  gap: var(--dt-space-3);
  font-weight: var(--dt-weight-semibold);
}

.dt-menu__item[aria-current='true']::after {
  content: '';
  flex: none;
  width: var(--dt-space-1_5);
  height: var(--dt-space-2_5);
  margin-inline: auto var(--dt-space-1);
  border: solid var(--dt-text-blue);
  border-width: 0 var(--dt-space-0_5) var(--dt-space-0_5) 0;
  transform: rotate(45deg);
}

/*
 * `.dt-avatar` — a person or a system, as a picture or as initials. One fill for everyone:
 * hashing a name onto a set of hues would make identity look like status, and colour carries
 * neither here. The initials are text on `--dt-fill-blue-soft`, a gated pairing.
 *
 * Markup: `<span class="dt-avatar dt-avatar--md" role="img" aria-label="Ana Ruiz">AR</span>`,
 * or `<img class="dt-avatar dt-avatar--md" src="…" alt="Ana Ruiz" />`.
 */

.dt-avatar {
  display: inline-flex;
  flex: none;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  object-fit: cover;
  border-radius: var(--dt-radius-full);
  background-color: var(--dt-fill-blue-soft);
  color: var(--dt-on-fill-blue-soft);
  font-family: var(--dt-font-sans);
  font-weight: var(--dt-weight-semibold);
  user-select: none;
}

.dt-avatar--sm {
  width: var(--dt-space-6);
  height: var(--dt-space-6);
  font-size: var(--dt-text-2xs);
}

.dt-avatar--md {
  width: var(--dt-space-10);
  height: var(--dt-space-10);
  font-size: var(--dt-text-sm);
}

.dt-avatar--lg {
  width: var(--dt-space-14);
  height: var(--dt-space-14);
  font-size: var(--dt-text-lg);
}

/*
 * `.dt-tabs` — a tab list and its panels. The selected tab is marked three ways — a weight, an
 * underline and `aria-selected` — so it does not rest on colour; the underline is
 * `--dt-text-blue`, a gated reading colour, so it stays 3:1 against the surface in both modes.
 *
 * Markup:
 *   <div class="dt-tabs">
 *     <div class="dt-tabs__list" role="tablist" aria-label="…">
 *       <button class="dt-tabs__tab" role="tab" aria-selected="true" aria-controls="p1" id="t1">…</button>
 *     </div>
 *     <div class="dt-tabs__panel" role="tabpanel" id="p1" aria-labelledby="t1" tabindex="0">…</div>
 *   </div>
 */

.dt-tabs {
  font-family: var(--dt-font-sans);
}

/*
 * The rule under the tabs is an inset shadow, not a border, so the selected tab's underline can
 * sit over it without overflowing the list. It was a border with `margin-bottom: -1px` on each
 * tab; since `overflow-x: auto` makes `overflow-y` auto as well, that 1 px of overlap drew a
 * vertical scrollbar beside the tabs wherever scrollbars take space (Windows).
 */
.dt-tabs__list {
  display: flex;
  gap: var(--dt-space-1);
  box-shadow: inset 0 -1px 0 var(--dt-border);
  overflow-x: auto;
}

.dt-tabs__tab {
  position: relative;
  padding: var(--dt-space-2) var(--dt-space-3);
  font-family: inherit;
  font-size: var(--dt-text-sm);
  line-height: var(--dt-leading-sm);
  font-weight: var(--dt-weight-medium);
  color: var(--dt-text-muted);
  background: none;
  border: none;
  border-bottom: 2px solid transparent;
  cursor: pointer;
  white-space: nowrap;
  transition:
    color var(--dt-duration-fast) var(--dt-ease-standard),
    border-color var(--dt-duration-fast) var(--dt-ease-standard);
}

.dt-tabs__tab:hover {
  color: var(--dt-text);
}

.dt-tabs__tab[aria-selected='true'] {
  color: var(--dt-text);
  font-weight: var(--dt-weight-semibold);
  border-bottom-color: var(--dt-text-blue);
}

.dt-tabs__tab:disabled {
  color: var(--dt-text-muted);
  opacity: 0.5;
  cursor: not-allowed;
}

.dt-tabs__tab:focus-visible {
  outline: var(--dt-focus-width) solid var(--dt-focus-ring);
  outline-offset: calc(var(--dt-focus-width) * -1);
}

.dt-tabs__panel {
  padding: var(--dt-space-4) 0;
  font-size: var(--dt-text-sm);
  line-height: var(--dt-leading-sm);
  color: var(--dt-text);
}

.dt-tabs__panel:focus-visible {
  outline: var(--dt-focus-width) solid var(--dt-focus-ring);
  outline-offset: var(--dt-focus-offset);
}

/* Motion is decoration here: with reduced motion asked for, state changes are instant. */
@media (prefers-reduced-motion: reduce) {
  .dt-tabs__tab {
    transition: none;
  }
}

/*
 * `.dt-disclosure` — a button that shows and hides the region after it; `.dt-accordion` — a set
 * of them, one above the other, in one bordered block.
 *
 * The button says its state with `aria-expanded` and a chevron that turns. The panel's `hidden`
 * is the behaviour script's to set (`@deeptrack/css/behaviour.js`), or `@deeptrack/react`'s
 * `Disclosure`: a server renders every panel without it, and the script hides the ones whose
 * button says `aria-expanded="false"`. A page whose script never loads then shows every panel,
 * so nothing is out of reach.
 *
 * In an accordion, ArrowUp and ArrowDown move between the buttons, Home and End jump to the
 * first and last, and `data-dt-accordion="single"` keeps at most one panel open.
 *
 * Markup:
 *   <div class="dt-accordion" data-dt-accordion>
 *     <div class="dt-disclosure">
 *       <h3 class="dt-disclosure__heading">
 *         <button class="dt-disclosure__trigger" type="button" aria-expanded="false"
 *                 aria-controls="p1" data-dt-disclosure>Network</button>
 *       </h3>
 *       <div class="dt-disclosure__panel" id="p1">…</div>
 *     </div>
 *   </div>
 *
 * A disclosure on its own is the same `.dt-disclosure` without the accordion around it.
 */

.dt-disclosure {
  font-family: var(--dt-font-sans);
}

.dt-disclosure__heading {
  margin: 0;
  font-size: inherit;
  font-weight: inherit;
  line-height: inherit;
}

.dt-disclosure__trigger {
  display: flex;
  align-items: center;
  gap: var(--dt-space-2);
  width: 100%;
  padding: var(--dt-space-2) 0;
  border: 0;
  background-color: transparent;
  font-family: inherit;
  font-size: var(--dt-text-sm);
  line-height: var(--dt-leading-sm);
  font-weight: var(--dt-weight-semibold);
  text-align: start;
  color: var(--dt-text);
  cursor: pointer;
}

/* The chevron: points right when closed, down when open. Drawn in the text colour. */
.dt-disclosure__trigger::after {
  content: '';
  flex: none;
  width: var(--dt-space-1_5);
  height: var(--dt-space-1_5);
  margin-inline-start: auto;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: rotate(-45deg);
  transition: transform var(--dt-duration-fast) var(--dt-ease-standard);
}

.dt-disclosure__trigger[aria-expanded='true']::after {
  transform: rotate(45deg);
}

.dt-disclosure__trigger:focus-visible {
  outline: var(--dt-focus-width) solid var(--dt-focus-ring);
  outline-offset: var(--dt-focus-offset);
}

.dt-disclosure__panel {
  padding-bottom: var(--dt-space-3);
  font-size: var(--dt-text-sm);
  line-height: var(--dt-leading-sm);
  color: var(--dt-text);
}

.dt-accordion {
  border: 1px solid var(--dt-border);
  border-radius: var(--dt-radius-lg);
  background-color: var(--dt-surface);
}

.dt-accordion > .dt-disclosure {
  padding-inline: var(--dt-space-4);
}

.dt-accordion > .dt-disclosure + .dt-disclosure {
  border-top: 1px solid var(--dt-border);
}

.dt-accordion .dt-disclosure__trigger {
  padding-block: var(--dt-space-3);
}

/* Motion is decoration here: with reduced motion asked for, state changes are instant. */
@media (prefers-reduced-motion: reduce) {
  .dt-disclosure__trigger::after {
    transition: none;
  }
}

/*
 * `.dt-tooltip` — the name of an icon-only button, shown when the button is hovered or focused.
 * It is for icon-only buttons and nothing else: the tooltip's text **is** the button's
 * accessible name (`aria-labelledby`), so a screen reader hears it once, as the name, and a
 * sighted reader sees the same words. Help text goes under a field, never in a tooltip.
 *
 * It shows with no script: on hover of the anchor, which holds the tooltip, so the pointer can
 * move onto the tooltip without it vanishing, and on keyboard focus of the button. The behaviour
 * script (`@deeptrack/css/behaviour.js`) adds the one thing CSS cannot do: Escape hides it until
 * the pointer leaves or focus moves on (WCAG 1.4.13).
 *
 * It does not reposition itself: one at the very edge of a page can be cut off there.
 *
 * Markup:
 *   <span class="dt-tooltip-anchor" data-dt-tooltip>
 *     <button class="dt-button dt-button--ghost" type="button" aria-labelledby="tip-1">
 *       <svg aria-hidden="true" …></svg>
 *     </button>
 *     <span class="dt-tooltip" role="tooltip" id="tip-1">Delete device</span>
 *   </span>
 */

.dt-tooltip-anchor {
  position: relative;
  display: inline-flex;
}

.dt-tooltip {
  position: absolute;
  bottom: calc(100% + var(--dt-space-1));
  left: 50%;
  z-index: var(--dt-z-tooltip);
  padding: var(--dt-space-1) var(--dt-space-2);
  border-radius: var(--dt-radius-md);
  background-color: var(--dt-text);
  color: var(--dt-surface);
  font-family: var(--dt-font-sans);
  font-size: var(--dt-text-xs);
  line-height: var(--dt-leading-xs);
  font-weight: var(--dt-weight-medium);
  white-space: nowrap;
  pointer-events: auto;
  transform: translateX(-50%);
  visibility: hidden;
  opacity: 0;
  transition:
    opacity var(--dt-duration-fast) var(--dt-ease-standard),
    visibility 0s linear var(--dt-duration-fast);
}

/* A transparent strip over the gap, so the pointer can cross onto the tooltip without leaving. */
.dt-tooltip::before {
  content: '';
  position: absolute;
  inset-inline: 0;
  top: 100%;
  height: calc(var(--dt-space-1) + 1px);
}

.dt-tooltip--below::before {
  top: auto;
  bottom: 100%;
}

.dt-tooltip-anchor:hover > .dt-tooltip,
.dt-tooltip-anchor:has(:focus-visible) > .dt-tooltip {
  visibility: visible;
  opacity: 1;
  transition: opacity var(--dt-duration-fast) var(--dt-ease-standard);
}

/* Escape dismissed it: hidden until the pointer leaves or focus moves (the script clears this). */
.dt-tooltip-anchor[data-dt-tooltip='dismissed'] > .dt-tooltip {
  visibility: hidden;
  opacity: 0;
}

/* Below the trigger, for a button at the top of a page or a dialog. */
.dt-tooltip--below {
  top: calc(100% + var(--dt-space-1));
  bottom: auto;
}

@media (prefers-reduced-motion: reduce) {
  .dt-tooltip,
  .dt-tooltip-anchor:hover > .dt-tooltip,
  .dt-tooltip-anchor:has(:focus-visible) > .dt-tooltip {
    transition: none;
  }
}

/*
 * Progress — how far a piece of work has got, in words as well as in a shape.
 *
 * `.dt-progress` is a labelled bar on the native `<progress>`, so the platform gives it its role
 * and its value with no script. With a `value` it is determinate; without one it is
 * indeterminate: work is going on and how much is left is not known. The value is said in words
 * beside the bar (`.dt-progress__value`), and `aria-valuetext` says the same to a screen reader
 * when the words are not a percentage ("3 of 7 files").
 *
 * `.dt-steps` is step progress: an ordered list of named steps, each pending, running, done or
 * failed, and each saying so in a word (`.dt-steps__state`), so a state never rests on its colour
 * or its marker. `aria-current="step"` marks the running step.
 *
 * Both announce politely (`role="status"`), never on every change: the bar when it passes a
 * quarter and when it completes, the steps when the summary line changes ("Step 2 of 4, Flash
 * firmware: Running"). The behaviour script writes those lines on a server-rendered page
 * (`behaviour/progress.js`), and `@deeptrack/react`'s `Progress` and `StepProgress` do the same.
 *
 * Markup:
 *   <div class="dt-progress" data-dt-progress>
 *     <div class="dt-progress__header">
 *       <label class="dt-progress__label" for="upload">Uploading firmware</label>
 *       <span class="dt-progress__value" aria-hidden="true">42%</span>
 *     </div>
 *     <progress class="dt-progress__bar" id="upload" max="100" value="42">42%</progress>
 *     <span class="dt-progress__status" role="status"></span>
 *   </div>
 *
 *   <div class="dt-steps" data-dt-steps>
 *     <ol class="dt-steps__list" aria-label="Commissioning TBOX 04">
 *       <li class="dt-steps__step" data-dt-state="done">
 *         <span class="dt-steps__marker" aria-hidden="true">1</span>
 *         <span class="dt-steps__label">Read parameters</span>
 *         <span class="dt-steps__state">Done</span>
 *       </li>
 *       <li class="dt-steps__step" data-dt-state="running" aria-current="step">…</li>
 *     </ol>
 *     <p class="dt-steps__status" role="status">Step 2 of 4, Flash firmware: Running</p>
 *   </div>
 */

/* --- The bar ------------------------------------------------------------------------------ */

.dt-progress {
  display: flex;
  flex-direction: column;
  gap: var(--dt-space-1);
  font-family: var(--dt-font-sans);
}

.dt-progress__header {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--dt-space-3);
  font-size: var(--dt-text-sm);
  line-height: var(--dt-leading-sm);
}

.dt-progress__label {
  color: var(--dt-text);
  font-weight: var(--dt-weight-medium);
}

.dt-progress__value {
  color: var(--dt-text-muted);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* The announcement is for a screen reader: the value beside the bar already shows it. */
.dt-progress__status {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: 0;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/*
 * The track is outlined in `border-strong`, the gated boundary of a control, so the whole the
 * fill is a part of can be seen; the fill against the track clears 3:1 in both modes.
 */
.dt-progress__bar {
  appearance: none;
  display: block;
  box-sizing: border-box;
  width: 100%;
  height: var(--dt-space-2);
  overflow: hidden;
  border: 1px solid var(--dt-border-strong);
  border-radius: var(--dt-radius-full);
  background-color: var(--dt-surface-sunken);
  color: var(--dt-fill-blue);
}

.dt-progress__bar::-webkit-progress-bar {
  background-color: var(--dt-surface-sunken);
}

.dt-progress__bar::-webkit-progress-value {
  background-color: var(--dt-fill-blue);
  transition: width var(--dt-duration-base) var(--dt-ease-standard);
}

.dt-progress__bar::-moz-progress-bar {
  background-color: var(--dt-fill-blue);
}

/* Indeterminate: a band that travels the track. The words beside it carry the meaning. */
.dt-progress__bar:indeterminate {
  background-image: linear-gradient(
    90deg,
    transparent 0%,
    var(--dt-fill-blue) 35%,
    var(--dt-fill-blue) 65%,
    transparent 100%
  );
  background-size: 40% 100%;
  background-repeat: no-repeat;
  animation: dt-progress-travel 1.5s var(--dt-ease-standard) infinite;
}

.dt-progress__bar:indeterminate::-webkit-progress-bar {
  background-color: transparent;
}

.dt-progress__bar:indeterminate::-moz-progress-bar {
  background-color: transparent;
}

@keyframes dt-progress-travel {
  from {
    background-position: -40% 0;
  }

  to {
    background-position: 140% 0;
  }
}

/* --- The steps ---------------------------------------------------------------------------- */

.dt-steps {
  container-type: inline-size;
  display: flex;
  flex-direction: column;
  gap: var(--dt-space-3);
  font-family: var(--dt-font-sans);
}

.dt-steps__list {
  display: flex;
  flex-direction: column;
  margin: 0;
  padding: 0;
  list-style: none;
}

/* Text sits level with the marker's middle: the marker is taller than a line by this much. */
.dt-steps__step {
  --dtc-steps-marker: var(--dt-space-6);
  --dtc-steps-inset: calc((var(--dtc-steps-marker) - var(--dt-leading-sm)) / 2);
  position: relative;
  display: grid;
  grid-template-columns: var(--dtc-steps-marker) minmax(0, 1fr) auto;
  align-items: start;
  column-gap: var(--dt-space-3);
  padding-block: var(--dt-space-2);
  font-size: var(--dt-text-sm);
  line-height: var(--dt-leading-sm);
}

/* Narrower than the one breakpoint (layout.css), the state goes under its label. */
@container (width < 48rem) {
  .dt-steps__step {
    grid-template-columns: var(--dtc-steps-marker) minmax(0, 1fr);
  }

  /* Two classes, to outrank the state's own padding further down. */
  .dt-steps__step .dt-steps__state {
    grid-column: 2;
    padding-block-start: 0;
  }
}

/* The rail between two markers: from under this one to the top of the next. */
.dt-steps__step:not(:last-child)::after {
  content: '';
  position: absolute;
  inset-inline-start: calc(var(--dtc-steps-marker) / 2 - 1px);
  top: calc(var(--dt-space-2) + var(--dtc-steps-marker));
  height: calc(100% - var(--dtc-steps-marker));
  border-inline-start: 2px solid var(--dt-border);
}

.dt-steps__step[data-dt-state='done']:not(:last-child)::after {
  border-color: var(--dt-success-solid);
}

.dt-steps__marker {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  width: var(--dtc-steps-marker);
  height: var(--dtc-steps-marker);
  border: 2px solid var(--dt-border-strong);
  border-radius: var(--dt-radius-full);
  background-color: var(--dt-surface);
  color: var(--dt-text-muted);
  font-size: var(--dt-text-xs);
  font-weight: var(--dt-weight-semibold);
  font-variant-numeric: tabular-nums;
}

.dt-steps__label {
  padding-block: var(--dtc-steps-inset);
  color: var(--dt-text);
  overflow-wrap: anywhere;
}

.dt-steps__state {
  padding-block: var(--dtc-steps-inset);
  color: var(--dt-text-muted);
  overflow-wrap: anywhere;
}

/* Running: the marker's ring turns, and the step is the one the reader is waiting on. */
.dt-steps__step[data-dt-state='running'] .dt-steps__marker {
  border-color: var(--dt-fill-blue);
  color: var(--dt-text-blue);
}

.dt-steps__step[data-dt-state='running'] .dt-steps__marker::before {
  content: '';
  position: absolute;
  inset: -2px;
  border: 2px solid transparent;
  border-top-color: var(--dt-surface);
  border-radius: inherit;
  animation: dt-steps-turn 1s linear infinite;
}

.dt-steps__step[data-dt-state='running'] .dt-steps__label,
.dt-steps__step[data-dt-state='running'] .dt-steps__state {
  color: var(--dt-text);
  font-weight: var(--dt-weight-semibold);
}

/* Done and failed: the number gives way to a mark, and the word stays. */
.dt-steps__step[data-dt-state='done'] .dt-steps__marker,
.dt-steps__step[data-dt-state='failed'] .dt-steps__marker {
  font-size: 0;
}

.dt-steps__step[data-dt-state='done'] .dt-steps__marker {
  border-color: var(--dt-success-solid);
  background-color: var(--dt-success-solid);
}

/* A tick, drawn: two sides of a box turned 45°, in the colour placed on the solid fill. */
.dt-steps__step[data-dt-state='done'] .dt-steps__marker::before {
  content: '';
  width: 30%;
  height: 55%;
  margin-block-start: -12%;
  border-inline-end: 2px solid var(--dt-success-on-solid);
  border-block-end: 2px solid var(--dt-success-on-solid);
  transform: rotate(45deg);
}

.dt-steps__step[data-dt-state='done'] .dt-steps__state {
  color: var(--dt-success-text);
}

.dt-steps__step[data-dt-state='failed'] .dt-steps__marker {
  border-color: var(--dt-error-solid);
  background-color: var(--dt-error-solid);
}

/* An exclamation mark, as text the face already has. */
.dt-steps__step[data-dt-state='failed'] .dt-steps__marker::before {
  content: '!';
  content: '!' / '';
  color: var(--dt-error-on-solid);
  font-size: var(--dt-text-sm);
}

.dt-steps__step[data-dt-state='failed'] .dt-steps__label,
.dt-steps__step[data-dt-state='failed'] .dt-steps__state {
  color: var(--dt-error-text);
  font-weight: var(--dt-weight-semibold);
}

.dt-steps__status {
  margin: 0;
  font-size: var(--dt-text-sm);
  line-height: var(--dt-leading-sm);
  color: var(--dt-text-muted);
}

@keyframes dt-steps-turn {
  to {
    transform: rotate(1turn);
  }
}

/*
 * Forced colours (Windows high contrast) drop every background, which is how the fill and the
 * markers are drawn: they take the system's colours instead, and the words stay as they were.
 */
@media (forced-colors: active) {
  .dt-progress__bar::-webkit-progress-value {
    background-color: Highlight;
  }

  .dt-progress__bar::-moz-progress-bar {
    background-color: Highlight;
  }

  .dt-progress__bar:indeterminate {
    background-image: linear-gradient(90deg, Highlight, Highlight);
  }

  .dt-steps__marker,
  .dt-steps__step[data-dt-state] .dt-steps__marker {
    forced-color-adjust: none;
    border-color: CanvasText;
    background-color: Canvas;
    color: CanvasText;
  }

  .dt-steps__step[data-dt-state='done'] .dt-steps__marker,
  .dt-steps__step[data-dt-state='failed'] .dt-steps__marker {
    background-color: CanvasText;
  }

  .dt-steps__step[data-dt-state='done'] .dt-steps__marker::before {
    border-color: Canvas;
  }

  .dt-steps__step[data-dt-state='failed'] .dt-steps__marker::before {
    color: Canvas;
  }
}

/* With reduced motion asked for, nothing travels or turns: the words say it is going on. */
@media (prefers-reduced-motion: reduce) {
  .dt-progress__bar:indeterminate {
    animation: none;
    background-position: 50% 0;
  }

  .dt-progress__bar::-webkit-progress-value {
    transition: none;
  }

  .dt-steps__step[data-dt-state='running'] .dt-steps__marker::before {
    animation: none;
  }
}

/*
 * The states of one part of a page — a card, a table, a panel — so that part, not the page,
 * says it is empty, loading or failed.
 *
 *  - `.dt-region` is that part: the element whose content is swapped as it loads — in a card,
 *    its `.dt-card__body`, which keeps the card's padding and header. It is
 *    `aria-busy="true"` while loading, so assistive technology waits for it rather than reading
 *    half of it, and `tabindex="-1"`, so focus can rest on it when the control that had focus is
 *    replaced (a "Try again" that started the load again).
 *  - `.dt-empty` is an empty state with its action: "No devices on this TBOX yet", and the button
 *    that adds one. `--compact` for a table cell or a small card.
 *  - `.dt-skeleton` is a placeholder in the shape of the content to come, `--text` (a line),
 *    `--title`, `--block` and `--circle`. It is `aria-hidden`: the region's own words say it is
 *    loading.
 *  - A region that failed holds a `.dt-alert--error` with `role="alert"`, saying what failed in
 *    words, and a `data-dt-retry` button. The behaviour script (`behaviour/region.js`) turns the
 *    press into a cancelable `dt:retry` event on the region, for the page to load it again; unless
 *    the page cancels it, the region is marked busy and takes focus.
 *
 * Markup:
 *   <div class="dt-card">
 *     <div class="dt-card__body dt-region" data-dt-region tabindex="-1" aria-busy="true">
 *       <span class="dt-visually-hidden">Loading devices</span>
 *       <div class="dt-skeleton dt-skeleton--title" aria-hidden="true"></div>
 *       <div class="dt-skeleton dt-skeleton--text" aria-hidden="true"></div>
 *     </div>
 *   </div>
 *
 *   <div class="dt-empty">
 *     <svg class="dt-icon dt-icon--lg dt-empty__icon" aria-hidden="true">…</svg>
 *     <p class="dt-empty__title">No devices on this TBOX yet</p>
 *     <p class="dt-empty__text">Devices appear here once they are added or reported.</p>
 *     <div class="dt-empty__actions"><a class="dt-button dt-button--primary" href="…">Add a device</a></div>
 *   </div>
 *
 *   <div class="dt-alert dt-alert--error" role="alert">
 *     <div class="dt-alert__body">
 *       <strong>Error —</strong> The devices could not be loaded.
 *       <div class="dt-alert__actions">
 *         <button class="dt-button dt-button--secondary" type="button" data-dt-retry>Try again</button>
 *       </div>
 *     </div>
 *   </div>
 */

.dt-region {
  position: relative;
}

/* Focus put here by a script shows a ring only when the reader is on the keyboard. */
.dt-region:focus-visible {
  outline: var(--dt-focus-width) solid var(--dt-focus-ring);
  outline-offset: var(--dt-focus-offset);
}

/* --- Empty -------------------------------------------------------------------------------- */

.dt-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--dt-space-2);
  padding: var(--dt-space-8) var(--dt-space-4);
  font-family: var(--dt-font-sans);
  text-align: center;
}

.dt-empty--compact {
  padding: var(--dt-space-4);
}

.dt-empty__icon {
  color: var(--dt-text-muted);
}

.dt-empty__title {
  margin: 0;
  font-size: var(--dt-text-base);
  line-height: var(--dt-leading-base);
  font-weight: var(--dt-weight-semibold);
  color: var(--dt-text);
}

.dt-empty__text {
  margin: 0;
  max-width: 40ch;
  font-size: var(--dt-text-sm);
  line-height: var(--dt-leading-sm);
  color: var(--dt-text-muted);
}

.dt-empty__actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--dt-space-2);
  margin-block-start: var(--dt-space-2);
}

/* --- Skeleton ----------------------------------------------------------------------------- */

.dt-skeleton {
  display: block;
  border-radius: var(--dt-radius-md);
  background-color: var(--dt-surface-sunken);
  background-image: linear-gradient(90deg, transparent 0%, var(--dt-border) 50%, transparent 100%);
  background-size: 50% 100%;
  background-repeat: no-repeat;
  animation: dt-skeleton-sweep 1.6s var(--dt-ease-standard) infinite;
}

.dt-skeleton--text {
  height: var(--dt-leading-sm);
  margin-block: var(--dt-space-1);
}

/* The last of several lines is short, as the last line of a paragraph is. */
.dt-skeleton--text:last-child:not(:first-child) {
  width: 60%;
}

.dt-skeleton--title {
  width: 40%;
  height: var(--dt-leading-2xl);
  margin-block: var(--dt-space-1) var(--dt-space-3);
}

.dt-skeleton--block {
  height: var(--dt-space-24);
}

.dt-skeleton--circle {
  width: var(--dt-space-10);
  height: var(--dt-space-10);
  border-radius: var(--dt-radius-full);
}

@keyframes dt-skeleton-sweep {
  from {
    background-position: -50% 0;
  }

  to {
    background-position: 150% 0;
  }
}

/* With reduced motion asked for, the placeholders stand still. */
@media (prefers-reduced-motion: reduce) {
  .dt-skeleton {
    animation: none;
    background-image: none;
  }
}

/* Forced colours drop backgrounds, so a placeholder is drawn by its outline instead. */
@media (forced-colors: active) {
  .dt-skeleton {
    border: 1px dashed GrayText;
  }
}

/*
 * Live status — whether a view is live, stale or disconnected, and when its data was last
 * updated, for a page fed by a push channel (or any other transport: the marker never sees it).
 *
 * `.dt-live-status` says its state in a word (`.dt-live-status__state`), and its marker draws the
 * state as a shape as well as a colour: a filled dot for live, a ring for stale, a struck ring
 * for disconnected. The age follows ("Updated 12 s ago"), in a `<time>` whose `datetime` is the
 * moment itself. It is a `<span>`, so it sits in a heading, a paragraph or a card's header.
 *
 * The behaviour script (`behaviour/live.js`) keeps it true: the page sets `data-dt-updated` and
 * `data-dt-connected`, or fires `dt:update` and `dt:connection` on the marker, and the script
 * works out the state on a timer of its own, writes `data-dt-state`, the word and the age, and
 * says a change of state in the polite status. `@deeptrack/react`'s `LiveStatus` does the same.
 *
 * Markup:
 *   <span class="dt-live-status" data-dt-live-status data-dt-state="live"
 *        data-dt-updated="2026-10-02T10:42:05Z" data-dt-stale-after="60" data-dt-label="Telemetry">
 *     <span class="dt-live-status__marker" aria-hidden="true"></span>
 *     <span class="dt-visually-hidden">Telemetry:</span>
 *     <span class="dt-live-status__state">Live</span>
 *     <span class="dt-live-status__age">Updated <time datetime="2026-10-02T10:42:05Z">just now</time></span>
 *     <span class="dt-live-status__status" role="status"></span>
 *   </span>
 */

.dt-live-status {
  position: relative;
  display: inline-flex;
  flex-wrap: wrap;
  align-items: center;
  column-gap: var(--dt-space-2);
  font-family: var(--dt-font-sans);
  font-size: var(--dt-text-sm);
  line-height: var(--dt-leading-sm);
  color: var(--dt-text-muted);
}

.dt-live-status__marker {
  position: relative;
  flex: none;
  width: var(--dt-space-2);
  height: var(--dt-space-2);
  border: 2px solid var(--dt-success-solid);
  border-radius: var(--dt-radius-full);
  background-color: var(--dt-success-solid);
}

.dt-live-status__state {
  font-weight: var(--dt-weight-medium);
  color: var(--dt-success-text);
}

.dt-live-status__age {
  font-variant-numeric: tabular-nums;
}

/* A dot between the word and the age; it is not read. */
.dt-live-status__age::before {
  content: '·' / '';
  margin-inline-end: var(--dt-space-2);
}

/* The announcement is for a screen reader: the word and the age already show it. */
.dt-live-status__status {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: 0;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* --- Stale: a ring --------------------------------------------------------------------------- */

.dt-live-status[data-dt-state='stale'] .dt-live-status__marker {
  border-color: var(--dt-warning-solid);
  background-color: transparent;
}

.dt-live-status[data-dt-state='stale'] .dt-live-status__state {
  color: var(--dt-warning-text);
}

/* --- Disconnected: a struck ring ------------------------------------------------------------- */

.dt-live-status[data-dt-state='disconnected'] .dt-live-status__marker {
  border-color: var(--dt-error-solid);
  background-color: transparent;
}

.dt-live-status[data-dt-state='disconnected'] .dt-live-status__marker::after {
  content: '';
  position: absolute;
  inset-block-start: 50%;
  inset-inline-start: 50%;
  width: calc(var(--dt-space-2) + 4px);
  height: 2px;
  background-color: var(--dt-error-solid);
  transform: translate(-50%, -50%) rotate(-45deg);
}

.dt-live-status[data-dt-state='disconnected'] .dt-live-status__state {
  color: var(--dt-error-text);
}

/* Forced colours drop the fills: the shapes stay, drawn in the text colour. */
@media (forced-colors: active) {
  .dt-live-status__marker {
    border-color: CanvasText;
    background-color: CanvasText;
  }

  .dt-live-status[data-dt-state] .dt-live-status__marker {
    border-color: CanvasText;
  }

  .dt-live-status[data-dt-state='disconnected'] .dt-live-status__marker::after {
    background-color: CanvasText;
  }
}

/*
 * `.dt-nav` — a set of navigation links, horizontal (a top bar) or vertical (a sidebar). The
 * current page is `aria-current="page"` and is marked by weight and a bar as well as colour,
 * so the reader who cannot see the colour still finds where they are.
 *
 * Markup:
 *   <nav class="dt-nav" aria-label="Main">
 *     <ul class="dt-nav__list">
 *       <li><a class="dt-nav__link" href="/" aria-current="page">Overview</a></li>
 *     </ul>
 *   </nav>
 */

.dt-nav {
  font-family: var(--dt-font-sans);
}

.dt-nav__list {
  display: flex;
  gap: var(--dt-space-1);
  margin: 0;
  padding: 0;
  list-style: none;
}

.dt-nav--vertical .dt-nav__list {
  flex-direction: column;
}

.dt-nav__link {
  display: block;
  padding: var(--dt-space-2) var(--dt-space-3);
  border-radius: var(--dt-radius-md);
  font-size: var(--dt-text-sm);
  line-height: var(--dt-leading-sm);
  font-weight: var(--dt-weight-medium);
  color: var(--dt-text-muted);
  text-decoration: none;
  transition:
    background-color var(--dt-duration-fast) var(--dt-ease-standard),
    color var(--dt-duration-fast) var(--dt-ease-standard);
}

.dt-nav__link:hover {
  color: var(--dt-text);
  background-color: var(--dt-surface-sunken);
}

.dt-nav__link[aria-current='page'] {
  color: var(--dt-text-blue);
  font-weight: var(--dt-weight-semibold);
  box-shadow: inset 0 calc(var(--dt-space-0_5) * -1) 0 var(--dt-text-blue);
}

.dt-nav--vertical .dt-nav__link[aria-current='page'] {
  box-shadow: inset var(--dt-space-0_5) 0 0 var(--dt-text-blue);
}

.dt-nav__link:focus-visible {
  outline: var(--dt-focus-width) solid var(--dt-focus-ring);
  outline-offset: var(--dt-focus-offset);
}

/* Motion is decoration here: with reduced motion asked for, state changes are instant. */
@media (prefers-reduced-motion: reduce) {
  .dt-nav__link {
    transition: none;
  }
}

/*
 * `.dt-breadcrumbs` — where the page sits: Plant › TBOX › Device (WAI-ARIA APG "Breadcrumb").
 * A `<nav>` named "Breadcrumb" holding an ordered list, every ancestor a link and the page itself
 * last, as text marked `aria-current="page"`: a link to the page being read leads nowhere.
 *
 * The separators are drawn by CSS and given empty alternative text, so a screen reader reads the
 * list as a list, not "greater than" between each pair. On a phone the trail wraps rather than
 * hiding a level, and a long serial breaks anywhere rather than widening the page.
 *
 * Markup:
 *   <nav class="dt-breadcrumbs" aria-label="Breadcrumb">
 *     <ol class="dt-breadcrumbs__list">
 *       <li class="dt-breadcrumbs__item"><a class="dt-breadcrumbs__link" href="/plants/7">Lerma</a></li>
 *       <li class="dt-breadcrumbs__item"><a class="dt-breadcrumbs__link" href="/tboxes/4">TBOX 04</a></li>
 *       <li class="dt-breadcrumbs__item"><span class="dt-breadcrumbs__current" aria-current="page">DBOX 0412</span></li>
 *     </ol>
 *   </nav>
 */

.dt-breadcrumbs {
  font-family: var(--dt-font-sans);
  font-size: var(--dt-text-sm);
  line-height: var(--dt-leading-sm);
}

.dt-breadcrumbs__list {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--dt-space-1) var(--dt-space-2);
  margin: 0;
  padding: 0;
  list-style: none;
}

.dt-breadcrumbs__item {
  display: inline-flex;
  align-items: baseline;
  gap: var(--dt-space-2);
  min-width: 0;
  overflow-wrap: anywhere;
}

.dt-breadcrumbs__item + .dt-breadcrumbs__item::before {
  /* The first declaration is for a browser that cannot read the alternative text after `/`. */
  content: '›';
  content: '›' / '';
  color: var(--dt-text-muted);
}

.dt-breadcrumbs__link {
  color: var(--dt-text-blue);
  text-decoration: underline;
  text-underline-offset: 0.15em;
}

.dt-breadcrumbs__link:hover {
  text-decoration-thickness: 0.125em;
}

.dt-breadcrumbs__link:focus-visible {
  outline: var(--dt-focus-width) solid var(--dt-focus-ring);
  outline-offset: var(--dt-focus-offset);
  border-radius: var(--dt-radius-sm);
}

/* The page itself: by weight as well as colour, as `.dt-nav` marks its current link. */
.dt-breadcrumbs__current {
  color: var(--dt-text);
  font-weight: var(--dt-weight-semibold);
}

/*
 * `.dt-page-header` — the top of a page: where it sits (`.dt-breadcrumbs`), what it is (the
 * title, the page's one `<h1>`), its context in a line of words (the TBOX it belongs to, a
 * serial, when it last reported), and what can be done to it (the actions).
 *
 * When the header is narrower than 48rem, the one breakpoint (layout.css), the actions fold into
 * one menu button, `.dt-page-header__more`: the same actions again, as `.dt-menu` items. The
 * header's own width decides, not the viewport's, so a header beside an open sidebar on a tablet
 * folds as a phone's does. They fold only once the menu is wired, which the behaviour script (and
 * `@deeptrack/react`'s `Dropdown`) marks with `data-dt-ready`; without the script a narrow header
 * shows the buttons, wrapped, so no action is ever out of reach. The two sets are never shown
 * together, and the hidden one is `display: none`, so assistive technology meets each action
 * once. Both carry the same `href`, `form` or `data-dt-dialog-open`, so either one does the same.
 *
 * Inside the app frame it is the first child of `<main>`, where `<header>` is not a landmark. It
 * is a size container, so it takes its width from its parent and none from its content: put it
 * in a block, not in a flex row or an inline-block, where it would shrink to nothing.
 *
 * Markup:
 *   <header class="dt-page-header">
 *     <nav class="dt-breadcrumbs" aria-label="Breadcrumb">…</nav>
 *     <div class="dt-page-header__main">
 *       <div class="dt-page-header__heading">
 *         <h1 class="dt-page-header__title">DBOX 0412</h1>
 *         <p class="dt-page-header__context">Row 12, TBOX 04 · Firmware 3.2.1</p>
 *       </div>
 *       <div class="dt-page-header__actions">
 *         <a class="dt-button dt-button--secondary" href="/dboxes/412/edit">Edit</a>
 *         <button class="dt-button dt-button--primary" type="button" data-dt-dialog-open="stow">Stow</button>
 *       </div>
 *       <div class="dt-page-header__more dt-menu-anchor" data-dt-menu>
 *         <button class="dt-button dt-button--secondary" type="button" aria-haspopup="menu"
 *                 aria-expanded="false">Actions<span aria-hidden="true">▾</span></button>
 *         <ul class="dt-menu dt-menu--end" role="menu" hidden>
 *           <li role="none"><a class="dt-menu__item" role="menuitem" href="/dboxes/412/edit">Edit</a></li>
 *           <li role="none"><button class="dt-menu__item" role="menuitem" type="button"
 *                                   data-dt-dialog-open="stow">Stow</button></li>
 *         </ul>
 *       </div>
 *     </div>
 *   </header>
 */

.dt-page-header {
  container-type: inline-size;
  display: flex;
  flex-direction: column;
  gap: var(--dt-space-2);
  margin-block-end: var(--dt-space-6);
  font-family: var(--dt-font-sans);
}

.dt-page-header__main {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--dt-space-3) var(--dt-space-4);
}

.dt-page-header__heading {
  display: flex;
  flex-direction: column;
  gap: var(--dt-space-1);
  flex: 1 1 20rem;
  min-width: 0;
}

.dt-page-header__title {
  margin: 0;
  font-size: var(--dt-text-2xl);
  line-height: var(--dt-leading-2xl);
  font-weight: var(--dt-weight-semibold);
  color: var(--dt-text);
  overflow-wrap: anywhere;
}

.dt-page-header__context {
  margin: 0;
  font-size: var(--dt-text-sm);
  line-height: var(--dt-leading-sm);
  color: var(--dt-text-muted);
}

.dt-page-header__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--dt-space-2);
}

.dt-page-header__more {
  display: none;
}

@container (width >= 48rem) {
  .dt-page-header__title {
    font-size: var(--dt-text-3xl);
    line-height: var(--dt-leading-3xl);
  }
}

/* Narrower than the one breakpoint, a wired menu takes the actions' place. */
@container (width < 48rem) {
  /* At the end, even on a line of its own, so its menu (`--end`) opens inward. */
  .dt-page-header__more[data-dt-ready] {
    display: inline-block;
    margin-inline-start: auto;
  }

  .dt-page-header__main:has(> .dt-page-header__more[data-dt-ready]) > .dt-page-header__actions {
    display: none;
  }
}

/*
 * `.dt-search` — global search, in the app frame's bar: a box that jumps to an object by its name
 * or serial. It is an ordinary `GET` form, so with no script it goes to the app's results page.
 * The behaviour script (`behaviour/search.js`) or `@deeptrack/react`'s `GlobalSearch` turns the
 * box into a combobox with a list of the objects the app's endpoint suggests, each its label, its
 * kind and a detail (a serial, a place), and an "All results" entry last. The entry the arrows
 * are on is a fill and a ring, so it is not colour alone.
 *
 * On a phone the box folds to its button, `.dt-search__open`, once the script is running
 * (`data-dt-ready`); pressed, the box opens across the bar (`data-dt-search-open`). Without the
 * script the box stays, narrower.
 *
 * Markup:
 *   <form class="dt-search" role="search" action="/search/" method="get" data-dt-search
 *         data-dt-search-url="/search/suggest/">
 *     <button class="dt-search__open dt-button dt-button--ghost dt-button--icon" type="button"
 *             aria-expanded="false" aria-label="Search">…icon…</button>
 *     <div class="dt-search__field">
 *       <label class="dt-visually-hidden" for="dt-search">Search</label>
 *       <svg class="dt-icon dt-search__icon" aria-hidden="true">…</svg>
 *       <input class="dt-search__input" type="search" id="dt-search" name="q"
 *              placeholder="Name or serial" aria-keyshortcuts="Control+K Meta+K">
 *       <kbd class="dt-search__hint" aria-hidden="true">Ctrl K</kbd>
 *     </div>
 *   </form>
 *
 * Drawn by the script:
 *   <div class="dt-search__popup">
 *     <ul class="dt-search__listbox" role="listbox">
 *       <li class="dt-search__option dt-search__option--active" role="option">
 *         <span class="dt-search__label">TBOX 04</span>
 *         <span class="dt-search__kind">TBOX</span>
 *         <span class="dt-search__detail">SN 2401-0042 · Lerma</span></li>
 *       <li class="dt-search__option dt-search__option--all" role="option">All results for “tbox”</li>
 *     </ul>
 *     <p class="dt-search__message">No results</p>
 *   </div>
 *   <span class="dt-search__status" role="status"></span>
 */

.dt-search {
  --dtc-search-width: 24rem;
  position: relative;
  flex: 0 1 var(--dtc-search-width);
  min-width: 0;
  font-family: var(--dt-font-sans);
}

.dt-search__field {
  display: flex;
  align-items: center;
  gap: var(--dt-space-2);
  padding-inline: var(--dt-space-3) var(--dt-space-2);
  background-color: var(--dt-surface-sunken);
  border: 1px solid var(--dt-border);
  border-radius: var(--dt-radius-md);
  color: var(--dt-text-muted);
}

.dt-search__field:has(.dt-search__input:focus-visible) {
  background-color: var(--dt-surface);
  border-color: var(--dt-border-strong);
  outline: var(--dt-focus-width) solid var(--dt-focus-ring);
  outline-offset: var(--dt-focus-offset);
}

.dt-search__icon {
  flex: none;
}

.dt-search__input {
  flex: 1;
  min-width: 0;
  margin: 0;
  padding: var(--dt-space-2) 0;
  font-family: inherit;
  font-size: var(--dt-text-sm);
  line-height: var(--dt-leading-sm);
  color: var(--dt-text);
  background: transparent;
  border: 0;
  appearance: none;
}

.dt-search__input::placeholder {
  color: var(--dt-text-muted);
  opacity: 1;
}

.dt-search__input:focus-visible {
  outline: none;
  /* base.css's halo too: the box around it carries the ring. */
  box-shadow: none;
}

.dt-search__hint {
  flex: none;
  padding: 0 var(--dt-space-1_5);
  font-family: var(--dt-font-sans);
  font-size: var(--dt-text-xs);
  line-height: var(--dt-leading-xs);
  color: var(--dt-text-muted);
  background-color: var(--dt-surface);
  border: 1px solid var(--dt-border);
  border-radius: var(--dt-radius-sm);
  white-space: nowrap;
}

/* The shortcut is the script's: without it, no hint. */
.dt-search:not([data-dt-ready]) .dt-search__hint,
.dt-search__field:has(.dt-search__input:focus) .dt-search__hint {
  visibility: hidden;
}

.dt-search__open {
  display: none;
}

.dt-search__popup {
  position: absolute;
  top: calc(100% + var(--dt-space-1));
  /* Laid from the box's end, so a list wider than a narrow box grows into the page, not off it. */
  inset-inline: auto 0;
  z-index: var(--dt-z-dropdown);
  box-sizing: border-box;
  width: max(100%, min(20rem, calc(100vw - var(--dt-space-4))));
  padding: var(--dt-space-1);
  background-color: var(--dt-surface);
  border: 1px solid var(--dt-border);
  border-radius: var(--dt-radius-lg);
  box-shadow: var(--dt-elevation-lg);
}

/* The inline variant lays the list in the flow of a page — a documentation specimen. */
.dt-search--inline .dt-search__popup {
  position: static;
  margin-block-start: var(--dt-space-1);
}

.dt-search__listbox {
  max-height: 22rem;
  margin: 0;
  padding: 0;
  overflow-y: auto;
  list-style: none;
}

.dt-search__option {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  grid-template-areas:
    'label kind'
    'detail detail';
  column-gap: var(--dt-space-3);
  padding: var(--dt-space-2) var(--dt-space-3);
  font-size: var(--dt-text-sm);
  line-height: var(--dt-leading-sm);
  color: var(--dt-text);
  border-radius: var(--dt-radius-md);
  cursor: pointer;
}

.dt-search__option:hover {
  background-color: var(--dt-surface-sunken);
}

/* The entry the arrows are on: a fill and an inset ring, so it is not colour alone. */
.dt-search__option--active {
  background-color: var(--dt-surface-sunken);
  box-shadow: inset 0 0 0 var(--dt-focus-width) var(--dt-focus-ring);
}

.dt-search__label {
  grid-area: label;
  overflow: hidden;
  font-weight: var(--dt-weight-medium);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.dt-search__kind {
  grid-area: kind;
  align-self: center;
  padding: 0 var(--dt-space-1_5);
  font-size: var(--dt-text-xs);
  line-height: var(--dt-leading-xs);
  font-weight: var(--dt-weight-medium);
  color: var(--dt-text-muted);
  border: 1px solid var(--dt-border);
  border-radius: var(--dt-radius-full);
}

.dt-search__detail {
  grid-area: detail;
  overflow: hidden;
  font-size: var(--dt-text-xs);
  line-height: var(--dt-leading-xs);
  color: var(--dt-text-muted);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.dt-search__option--all {
  display: block;
  margin-block-start: var(--dt-space-1);
  color: var(--dt-text-blue);
  box-shadow: inset 0 1px 0 var(--dt-border);
}

.dt-search__option--all.dt-search__option--active {
  box-shadow: inset 0 0 0 var(--dt-focus-width) var(--dt-focus-ring);
}

.dt-search__message {
  margin: 0;
  padding: var(--dt-space-2) var(--dt-space-3);
  font-size: var(--dt-text-sm);
  line-height: var(--dt-leading-sm);
  color: var(--dt-text-muted);
}

/* Said, not shown: the list already shows the count. */
.dt-search__status {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* A phone, once the script is running: the box folds to its button, and opens across the bar. */
@media (width < 48rem) {
  .dt-search[data-dt-ready] {
    flex: none;
  }

  .dt-search[data-dt-ready] .dt-search__open {
    display: inline-flex;
  }

  .dt-search[data-dt-ready]:not([data-dt-search-open]) .dt-search__field {
    display: none;
  }

  .dt-search__hint {
    display: none;
  }

  /* Open, the box and its list are laid against the bar, which is the positioned ancestor. */
  .dt-search[data-dt-search-open] {
    position: static;
  }

  .dt-search[data-dt-search-open] .dt-search__field {
    position: absolute;
    inset-inline: var(--dt-space-2);
    top: 50%;
    z-index: var(--dt-z-raised);
    background-color: var(--dt-surface);
    transform: translateY(-50%);
  }

  .dt-search[data-dt-search-open] .dt-search__popup {
    top: calc(100% + var(--dt-space-1));
    inset-inline: var(--dt-space-2);
    width: auto;
  }
}

@media print {
  .dt-search {
    display: none;
  }
}

/*
 * `.dt-context` — the context switcher, in the app frame's bar: which plant (or site, or fleet)
 * the app is showing, in words — "Plant · Lerma" — and a menu to choose another. It is a menu
 * button (`components/menu.css`, `behaviour/menu.js`, React's `Dropdown`): each choice is a link,
 * or a submit button of a `POST` form the app's view answers by storing the choice. The current
 * choice is `aria-current="true"`, ticked and in weight. The last item leads to the full list.
 *
 * A menu needs its script, so until the menu is wired (`data-dt-ready`) the trigger is hidden and
 * `.dt-context__fallback`, a link to the full list saying the same words, takes its place.
 *
 * Which pages and navigation follow the choice is the app's: its views build the sidebar's
 * sections for the current plant (`{% sidenav %}`), and its URLs carry it.
 *
 * Markup:
 *   <div class="dt-context">
 *     <a class="dt-context__fallback" href="/plants/">
 *       <span class="dt-context__kind">Plant</span> <span class="dt-context__current">Lerma</span></a>
 *     <div class="dt-menu-anchor" data-dt-menu>
 *       <button class="dt-context__trigger dt-button dt-button--ghost" type="button"
 *               aria-haspopup="menu" aria-expanded="false">
 *         <span class="dt-context__kind">Plant</span>
 *         <span class="dt-context__current">Lerma</span>
 *         <span class="dt-context__chevron" aria-hidden="true"></span>
 *       </button>
 *       <ul class="dt-menu dt-context__menu" role="menu" aria-label="Choose the plant" hidden>
 *         <li role="none"><a class="dt-menu__item" role="menuitem" href="/plants/7/"
 *             aria-current="true">Lerma <span class="dt-context__detail">Burgos</span></a></li>
 *         <li role="none"><a class="dt-menu__item dt-context__all" role="menuitem"
 *             href="/plants/">All plants</a></li>
 *       </ul>
 *     </div>
 *   </div>
 */

.dt-context {
  display: flex;
  align-items: center;
  min-width: 0;
  font-family: var(--dt-font-sans);
}

.dt-context .dt-menu-anchor {
  min-width: 0;
}

.dt-context__trigger,
.dt-context__fallback {
  display: inline-flex;
  align-items: baseline;
  gap: var(--dt-space-2);
  max-width: 100%;
  min-width: 0;
}

.dt-context__fallback {
  padding: var(--dt-space-2) var(--dt-space-3);
  border-radius: var(--dt-radius-md);
  color: var(--dt-text);
  text-decoration: none;
}

.dt-context__fallback:hover {
  background-color: var(--dt-surface-sunken);
}

.dt-context__fallback:focus-visible {
  outline: var(--dt-focus-width) solid var(--dt-focus-ring);
  outline-offset: var(--dt-focus-offset);
}

/* One or the other: the menu once it works, the link until then. */
.dt-context:has(.dt-menu-anchor[data-dt-ready]) .dt-context__fallback,
.dt-context:not(:has(.dt-menu-anchor[data-dt-ready])) .dt-menu-anchor {
  display: none;
}

/* The kind is a label in muted words; the current choice is what is read. */
.dt-context__kind {
  flex: none;
  font-size: var(--dt-text-xs);
  line-height: var(--dt-leading-xs);
  font-weight: var(--dt-weight-semibold);
  letter-spacing: var(--dt-tracking-wide);
  text-transform: uppercase;
  color: var(--dt-text-muted);
}

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

/* The chevron, drawn in the text colour, so it needs no icon set. */
.dt-context__chevron {
  flex: none;
  align-self: center;
  width: var(--dt-space-1_5);
  height: var(--dt-space-1_5);
  margin-block-end: var(--dt-space-0_5);
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: rotate(45deg);
}

.dt-context__menu {
  min-width: 14rem;
  max-height: min(24rem, 70vh);
  overflow-y: auto;
}

.dt-context__detail {
  margin-inline-start: auto;
  font-size: var(--dt-text-xs);
  line-height: var(--dt-leading-xs);
  font-weight: var(--dt-weight-regular);
  color: var(--dt-text-muted);
}

.dt-context__menu .dt-menu__item {
  display: flex;
  align-items: baseline;
  gap: var(--dt-space-3);
}

/* Every choice keeps the tick's room, so the details line up whichever is current. */
.dt-context__menu .dt-menu__item::after {
  content: '';
  flex: none;
  width: var(--dt-space-1_5);
  margin-inline: 0 var(--dt-space-1);
}

.dt-context__menu .dt-menu__item[aria-current='true']::after {
  margin-inline: 0 var(--dt-space-1);
}

.dt-context__all::after {
  display: none;
}

/* The way to the full list, set off from the choices. */
.dt-context__all {
  margin-block-start: var(--dt-space-1);
  color: var(--dt-text-blue);
  box-shadow: inset 0 1px 0 var(--dt-border);
}

/* A phone's bar is narrow: the kind is said but not shown, and the choice is cut short if need
   be. The menu spans the screen under the bar, so it never runs off its edge. */
@media (width < 48rem) {
  .dt-context .dt-context__kind {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }

  .dt-context__current {
    max-width: 28vw;
  }

  .dt-context__menu {
    position: fixed;
    top: calc(var(--dtc-app-bar-height, var(--dt-space-14)) + var(--dt-space-1));
    inset-inline: var(--dt-space-2);
    min-width: 0;
  }
}

/*
 * Notifications — `.dt-notifications`, the bell in the app frame's bar with its panel and the
 * pop-ups for new items, and `.dt-notice`, one item, in the panel, a pop-up or the inbox page
 * (`.dt-notices`, a list of them).
 *
 * An item's severity — `info`, `warning`, `critical` — is a word (`.dt-notice__severity`) and a
 * shape beside it: a ring, a triangle, a diamond. Its state is a word too ("Unread", "Read",
 * "Not acknowledged", "Acknowledged"), and an unread item is drawn in weight with a bar, so
 * neither is colour alone. A critical item is acknowledged rather than read.
 *
 * The behaviour script (`behaviour/notifications.js`) or `@deeptrack/react`'s `Notifications`
 * opens the panel, sends Mark as read and Acknowledge in the background, and pops up what the
 * page's push channel brings (`dt:notification`). Until the script runs (`data-dt-ready`) the
 * bell is a link to the inbox (`.dt-notifications__link`), and its forms post as forms do.
 *
 * Markup:
 *   <div class="dt-notifications" data-dt-notifications data-dt-unread="3"
 *        data-dt-time-zone="Europe/Madrid">
 *     <a class="dt-notifications__link dt-button dt-button--ghost dt-button--icon"
 *        href="/notifications/" aria-label="Notifications, 3 unread">…icon…
 *       <span class="dt-notifications__count" aria-hidden="true">3</span></a>
 *     <button class="dt-notifications__bell dt-button dt-button--ghost dt-button--icon"
 *             type="button" aria-expanded="false" aria-controls="…-panel"
 *             aria-label="Notifications, 3 unread">…icon…
 *       <span class="dt-notifications__count" aria-hidden="true">3</span></button>
 *     <section class="dt-notifications__panel" id="…-panel" aria-labelledby="…-title" hidden>
 *       <div class="dt-notifications__header">
 *         <h2 class="dt-notifications__title" id="…-title">Notifications</h2>
 *         <form method="post" action="/notifications/read/" data-dt-notice-action="read-all">…</form>
 *       </div>
 *       <ul class="dt-notifications__list dt-notices">…items…</ul>
 *       <p class="dt-notifications__empty" hidden>No notifications</p>
 *       <a class="dt-notifications__inbox" href="/notifications/">Open the inbox</a>
 *     </section>
 *     <div class="dt-notifications__toasts">
 *       <div class="dt-notifications__toasts--urgent" aria-live="assertive">…critical pop-ups…</div>
 *       <div class="dt-notifications__toasts--polite" aria-live="polite">…pop-ups…</div>
 *     </div>
 *     <span class="dt-notifications__status" role="status"></span>
 *     <template class="dt-notifications__template">…one item…</template>
 *   </div>
 *
 *   <li class="dt-notice dt-notice--critical" data-dt-notice="42" data-dt-severity="critical"
 *       data-dt-read="false" data-dt-acknowledged="false">
 *     <span class="dt-notice__severity">Critical</span>
 *     <a class="dt-notice__title" href="/alarms/42/">Wind stow on Lerma</a>
 *     <p class="dt-notice__body">TBOX 04 stowed at 18 m/s.</p>
 *     <p class="dt-notice__meta"><time class="dt-notice__time" datetime="…">5 Oct, 12:04</time>
 *       · <span class="dt-notice__state">Not acknowledged</span></p>
 *     <div class="dt-notice__actions">
 *       <form method="post" action="/notifications/42/acknowledge/"
 *             data-dt-notice-action="acknowledge">…<button class="dt-button dt-button--primary
 *             dt-button--sm">Acknowledge</button></form>
 *     </div>
 *   </li>
 */

.dt-notifications {
  --dtc-notifications-width: 24rem;
  position: relative;
  font-family: var(--dt-font-sans);
}

/* One or the other: the bell once the script works, the inbox link until then. */
.dt-notifications:not([data-dt-ready]) .dt-notifications__bell,
.dt-notifications[data-dt-ready] .dt-notifications__link {
  display: none;
}

.dt-notifications__bell,
.dt-notifications__link {
  position: relative;
}

/* The count, on the bell's corner: its words are the bell's name. */
.dt-notifications__count {
  position: absolute;
  top: 0;
  right: 0;
  min-width: var(--dt-space-4);
  padding-inline: var(--dt-space-1);
  box-sizing: border-box;
  font-size: var(--dt-text-2xs);
  line-height: var(--dt-space-4);
  font-weight: var(--dt-weight-bold);
  text-align: center;
  color: var(--dt-error-on-solid);
  background-color: var(--dt-error-solid);
  border-radius: var(--dt-radius-full);
  box-shadow: 0 0 0 2px var(--dt-surface);
}

.dt-notifications__count[hidden] {
  display: none;
}

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

.dt-notifications__panel {
  position: absolute;
  top: calc(100% + var(--dt-space-2));
  right: 0;
  z-index: var(--dt-z-dropdown);
  box-sizing: border-box;
  width: min(var(--dtc-notifications-width), calc(100vw - var(--dt-space-4)));
  max-height: min(32rem, 75dvh);
  overflow-y: auto;
  overscroll-behavior: contain;
  background-color: var(--dt-surface);
  color: var(--dt-text);
  border: 1px solid var(--dt-border);
  border-radius: var(--dt-radius-lg);
  box-shadow: var(--dt-elevation-lg);
}

/* The inline variant lays the panel in the flow of a page — a documentation specimen. */
.dt-notifications--inline .dt-notifications__panel {
  position: static;
  width: auto;
}

.dt-notifications__header {
  position: sticky;
  top: 0;
  z-index: var(--dt-z-raised);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--dt-space-3);
  padding: var(--dt-space-3) var(--dt-space-4);
  background-color: var(--dt-surface);
  border-bottom: 1px solid var(--dt-border);
}

.dt-notifications__title {
  margin: 0;
  font-size: var(--dt-text-base);
  line-height: var(--dt-leading-base);
  font-weight: var(--dt-weight-semibold);
}

.dt-notifications__header form {
  margin: 0;
}

.dt-notifications__list {
  margin: 0;
  padding: 0;
  list-style: none;
}

.dt-notifications__list > .dt-notice + .dt-notice {
  border-top: 1px solid var(--dt-border);
}

.dt-notifications__empty {
  margin: 0;
  padding: var(--dt-space-6) var(--dt-space-4);
  font-size: var(--dt-text-sm);
  line-height: var(--dt-leading-sm);
  color: var(--dt-text-muted);
  text-align: center;
}

.dt-notifications__inbox {
  display: block;
  padding: var(--dt-space-3) var(--dt-space-4);
  font-size: var(--dt-text-sm);
  line-height: var(--dt-leading-sm);
  font-weight: var(--dt-weight-medium);
  color: var(--dt-text-blue);
  text-align: center;
  border-top: 1px solid var(--dt-border);
}

.dt-notifications__inbox:focus-visible {
  outline: var(--dt-focus-width) solid var(--dt-focus-ring);
  outline-offset: calc(var(--dt-focus-width) * -1);
}

/* Said, not shown. */
.dt-notifications__status {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* --- The pop-ups ----------------------------------------------------------------------------- */

.dt-notifications__toasts {
  position: fixed;
  top: calc(var(--dtc-app-bar-height, var(--dt-space-14)) + var(--dt-space-2));
  right: var(--dt-space-4);
  z-index: var(--dt-z-toast);
  display: flex;
  flex-direction: column;
  gap: var(--dt-space-2);
  width: min(var(--dtc-notifications-width), calc(100vw - var(--dt-space-8)));
  /* Several critical pop-ups wait to be acknowledged: they scroll rather than cover the page. */
  max-height: 50dvh;
  overflow-y: auto;
  overscroll-behavior: contain;
  pointer-events: none;
}

.dt-notifications__toasts:not(:has(.dt-notice)) {
  display: none;
}

/* The open panel lists the same items with their actions: the pop-ups would cover it. */
.dt-notifications:has(> .dt-notifications__panel:not([hidden])) .dt-notifications__toasts {
  display: none;
}

.dt-notifications__toasts--urgent,
.dt-notifications__toasts--polite {
  display: flex;
  flex-direction: column;
  gap: var(--dt-space-2);
}

/* The inline variant lays the pop-ups in the flow of a page — a documentation specimen. */
.dt-notifications--inline .dt-notifications__toasts {
  position: static;
  width: auto;
}

.dt-notice--toast {
  pointer-events: auto;
  background-color: var(--dt-surface);
  border: 1px solid var(--dt-border);
  border-radius: var(--dt-radius-lg);
  box-shadow: var(--dt-elevation-lg);
}

/* A critical pop-up is set off by its tone's border, as well as its word and shape. */
.dt-notice--toast.dt-notice--critical {
  border-color: var(--dt-error-border);
  border-inline-start-width: var(--dt-space-1);
}

.dt-notice--toast.dt-notice--warning {
  border-inline-start: var(--dt-space-1) solid var(--dt-warning-border);
}

.dt-notice--toast.dt-notice--info {
  border-inline-start: var(--dt-space-1) solid var(--dt-info-border);
}

/* --- One item -------------------------------------------------------------------------------- */

/* An inbox page's list, wired by the script: its status is placed against it. */
[data-dt-notices] {
  position: relative;
}

.dt-notices {
  margin: 0;
  padding: 0;
  list-style: none;
}

.dt-notices > .dt-notice + .dt-notice {
  border-top: 1px solid var(--dt-border);
}

.dt-notice {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  grid-template-areas:
    'severity dismiss'
    'title title'
    'body body'
    'meta meta'
    'actions actions';
  row-gap: var(--dt-space-1);
  padding: var(--dt-space-3) var(--dt-space-4);
  font-size: var(--dt-text-sm);
  line-height: var(--dt-leading-sm);
  color: var(--dt-text);
}

/* Unread, or not acknowledged: a bar at its start and its title in weight. */
.dt-notice[data-dt-read='false'],
.dt-notice--critical[data-dt-acknowledged='false'] {
  box-shadow: inset var(--dt-space-0_5) 0 0 var(--dt-text-blue);
}

.dt-notice--critical[data-dt-acknowledged='true'] {
  box-shadow: none;
}

.dt-notice[data-dt-read='false'] .dt-notice__title,
.dt-notice--critical[data-dt-acknowledged='false'] .dt-notice__title {
  font-weight: var(--dt-weight-semibold);
}

.dt-notice__severity {
  grid-area: severity;
  display: inline-flex;
  align-items: center;
  gap: var(--dt-space-1_5);
  font-size: var(--dt-text-xs);
  line-height: var(--dt-leading-xs);
  font-weight: var(--dt-weight-semibold);
  letter-spacing: var(--dt-tracking-wide);
  text-transform: uppercase;
}

/* The severity's shape, beside its word: a ring, a triangle, a diamond. */
.dt-notice__severity::before {
  content: '';
  flex: none;
  width: var(--dt-space-2_5);
  height: var(--dt-space-2_5);
  box-sizing: border-box;
}

.dt-notice--info .dt-notice__severity {
  color: var(--dt-info-text);
}

.dt-notice--info .dt-notice__severity::before {
  border: 2px solid currentColor;
  border-radius: var(--dt-radius-full);
}

.dt-notice--warning .dt-notice__severity {
  color: var(--dt-warning-text);
}

.dt-notice--warning .dt-notice__severity::before {
  background-color: currentColor;
  clip-path: polygon(50% 0, 100% 100%, 0 100%);
}

.dt-notice--critical .dt-notice__severity {
  color: var(--dt-error-text);
}

.dt-notice--critical .dt-notice__severity::before {
  background-color: currentColor;
  transform: rotate(45deg) scale(0.8);
}

.dt-notice__title {
  grid-area: title;
  font-weight: var(--dt-weight-medium);
  color: var(--dt-text);
}

a.dt-notice__title {
  text-decoration: underline;
  text-decoration-color: var(--dt-border-strong);
  text-underline-offset: 0.15em;
}

a.dt-notice__title:hover {
  text-decoration-color: currentColor;
}

a.dt-notice__title:focus-visible {
  outline: var(--dt-focus-width) solid var(--dt-focus-ring);
  outline-offset: var(--dt-focus-offset);
  border-radius: var(--dt-radius-xs);
}

.dt-notice__body {
  grid-area: body;
  margin: 0;
  color: var(--dt-text);
}

.dt-notice__meta {
  grid-area: meta;
  margin: 0;
  font-size: var(--dt-text-xs);
  line-height: var(--dt-leading-xs);
  color: var(--dt-text-muted);
}

.dt-notice__time {
  white-space: nowrap;
}

/* The state is what a reader scans for: in weight, beside the time. */
.dt-notice__state {
  font-weight: var(--dt-weight-medium);
}

.dt-notice__actions {
  grid-area: actions;
  display: flex;
  flex-wrap: wrap;
  gap: var(--dt-space-2);
  margin-block-start: var(--dt-space-1);
}

.dt-notice__actions:empty {
  display: none;
}

.dt-notice__actions form {
  margin: 0;
}

/* 24 px, WCAG 2.2's smallest target. */
.dt-notice__dismiss {
  grid-area: dismiss;
  display: inline-grid;
  place-items: center;
  width: var(--dt-space-6);
  height: var(--dt-space-6);
  margin: calc(var(--dt-space-1) * -1);
  padding: 0;
  font: inherit;
  font-size: var(--dt-text-lg);
  color: var(--dt-text-muted);
  background: none;
  border: 0;
  border-radius: var(--dt-radius-full);
  cursor: pointer;
}

.dt-notice__dismiss:hover {
  color: var(--dt-text);
  background-color: var(--dt-surface-sunken);
}

.dt-notice__dismiss:focus-visible {
  outline: var(--dt-focus-width) solid var(--dt-focus-ring);
  outline-offset: 0;
}

/* A phone: the panel spans the screen under the bar, and the pop-ups do too. */
@media (width < 48rem) {
  .dt-notifications__panel {
    position: fixed;
    top: calc(var(--dtc-app-bar-height, var(--dt-space-14)) + var(--dt-space-1));
    inset-inline: var(--dt-space-2);
    width: auto;
  }

  .dt-notifications__toasts {
    inset-inline: var(--dt-space-2);
    width: auto;
  }
}

/* Paper keeps the page, not its bell. */
@media print {
  .dt-notifications__panel,
  .dt-notifications__toasts {
    display: none;
  }
}

/*
 * The home dashboard — a reader's own starting page: the shortcuts they pinned, the processes
 * they start often, and the objects they opened lately. No status widgets: those live on the
 * pages they belong to.
 *
 * `.dt-home` lays its sections on a grid that reflows by itself: the shortcuts across the top,
 * the processes and the recent items side by side when there is room, one under the other when
 * there is not. Each section (`.dt-home__section`) is named by its heading.
 *
 *  - `.dt-shortcuts` — pinned shortcut tiles: a grid of links (`.dt-shortcut`), each an icon, a
 *    label and a line of detail. The whole tile is the link.
 *  - `.dt-quick-actions` — process shortcuts: buttons that start a process ("Commission a TBOX").
 *  - `.dt-recent` — recent items: links to objects, each with its kind and when it was opened.
 *
 * Which shortcuts are pinned, their order, and how a reader changes them are the app's; a
 * `.dt-home__more` link leads to the page where they do.
 *
 * Markup:
 *   <div class="dt-home">
 *     <section class="dt-home__section dt-home__section--shortcuts" aria-labelledby="pinned">
 *       <div class="dt-home__header"><h2 class="dt-home__heading" id="pinned">Pinned</h2>
 *         <a class="dt-home__more" href="/me/shortcuts/">Edit shortcuts</a></div>
 *       <ul class="dt-shortcuts">
 *         <li><a class="dt-shortcut" href="/plants/7/">
 *           <span class="dt-shortcut__icon" aria-hidden="true">…icon…</span>
 *           <span class="dt-shortcut__label">Lerma</span>
 *           <span class="dt-shortcut__detail">Plant · 312 TBOXes</span></a></li>
 *       </ul>
 *     </section>
 *     <section class="dt-home__section" aria-labelledby="start">
 *       <div class="dt-home__header"><h2 class="dt-home__heading" id="start">Start</h2></div>
 *       <ul class="dt-quick-actions">
 *         <li><a class="dt-button dt-button--secondary" href="/commission/">Commission a TBOX</a></li>
 *       </ul>
 *     </section>
 *     <section class="dt-home__section" aria-labelledby="recent">
 *       <div class="dt-home__header"><h2 class="dt-home__heading" id="recent">Recent</h2></div>
 *       <ul class="dt-recent">
 *         <li class="dt-recent__item"><a class="dt-recent__label" href="/tboxes/4/">TBOX 04</a>
 *           <span class="dt-recent__kind">TBOX</span>
 *           <time class="dt-recent__time" datetime="…">5 Oct, 12:04</time></li>
 *       </ul>
 *     </section>
 *   </div>
 *
 * An empty section says so (`.dt-home__empty`) and offers its way to fill it.
 */

/* Columns reflow by their own minimum width, as `dt-grid`'s do, so no page picks breakpoints:
   the processes and the recent items sit side by side when both fit, under the shortcuts. */
.dt-home {
  --dtc-home-tile-min: 12rem;
  --dtc-home-column-min: 22rem;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(var(--dtc-home-column-min), 100%), 1fr));
  gap: var(--dt-space-6);
  font-family: var(--dt-font-sans);
  color: var(--dt-text);
}

.dt-home__section--shortcuts {
  grid-column: 1 / -1;
}

.dt-home__section {
  display: flex;
  flex-direction: column;
  gap: var(--dt-space-3);
  min-width: 0;
}

.dt-home__header {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--dt-space-2) var(--dt-space-4);
}

.dt-home__heading {
  margin: 0;
  font-size: var(--dt-text-lg);
  line-height: var(--dt-leading-lg);
  font-weight: var(--dt-weight-semibold);
}

.dt-home__more {
  font-size: var(--dt-text-sm);
  line-height: var(--dt-leading-sm);
  font-weight: var(--dt-weight-medium);
  color: var(--dt-text-blue);
}

.dt-home__more:focus-visible {
  outline: var(--dt-focus-width) solid var(--dt-focus-ring);
  outline-offset: var(--dt-focus-offset);
  border-radius: var(--dt-radius-xs);
}

.dt-home__empty {
  margin: 0;
  padding: var(--dt-space-4);
  font-size: var(--dt-text-sm);
  line-height: var(--dt-leading-sm);
  color: var(--dt-text-muted);
  background-color: var(--dt-surface);
  border: 1px dashed var(--dt-border-strong);
  border-radius: var(--dt-radius-lg);
}

/* --- Pinned shortcut tiles ------------------------------------------------------------------- */

.dt-shortcuts {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(var(--dtc-home-tile-min), 100%), 1fr));
  gap: var(--dt-space-3);
  margin: 0;
  padding: 0;
  list-style: none;
}

.dt-shortcut {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  grid-template-areas:
    'icon label'
    'icon detail';
  align-items: center;
  column-gap: var(--dt-space-3);
  height: 100%;
  box-sizing: border-box;
  padding: var(--dt-space-4);
  background-color: var(--dt-surface);
  color: var(--dt-text);
  border: 1px solid var(--dt-border);
  border-radius: var(--dt-radius-lg);
  box-shadow: var(--dt-elevation-xs);
  text-decoration: none;
  transition:
    border-color var(--dt-duration-fast) var(--dt-ease-standard),
    box-shadow var(--dt-duration-fast) var(--dt-ease-standard);
}

.dt-shortcut:hover {
  border-color: var(--dt-border-strong);
  box-shadow: var(--dt-elevation-sm);
}

.dt-shortcut:focus-visible {
  outline: var(--dt-focus-width) solid var(--dt-focus-ring);
  outline-offset: var(--dt-focus-offset);
}

.dt-shortcut__icon {
  grid-area: icon;
  display: inline-grid;
  place-items: center;
  width: var(--dt-space-10);
  height: var(--dt-space-10);
  color: var(--dt-text-blue);
  background-color: var(--dt-surface-sunken);
  border-radius: var(--dt-radius-md);
}

/* A name wraps rather than being cut: at a phone's width or 200 % zoom it is still read whole. */
.dt-shortcut__label {
  grid-area: label;
  font-size: var(--dt-text-base);
  line-height: var(--dt-leading-base);
  font-weight: var(--dt-weight-semibold);
  overflow-wrap: anywhere;
}

/* A tile without a detail centres its label; one without an icon closes the icon's column. */
.dt-shortcut:not(:has(.dt-shortcut__detail)) .dt-shortcut__label {
  grid-row: 1 / -1;
}

.dt-shortcut:not(:has(.dt-shortcut__icon)) {
  grid-template-columns: minmax(0, 1fr);
  grid-template-areas:
    'label'
    'detail';
}

/* The detail is a second line, kept to two. */
.dt-shortcut__detail {
  grid-area: detail;
  display: -webkit-box;
  overflow: hidden;
  font-size: var(--dt-text-sm);
  line-height: var(--dt-leading-sm);
  color: var(--dt-text-muted);
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
}

/* --- Process shortcuts ----------------------------------------------------------------------- */

.dt-quick-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--dt-space-2);
  margin: 0;
  padding: 0;
  list-style: none;
}

/* --- Recent items ---------------------------------------------------------------------------- */

.dt-recent {
  margin: 0;
  padding: 0;
  list-style: none;
  background-color: var(--dt-surface);
  border: 1px solid var(--dt-border);
  border-radius: var(--dt-radius-lg);
}

.dt-recent__item {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  grid-template-areas:
    'label time'
    'kind time';
  column-gap: var(--dt-space-3);
  padding: var(--dt-space-3) var(--dt-space-4);
  font-size: var(--dt-text-sm);
  line-height: var(--dt-leading-sm);
}

.dt-recent__item + .dt-recent__item {
  border-top: 1px solid var(--dt-border);
}

.dt-recent__label {
  grid-area: label;
  font-weight: var(--dt-weight-medium);
  color: var(--dt-text-blue);
  overflow-wrap: anywhere;
}

.dt-recent__label:focus-visible {
  outline: var(--dt-focus-width) solid var(--dt-focus-ring);
  outline-offset: var(--dt-focus-offset);
  border-radius: var(--dt-radius-xs);
}

.dt-recent__kind {
  grid-area: kind;
  color: var(--dt-text-muted);
}

.dt-recent__time {
  grid-area: time;
  align-self: center;
  color: var(--dt-text-muted);
  white-space: nowrap;
}

@media (prefers-reduced-motion: reduce) {
  .dt-shortcut {
    transition: none;
  }
}

/*
 * `.dt-auth` — the pages a reader sees before the app: sign in, and set or reset a password. One
 * card on the page's background, the product's mark above it, nothing else to navigate.
 *
 * The form inside is the system's own (`components/form.css`, Django's through
 * `deeptrackstyle.forms.FormRenderer`). "Sign in with Google" is Google's own button image
 * (`.dt-google-signin`), from the app's own copy of Google's bundle, as Google's branding rules ask:
 * its "G" and its words in Google's colours and face, unchanged. The light button is shown on a
 * light page and the dark one on a dark page.
 *
 * Markup:
 *   <main class="dt-auth">
 *     <div class="dt-auth__card">
 *       <div class="dt-auth__brand">
 *         <img class="dt-auth__mark--light" src="…/deeptrack.svg" alt="Deeptrack">
 *         <img class="dt-auth__mark--dark" src="…/deeptrack-white.svg" alt="Deeptrack"> PTO</div>
 *       <h1 class="dt-auth__title">Sign in</h1>
 *       <p class="dt-auth__lede">…</p>
 *       <form class="dt-auth__form" method="post">…fields…
 *         <div class="dt-auth__actions"><button class="dt-button dt-button--primary">Sign in</button>
 *           <a class="dt-auth__link" href="/accounts/password/reset/">Forgot your password?</a></div>
 *       </form>
 *       <p class="dt-auth__divider"><span>or</span></p>
 *       <a class="dt-google-signin" href="/accounts/google/login/">
 *         <img class="dt-google-signin__light" src="…/signin-light.png"
 *              srcset="…/signin-light@2x.png 2x" alt="Sign in with Google" width="180" height="40">
 *         <img class="dt-google-signin__dark" src="…/signin-dark.png"
 *              srcset="…/signin-dark@2x.png 2x" alt="Sign in with Google" width="180" height="40">
 *       </a>
 *     </div>
 *     <p class="dt-auth__footer">…</p>
 *   </main>
 */

.dt-auth {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--dt-space-4);
  box-sizing: border-box;
  min-height: 100vh;
  min-height: 100dvh;
  padding: var(--dt-space-6) var(--dt-space-4);
  background-color: var(--dt-background);
  color: var(--dt-text);
  font-family: var(--dt-font-sans);
}

/* The inline variant lays the page in the flow of another — a documentation specimen. */
.dt-auth--inline {
  min-height: 0;
}

.dt-auth__card {
  display: flex;
  flex-direction: column;
  gap: var(--dt-space-4);
  box-sizing: border-box;
  width: min(26rem, 100%);
  padding: var(--dt-space-8) var(--dt-space-6);
  background-color: var(--dt-surface);
  border: 1px solid var(--dt-border);
  border-radius: var(--dt-radius-xl);
  box-shadow: var(--dt-elevation-md);
}

.dt-auth__brand {
  display: flex;
  align-items: center;
  gap: var(--dt-space-3);
  font-size: var(--dt-text-lg);
  line-height: var(--dt-leading-lg);
  font-weight: var(--dt-weight-semibold);
}

/* The mark at its smallest legible size; its clear space is the gap around it. */
/* Sized, not shown: a `display` here would outrank the rule that hides the other mode's mark. */
.dt-auth__brand img,
.dt-auth__brand svg {
  height: var(--dt-space-8);
  width: auto;
}

.dt-auth__title {
  margin: 0;
  font-size: var(--dt-text-2xl);
  line-height: var(--dt-leading-2xl);
  font-weight: var(--dt-weight-semibold);
}

.dt-auth__lede {
  margin: 0;
  font-size: var(--dt-text-sm);
  line-height: var(--dt-leading-sm);
  color: var(--dt-text-muted);
}

.dt-auth__form {
  display: flex;
  flex-direction: column;
  gap: var(--dt-space-4);
  margin: 0;
}

.dt-auth__actions {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: var(--dt-space-3);
}

.dt-auth__actions .dt-button {
  justify-content: center;
}

.dt-auth__link {
  align-self: center;
  font-size: var(--dt-text-sm);
  line-height: var(--dt-leading-sm);
  font-weight: var(--dt-weight-medium);
  color: var(--dt-text-blue);
}

.dt-auth__link:focus-visible {
  outline: var(--dt-focus-width) solid var(--dt-focus-ring);
  outline-offset: var(--dt-focus-offset);
  border-radius: var(--dt-radius-xs);
}

/* "or", between two rules, between the form and the other way in. */
.dt-auth__divider {
  display: flex;
  align-items: center;
  gap: var(--dt-space-3);
  margin: 0;
  font-size: var(--dt-text-xs);
  line-height: var(--dt-leading-xs);
  font-weight: var(--dt-weight-semibold);
  letter-spacing: var(--dt-tracking-wide);
  text-transform: uppercase;
  color: var(--dt-text-muted);
}

.dt-auth__divider::before,
.dt-auth__divider::after {
  content: '';
  flex: 1;
  border-top: 1px solid var(--dt-border);
}

.dt-auth__footer {
  margin: 0;
  max-width: 26rem;
  font-size: var(--dt-text-xs);
  line-height: var(--dt-leading-xs);
  color: var(--dt-text-muted);
  text-align: center;
}

/* --- Sign in with Google ----------------------------------------------------------------------
   Google's button image, unchanged: its size, its "G" and its words are Google's to set. The link
   around it carries the focus ring, outside the image, and centres it. */

.dt-google-signin {
  display: flex;
  justify-content: center;
  align-self: center;
  border-radius: var(--dt-radius-sm);
  line-height: 0;
}

/* Sized here, but shown below: a `display` here would outrank the rule that hides one. */
.dt-google-signin img {
  width: 180px;
  height: 40px;
}

.dt-google-signin__light,
.dt-auth__mark--light {
  display: block;
}

.dt-google-signin:focus-visible {
  outline: var(--dt-focus-width) solid var(--dt-focus-ring);
  outline-offset: var(--dt-focus-offset);
}

/* One image per mode, as the page's: Google's light button and the colour mark unless the page is
   dark, then Google's dark button and the white mark. */
.dt-google-signin__dark,
.dt-auth__mark--dark {
  display: none;
}

[data-dt-theme='dark'] :is(.dt-google-signin__dark, .dt-auth__mark--dark) {
  display: block;
}

[data-dt-theme='dark'] :is(.dt-google-signin__light, .dt-auth__mark--light) {
  display: none;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-dt-theme='light']) :is(.dt-google-signin__dark, .dt-auth__mark--dark) {
    display: block;
  }

  :root:not([data-dt-theme='light']) :is(.dt-google-signin__light, .dt-auth__mark--light) {
    display: none;
  }

  /* A light panel pinned inside a dark page keeps the light images. */
  :root:not([data-dt-theme='light'])
    [data-dt-theme='light']
    :is(.dt-google-signin__dark, .dt-auth__mark--dark) {
    display: none;
  }

  :root:not([data-dt-theme='light'])
    [data-dt-theme='light']
    :is(.dt-google-signin__light, .dt-auth__mark--light) {
    display: block;
  }
}

/* And a light panel pinned inside a page pinned dark. */
[data-dt-theme='dark'] [data-dt-theme='light'] :is(.dt-google-signin__dark, .dt-auth__mark--dark) {
  display: none;
}

[data-dt-theme='dark']
  [data-dt-theme='light']
  :is(.dt-google-signin__light, .dt-auth__mark--light) {
  display: block;
}

/*
 * `.dt-app` — the application frame: a bar across the top, a sidebar of navigation, and the
 * page. `.dt-sidenav` is the navigation that goes in the sidebar: sections under an optional
 * heading, links two levels deep, the second level a native `<details>` disclosure, so it opens
 * and closes with no script at all.
 *
 * The frame has two shapes, either side of the one breakpoint (48rem, shared with layout.css):
 *
 *  - **Wider than a phone**, the sidebar sits beside the page and scrolls on its own. The toggle
 *    collapses it out of the way; `data-dt-sidebar="collapsed"` on `.dt-app` is that state.
 *  - **On a phone**, the sidebar is a drawer over the page, below the bar, closed until the
 *    toggle opens it; `data-dt-drawer="open"` is that state. The bar holds its minimum height,
 *    `--dtc-app-bar-height`, for the drawer to start under it.
 *
 * Both states, and the toggle's `aria-expanded`, are the behaviour script's to set
 * (`@deeptrack/css/behaviour.js`), or `@deeptrack/react`'s `AppFrame`. Until the script marks
 * the frame `data-dt-ready`, nothing depends on it: the toggle is hidden, the sidebar is always
 * open, and on a phone it renders above the page instead of as a drawer, so a page whose script
 * failed to load still has its navigation.
 *
 * The current page is `aria-current="page"` — marked by weight and a bar as well as colour — and
 * its group is rendered `open`, so the reader sees where they are without opening anything.
 *
 * Markup:
 *   <div class="dt-app" data-dt-app>
 *     <a class="dt-app__skip" href="#main">Skip to content</a>
 *     <header class="dt-app__bar">
 *       <button class="dt-app__toggle" type="button" aria-controls="sidebar" aria-expanded="true"
 *               data-dt-sidebar-toggle>
 *         <span class="dt-app__toggle-icon" aria-hidden="true"></span>
 *         <span class="dt-visually-hidden">Menu</span>
 *       </button>
 *       <a class="dt-app__brand" href="/">PTO</a>
 *       <div class="dt-app__actions">…</div>
 *     </header>
 *     <div class="dt-app__backdrop" data-dt-sidebar-close hidden></div>
 *     <nav class="dt-app__sidebar dt-sidenav" id="sidebar" aria-label="Main">
 *       <p class="dt-sidenav__heading" id="nav-plant">This plant</p>
 *       <ul class="dt-sidenav__list" aria-labelledby="nav-plant">
 *         <li><a class="dt-sidenav__link" href="/">Overview</a></li>
 *         <li>
 *           <details class="dt-sidenav__group" open>
 *             <summary class="dt-sidenav__link">Devices</summary>
 *             <ul class="dt-sidenav__sublist">
 *               <li><a class="dt-sidenav__link" href="/tboxes" aria-current="page">TBOXes</a></li>
 *             </ul>
 *           </details>
 *         </li>
 *       </ul>
 *     </nav>
 *     <main class="dt-app__main" id="main">…</main>
 *   </div>
 *
 * A link may lead with `<span class="dt-sidenav__icon" aria-hidden="true">` holding an icon.
 */

.dt-app {
  --dtc-app-bar-height: var(--dt-space-14);
  --dtc-app-sidebar-width: 16rem;
  display: grid;
  grid-template-areas:
    'bar'
    'sidebar'
    'main';
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: auto auto 1fr;
  min-height: 100vh;
  min-height: 100dvh;
  background-color: var(--dt-background);
  color: var(--dt-text);
  font-family: var(--dt-font-sans);
}

/* --- The bar --------------------------------------------------------------------------------- */

.dt-app__bar {
  grid-area: bar;
  position: sticky;
  top: 0;
  z-index: var(--dt-z-sticky);
  display: flex;
  align-items: center;
  gap: var(--dt-space-3);
  box-sizing: border-box;
  min-height: var(--dtc-app-bar-height);
  padding-inline: var(--dt-space-4);
  background-color: var(--dt-surface);
  border-bottom: 1px solid var(--dt-border);
}

.dt-app__brand {
  display: inline-flex;
  align-items: center;
  gap: var(--dt-space-2);
  border-radius: var(--dt-radius-md);
  font-size: var(--dt-text-lg);
  line-height: var(--dt-leading-lg);
  font-weight: var(--dt-weight-semibold);
  color: var(--dt-text);
  text-decoration: none;
}

.dt-app__brand img,
.dt-app__brand svg {
  display: block;
  height: var(--dt-space-8);
  width: auto;
}

.dt-app__actions {
  display: flex;
  align-items: center;
  gap: var(--dt-space-2);
  margin-inline-start: auto;
}

.dt-app__toggle {
  display: inline-flex;
  flex: none;
  align-items: center;
  justify-content: center;
  width: var(--dt-space-10);
  height: var(--dt-space-10);
  padding: 0;
  border: 0;
  border-radius: var(--dt-radius-md);
  background-color: transparent;
  color: var(--dt-text);
  cursor: pointer;
}

.dt-app__toggle:hover {
  background-color: var(--dt-surface-sunken);
}

/* Three bars, drawn in the text colour, so the toggle needs no icon set to be legible. */
.dt-app__toggle-icon,
.dt-app__toggle-icon::before,
.dt-app__toggle-icon::after {
  display: block;
  width: var(--dt-space-5);
  height: var(--dt-space-0_5);
  border-radius: var(--dt-radius-full);
  background-color: currentColor;
}

.dt-app__toggle-icon {
  position: relative;
}

.dt-app__toggle-icon::before,
.dt-app__toggle-icon::after {
  content: '';
  position: absolute;
  left: 0;
}

.dt-app__toggle-icon::before {
  top: calc(var(--dt-space-1_5) * -1);
}

.dt-app__toggle-icon::after {
  top: var(--dt-space-1_5);
}

.dt-app:not([data-dt-ready]) .dt-app__toggle {
  display: none;
}

.dt-app__skip {
  position: absolute;
  top: var(--dt-space-2);
  left: var(--dt-space-2);
  z-index: var(--dt-z-tooltip);
  padding: var(--dt-space-2) var(--dt-space-3);
  border-radius: var(--dt-radius-md);
  background-color: var(--dt-surface);
  color: var(--dt-text-blue);
  font-weight: var(--dt-weight-semibold);
  transform: translateY(calc(-100% - var(--dt-space-4)));
}

.dt-app__skip:focus {
  transform: none;
}

.dt-app__toggle:focus-visible,
.dt-app__brand:focus-visible,
.dt-app__skip:focus-visible {
  outline: var(--dt-focus-width) solid var(--dt-focus-ring);
  outline-offset: var(--dt-focus-offset);
}

/* --- The sidebar and the page ---------------------------------------------------------------- */

.dt-app__sidebar {
  grid-area: sidebar;
  box-sizing: border-box;
  background-color: var(--dt-surface);
  border-bottom: 1px solid var(--dt-border);
}

.dt-app__main {
  grid-area: main;
  min-width: 0;
  padding-block: var(--dt-space-6);
}

.dt-app__backdrop {
  display: none;
}

/* A phone, once the script is running: the sidebar becomes a drawer over the page. */
@media (width < 48rem) {
  .dt-app[data-dt-ready] {
    grid-template-areas:
      'bar'
      'main';
    grid-template-rows: auto 1fr;
  }

  /* Below the bar, so the toggle that closes the drawer stays in sight and in reach. */
  .dt-app[data-dt-ready] .dt-app__sidebar {
    position: fixed;
    top: var(--dtc-app-bar-height);
    bottom: 0;
    left: 0;
    z-index: var(--dt-z-modal);
    width: min(var(--dtc-app-sidebar-width), 85vw);
    overflow-y: auto;
    overscroll-behavior: contain;
    border-bottom: 0;
    border-right: 1px solid var(--dt-border);
    box-shadow: var(--dt-elevation-xl);
    transform: translateX(-100%);
    visibility: hidden;
    transition:
      transform var(--dt-duration-slow) var(--dt-ease-decelerate),
      visibility 0s linear var(--dt-duration-slow);
  }

  .dt-app[data-dt-drawer='open'] .dt-app__sidebar {
    transform: none;
    visibility: visible;
    transition: transform var(--dt-duration-slow) var(--dt-ease-decelerate);
  }

  .dt-app[data-dt-drawer='open'] .dt-app__backdrop {
    display: block;
    position: fixed;
    top: var(--dtc-app-bar-height);
    right: 0;
    bottom: 0;
    left: 0;
    z-index: var(--dt-z-overlay);
    background-color: rgb(from var(--dt-neutral-950) r g b / 0.5);
  }
}

/* Wider than a phone: the sidebar sits beside the page, under the bar, and scrolls on its own. */
@media (width >= 48rem) {
  .dt-app {
    grid-template-areas:
      'bar bar'
      'sidebar main';
    grid-template-columns: var(--dtc-app-sidebar-width) minmax(0, 1fr);
    grid-template-rows: auto 1fr;
  }

  .dt-app__sidebar {
    position: sticky;
    top: var(--dtc-app-bar-height);
    align-self: start;
    height: calc(100dvh - var(--dtc-app-bar-height));
    overflow-y: auto;
    border-bottom: 0;
    border-right: 1px solid var(--dt-border);
  }

  .dt-app[data-dt-sidebar='collapsed'] {
    grid-template-areas:
      'bar'
      'main';
    grid-template-columns: minmax(0, 1fr);
  }

  .dt-app[data-dt-sidebar='collapsed'] .dt-app__sidebar {
    display: none;
  }
}

/* --- The navigation -------------------------------------------------------------------------- */

.dt-sidenav {
  padding: var(--dt-space-3);
  font-family: var(--dt-font-sans);
}

.dt-sidenav__heading {
  margin: var(--dt-space-4) 0 var(--dt-space-1);
  padding-inline: var(--dt-space-3);
  font-size: var(--dt-text-xs);
  line-height: var(--dt-leading-xs);
  font-weight: var(--dt-weight-semibold);
  letter-spacing: var(--dt-tracking-wider);
  text-transform: uppercase;
  color: var(--dt-text-muted);
}

.dt-sidenav__heading:first-child {
  margin-top: 0;
}

.dt-sidenav__list,
.dt-sidenav__sublist {
  display: flex;
  flex-direction: column;
  gap: var(--dt-space-0_5);
  margin: 0;
  padding: 0;
  list-style: none;
}

.dt-sidenav__sublist {
  margin-top: var(--dt-space-0_5);
  padding-inline-start: var(--dt-space-8);
}

.dt-sidenav__link {
  display: flex;
  align-items: center;
  gap: var(--dt-space-3);
  padding: var(--dt-space-2) var(--dt-space-3);
  border-radius: var(--dt-radius-md);
  font-size: var(--dt-text-sm);
  line-height: var(--dt-leading-sm);
  font-weight: var(--dt-weight-medium);
  color: var(--dt-text-muted);
  text-decoration: none;
  cursor: pointer;
  transition:
    background-color var(--dt-duration-fast) var(--dt-ease-standard),
    color var(--dt-duration-fast) var(--dt-ease-standard);
}

.dt-sidenav__link:hover {
  color: var(--dt-text);
  background-color: var(--dt-surface-sunken);
}

.dt-sidenav__link[aria-current='page'],
.dt-sidenav__link[aria-current='page']:hover {
  color: var(--dt-text-blue);
  background-color: transparent;
  font-weight: var(--dt-weight-semibold);
  box-shadow: inset var(--dt-space-0_5) 0 0 var(--dt-text-blue);
}

.dt-sidenav__link:focus-visible {
  outline: var(--dt-focus-width) solid var(--dt-focus-ring);
  outline-offset: calc(var(--dt-focus-width) * -1);
}

.dt-sidenav__icon {
  display: inline-flex;
  flex: none;
  align-items: center;
  justify-content: center;
  width: var(--dt-space-5);
  height: var(--dt-space-5);
}

.dt-sidenav__group > summary {
  list-style: none;
}

.dt-sidenav__group > summary::-webkit-details-marker {
  display: none;
}

/* The chevron: points right when closed, down when open. Drawn in the text colour. */
.dt-sidenav__group > summary::after {
  content: '';
  flex: none;
  width: var(--dt-space-1_5);
  height: var(--dt-space-1_5);
  margin-inline-start: auto;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: rotate(-45deg);
  transition: transform var(--dt-duration-fast) var(--dt-ease-standard);
}

.dt-sidenav__group[open] > summary::after {
  transform: rotate(45deg);
}

/* The group holding the current page says so in weight, open or closed. */
.dt-sidenav__group:has([aria-current='page']) > summary {
  color: var(--dt-text);
  font-weight: var(--dt-weight-semibold);
}

/* Motion is decoration here: with reduced motion asked for, state changes are instant. */
@media (prefers-reduced-motion: reduce) {
  .dt-sidenav__link,
  .dt-sidenav__group > summary::after,
  .dt-app[data-dt-ready] .dt-app__sidebar,
  .dt-app[data-dt-drawer='open'] .dt-app__sidebar {
    transition: none;
  }
}

/* Paper has no navigation: the page alone is printed. */
@media print {
  .dt-app {
    display: block;
    min-height: 0;
  }

  .dt-app__bar,
  .dt-app__sidebar,
  .dt-app__backdrop,
  .dt-app__skip {
    display: none;
  }
}

/*
 * `.dt-error` — an error page: what happened and what to do, in words, for a 400, 403, 404, 500
 * or a failed form check. The status code is said too, for whoever reports it, but never alone.
 *
 * The page stands on its own: it uses only the tokens, the reset, the base elements, the layout
 * and the buttons, so `dist/error-page.css` (those and this file) can be inlined into a page that
 * renders with no static files, because the static files may be what failed. `deeptrackstyle`'s
 * Django error templates do that.
 *
 * Markup:
 *   <main class="dt-error">
 *     <div class="dt-error__panel">
 *       <p class="dt-error__code">Error 404</p>
 *       <h1 class="dt-error__title">Page not found</h1>
 *       <div class="dt-error__body">
 *         <p>Nothing is at this address. The link may be out of date.</p>
 *         <p>Check the address, or start again from the home page.</p>
 *       </div>
 *       <div class="dt-error__actions">
 *         <a class="dt-button dt-button--primary" href="/">Go to the home page</a>
 *       </div>
 *       <p class="dt-error__detail">Reference 7f3a2c</p>
 *     </div>
 *   </main>
 *
 * `.dt-error--inline` drops the full-viewport height, for a specimen inside another page.
 */

.dt-error {
  display: grid;
  /* One track no wider than the page, or long text widens it past a phone's screen. */
  grid-template-columns: minmax(0, 1fr);
  place-items: center;
  min-height: 100vh;
  min-height: 100dvh;
  padding: var(--dt-space-6) var(--dt-space-4);
  background-color: var(--dt-background);
  color: var(--dt-text);
  font-family: var(--dt-font-sans);
}

.dt-error--inline {
  min-height: 0;
}

.dt-error__panel {
  display: flex;
  flex-direction: column;
  gap: var(--dt-space-4);
  width: 100%;
  max-width: 36rem;
  padding: var(--dt-space-8) var(--dt-space-6);
  background-color: var(--dt-surface);
  border: 1px solid var(--dt-border);
  border-radius: var(--dt-radius-lg);
  box-shadow: var(--dt-elevation-sm);
}

.dt-error__code {
  margin: 0;
  font-size: var(--dt-text-sm);
  line-height: var(--dt-leading-sm);
  font-weight: var(--dt-weight-semibold);
  letter-spacing: var(--dt-tracking-wide);
  text-transform: uppercase;
  color: var(--dt-text-muted);
}

.dt-error__title {
  margin: 0;
  font-size: var(--dt-text-3xl);
  line-height: var(--dt-leading-3xl);
  font-weight: var(--dt-weight-semibold);
  color: var(--dt-text);
}

.dt-error__body {
  display: flex;
  flex-direction: column;
  gap: var(--dt-space-2);
}

.dt-error__body > * {
  margin: 0;
}

.dt-error__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--dt-space-3);
  padding-top: var(--dt-space-2);
}

.dt-error__detail {
  margin: 0;
  font-family: var(--dt-font-mono);
  font-size: var(--dt-text-xs);
  line-height: var(--dt-leading-xs);
  color: var(--dt-text-muted);
}

/*
 * `.dt-report` — the shell of an HTML report: the successor to `reporting_lib`'s head, title
 * block, sections and confidentiality notice. The Python package (`deeptrackstyle.report`)
 * emits this markup; any other renderer may too, since nothing here is Python-specific.
 *
 * A report is a document that is printed as often as it is read, so three things differ from
 * a portal page:
 *
 *  - **It is always light.** The renderer sets `data-dt-theme="light"` on `<html>`: a reader on
 *    a dark system would otherwise print white text on a dark page. The colours below are
 *    still role tokens, so the same markup inside a dark portal follows the portal.
 *  - **Sections are `<section>` with an `<h2>`**, not the always-open `<details>` the old shell
 *    used — a heading a screen reader announces, and one that print.css can keep with its
 *    content. Only the appendix collapses (`.dt-appendix`), and it is opened before printing.
 *  - **Backgrounds print.** Browsers drop them by default, which would erase the zebra stripe,
 *    the callout surfaces and the header rule from every PDF. The report opts back in.
 *
 * Markup:
 *   <html data-dt-theme="light"><body>
 *   <article class="dt-report">
 *     <header class="dt-report__header">
 *       <div class="dt-report__mark"><img src="…" alt="Deeptrack"></div>
 *       <div class="dt-report__heading">
 *         <h1 class="dt-report__title">Wind report after event</h1>
 *         <p class="dt-report__subtitle">Plant …</p>
 *         <p class="dt-report__notice">Confidential …</p>
 *         <div class="dt-report__actions dt-print-hide"><button class="dt-button">…</button></div>
 *       </div>
 *     </header>
 *     <section class="dt-report__section"><h2>Summary</h2> … </section>
 *     <footer class="dt-report__footer">…</footer>
 *   </article>
 */

.dt-report {
  max-width: 72rem;
  margin: 0 auto;
  padding: var(--dt-space-8) var(--dt-space-6);
  font-family: var(--dt-font-sans);
  color: var(--dt-text);
  -webkit-print-color-adjust: exact;
  print-color-adjust: exact;
}

.dt-report__header {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--dt-space-4) var(--dt-space-6);
  padding-bottom: var(--dt-space-5);
  margin-bottom: var(--dt-space-6);
  border-bottom: 3px solid var(--dt-fill-blue);
}

/*
 * The mark's clear space is the approved rule (`logos.json`): 0.25 x its rendered height on all
 * four sides, and never narrower than 120 px on screen or 25 mm on paper. At a 40 px height the
 * padding is 10 px, and the Deeptrack mark (aspect 4.7) is 190 px wide, above the minimum.
 */
.dt-report__mark {
  flex: none;
  /* stylelint-disable-next-line deeptrack/spacing-token -- the logo's clear space, not a step */
  padding: 10px;
}

/* The title, subtitles, notice and actions: everything beside the mark. */
.dt-report__heading {
  flex: 1 1 24rem;
  min-width: 0;
}

.dt-report__mark img,
.dt-report__mark svg {
  display: block;
  height: 40px;
  width: auto;
  min-width: 120px;
}

.dt-report__title {
  margin: 0;
  font-size: var(--dt-text-3xl);
  line-height: var(--dt-leading-3xl);
  font-weight: var(--dt-weight-bold);
  letter-spacing: var(--dt-tracking-tight);
  color: var(--dt-text-blue);
}

.dt-report__subtitle {
  margin: var(--dt-space-1) 0 0;
  font-size: var(--dt-text-base);
  line-height: var(--dt-leading-base);
  color: var(--dt-text-muted);
}

.dt-report__notice {
  margin: var(--dt-space-2) 0 0;
  font-size: var(--dt-text-xs);
  line-height: var(--dt-leading-xs);
  font-weight: var(--dt-weight-semibold);
  letter-spacing: var(--dt-tracking-wide);
  text-transform: uppercase;
  color: var(--dt-text-muted);
}

.dt-report__actions {
  margin-top: var(--dt-space-3);
}

.dt-report__section {
  margin-bottom: var(--dt-space-10);
}

.dt-report__section > h2 {
  margin: 0 0 var(--dt-space-4);
  padding-bottom: var(--dt-space-2);
  font-size: var(--dt-text-2xl);
  line-height: var(--dt-leading-2xl);
  color: var(--dt-text-blue);
  border-bottom: 1px solid var(--dt-border);
}

.dt-report__subhead {
  margin: var(--dt-space-6) 0 var(--dt-space-2);
  font-size: var(--dt-text-lg);
  line-height: var(--dt-leading-lg);
  font-weight: var(--dt-weight-semibold);
  color: var(--dt-text-blue);
}

.dt-report__note {
  margin: var(--dt-space-2) 0 var(--dt-space-3);
  font-size: var(--dt-text-sm);
  line-height: var(--dt-leading-sm);
  color: var(--dt-text-muted);
}

/* Tiles in a grid that fills the line, three across on paper. */
.dt-report__grid {
  display: grid;
  gap: var(--dt-space-3);
  grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
  margin: var(--dt-space-1) 0 var(--dt-space-3);
}

/* A tile's state: a coloured rule on its leading edge, never alone — the tile states it in words. */
.dt-report__grid .dt-tile {
  border-left-width: 4px;
}

.dt-tile--success {
  border-left-color: var(--dt-success-solid);
}

.dt-tile--warning {
  border-left-color: var(--dt-warning-solid);
}

.dt-tile--error {
  border-left-color: var(--dt-error-solid);
}

.dt-tile--info {
  border-left-color: var(--dt-info-solid);
}

.dt-tile__note {
  margin-top: var(--dt-space-1);
  font-size: var(--dt-text-xs);
  line-height: var(--dt-leading-xs);
  color: var(--dt-text-muted);
}

/* The state word inside a tile or a table cell: the part that survives a grey photocopy. */
.dt-state {
  font-size: var(--dt-text-xs);
  font-weight: var(--dt-weight-bold);
  letter-spacing: var(--dt-tracking-wide);
  text-transform: uppercase;
}

.dt-state--success {
  color: var(--dt-success-text);
}

.dt-state--warning {
  color: var(--dt-warning-text);
}

.dt-state--error {
  color: var(--dt-error-text);
}

.dt-state--info {
  color: var(--dt-info-text);
}

.dt-report__chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--dt-space-1_5);
  margin: var(--dt-space-1_5) 0 var(--dt-space-3);
}

.dt-report .dt-alert {
  margin: var(--dt-space-2) 0 var(--dt-space-3);
}

.dt-figure {
  margin: var(--dt-space-4) 0;
}

.dt-figure figcaption {
  margin-top: var(--dt-space-1);
  font-size: var(--dt-text-xs);
  line-height: var(--dt-leading-xs);
  color: var(--dt-text-muted);
}

/* Provenance and other label/value lists. */
.dt-report__facts {
  margin: 0;
  font-size: var(--dt-text-sm);
  line-height: var(--dt-leading-sm);
  color: var(--dt-text-muted);
}

.dt-report__facts dt {
  margin-top: var(--dt-space-2);
  font-weight: var(--dt-weight-semibold);
  color: var(--dt-text);
}

.dt-report__facts dd {
  margin: 0;
}

/* The appendix: collapsed on screen, opened by the report's `beforeprint` script for paper. */
.dt-appendix {
  margin-bottom: var(--dt-space-3);
  border: 1px solid var(--dt-border);
  border-radius: var(--dt-radius-md);
}

.dt-appendix > summary {
  cursor: pointer;
  padding: var(--dt-space-2) var(--dt-space-3);
  font-size: var(--dt-text-base);
  font-weight: var(--dt-weight-medium);
  color: var(--dt-text-blue);
  background-color: var(--dt-surface-sunken);
  border-radius: var(--dt-radius-md);
}

.dt-appendix[open] > summary {
  border-bottom: 1px solid var(--dt-border);
  border-radius: var(--dt-radius-md) var(--dt-radius-md) 0 0;
}

.dt-appendix > summary:focus-visible {
  outline: var(--dt-focus-width) solid var(--dt-focus-ring);
  outline-offset: var(--dt-focus-offset);
}

.dt-appendix__body {
  padding: var(--dt-space-2) var(--dt-space-3) var(--dt-space-3);
}

.dt-report__footer {
  margin-top: var(--dt-space-10);
  padding-top: var(--dt-space-3);
  border-top: 1px solid var(--dt-border);
  font-size: var(--dt-text-xs);
  line-height: var(--dt-leading-xs);
  color: var(--dt-text-muted);
}

/*
 * Paper. A4, because the department prints in Spain. The Python renderer re-lays plotly figures
 * at this page's content width before printing (`PRINT_WIDTH_PX`), so the size and the side
 * margins here and there change together; the renderer adds the confidentiality
 * notice to the bottom-left margin box, since only it knows the owner's name. Page numbers
 * repeat on every page without overlapping the content, which `position: fixed` cannot do.
 */
@page {
  size: A4;
  margin: 16mm 14mm 18mm;

  @bottom-right {
    content: counter(page) ' / ' counter(pages);
    font-family: var(--dt-font-sans);
    font-size: 8pt;
    color: var(--dt-text-muted);
  }
}

@media print {
  /* Paper is white: the page background is a screen tint, and it would print as a grey box. */
  :root:has(.dt-report),
  body:has(.dt-report) {
    background-color: var(--dt-surface);
  }

  .dt-report {
    max-width: none;
    padding: 0;
  }

  .dt-report__grid {
    grid-template-columns: repeat(3, 1fr);
  }

  /* On paper the notice is in every page's margin, so the header copy would only repeat it. */
  .dt-report__footer .dt-report__notice {
    display: none;
  }

  .dt-report__mark img,
  .dt-report__mark svg {
    min-width: 25mm;
  }

  .dt-report__section > h2,
  .dt-report__subhead {
    break-after: avoid;
  }

  .dt-appendix {
    break-inside: auto;
  }

  /* An appendix's summary is its heading on paper, and is kept with what it heads. */
  .dt-appendix > summary {
    break-after: avoid;
  }

  .dt-appendix > summary {
    background-color: transparent;
  }
}

/*
 * Print rules for anything the collection renders — a report, a portal page, a Django view.
 * Seeded from the alertmonitor P1 report, which had to override `reporting_lib` to get them:
 * that library set `page-break-inside: avoid` on `body` and on every `<details>`, which no
 * multi-page section can satisfy, and left the layout engine to fall back wherever it liked.
 *
 * The rule here is the inverse: **containers may break, the things inside them may not.** A
 * section runs across as many pages as it needs; a figure, a tile, a callout or a table row is
 * never cut in half, and a heading is never left at the foot of a page without what it heads.
 *
 * Controls are not content. Anything marked `.dt-print-hide` — a "Download as PDF" button, a
 * filter bar — disappears from the paper and from the PDF it produces.
 */

@media print {
  .dt-print-hide {
    display: none !important;
  }

  h1,
  h2,
  h3,
  h4,
  caption {
    break-after: avoid;
  }

  figure,
  svg,
  img,
  .dt-figure,
  .dt-tile,
  .dt-alert,
  .dt-card {
    break-inside: avoid;
  }

  /* A table may run across pages; its rows may not, and its header repeats on each one. */
  .dt-table {
    break-inside: auto;
  }

  .dt-table thead {
    display: table-header-group;
  }

  p,
  li,
  dd {
    orphans: 3;
    widows: 3;
  }

  /* Paper has no depth: an elevation shadow prints as a grey smudge under the panel. */
  .dt-card,
  .dt-tile {
    box-shadow: none;
  }
}
