/* ==========================================================================
   PUDL, the Pleasantly Usable Design Language (pronounced "puddle").
   Version 0.27.0.

   PUDL gives every affordance one visual representation, so that somebody
   who has learned it once can use any application built with it on sight.

   Elevation signals interactivity:
     - Raised, with a hairline border and an inset highlight, is pressable (.btn).
     - Sunken, with an inset shadow, is an input (.form-input).
     - Flat is display only (.kv-table cells, .form-input[readonly]).
   Colour is never the only signal: every meaningful state carries a glyph.
   One .btn-primary per visible context.

   State is marked with the ARIA attribute that states it, so the eye and
   assistive technology are told the same thing: aria-current for where the
   reader is, aria-pressed and aria-checked for toggles, aria-selected for
   the tab of a tab panel. The older .active class still works as an alias.

   A project themes PUDL with a stylesheet loaded after this one. It may
   change any colour token, including the background and the topbar, and
   the font tokens, within the restrictions in the README. It never changes
   what a raised, sunken or flat surface means.

   Extracted from the Andoneer Design Language v2 reference page
   (Andoneer repository, mockups/adl-v2-reference.html, commit 097a9c7).
   ========================================================================== */

/* Inter, by Rasmus Andersson, under the SIL Open Font License 1.1
   (fonts/Inter-LICENSE.txt). One variable file covers weights 400 to 700,
   and its optical-size axis tightens the face at heading sizes. */
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("/pudl/fonts/InterVariable-2bf3d951bf9d6109437ea0874bbf12fb.woff2") format("woff2");
}
@font-face {
  font-family: "Inter";
  font-style: italic;
  font-weight: 100 900;
  font-display: swap;
  src: url("/pudl/fonts/InterVariable-Italic-8d1dc86da25893737b6bb8a9c64b790e.woff2") format("woff2");
}

:root {
  /* === The palette ==========================================================
     These are the values a project theme sets. Everything under "Derived"
     further down is computed from them, so a new palette is this block and
     its dark counterpart, and nothing else. Set them on :root and on
     [data-theme="dark"], because the derived tokens are computed there. */

  /* Page and text. */
  --bg: #f3f4f6;
  --surface: #fbfbfc;
  --surface-alt: #e4e7eb;
  --text: #1b1f24;
  --text-muted: #5f6873;
  --border: #cfd4da;

  /* Accent and status. --pr is the old name for --positive; a theme that
     sets --pr still reaches --positive, and new themes set --positive. */
  --accent: #2c5282;
  --accent-hover: #1f3d63;
  --on-accent: #ffffff;
  --warn: #b86e1c;
  --danger: #b42318;
  --danger-hover: #8f1c13;
  --pr: #2e7d4f;
  --positive: var(--pr);          /* done, shipped, healthy */

  /* Lighting. --light is the colour of the light falling on a raised control
     and --shade the colour of its shadow. --lit sets how strongly the light
     shows and --depth how strongly the shadow does. A light theme wants a
     strong highlight and a soft shadow; a dark theme wants the reverse. */
  --light: #ffffff;
  --shade: #0e1621;
  --lit: 90%;
  --depth: 18%;

  /* Topbar. It keeps its own colours so that it can stay dark on a light
     page, as the default palette does. */
  --tb-bg: #262b31;
  --tb-fg: #f3f4f6;

  /* Syntax. The colours of highlighted code, in a code block or an
     editor, so every highlighter takes them from the theme. Each reaches
     4.5:1 on --surface, --surface-alt and --input-bg; a theme that sets
     them should keep that. */
  --syntax-keyword: #9b2c63;
  --syntax-string: #1d6a39;
  --syntax-number: #8a4a00;
  --syntax-comment: #57606b;
  --syntax-name: #1d4f91;
  --syntax-tag: #6b36a3;
  --syntax-attr: #6d5500;
  --syntax-heading: #1b1f24;
  --syntax-link: #2c5282;
  --syntax-error: #b42318;

  /* Fonts. A theme may change these within the roles the README sets out.
     Inter ships with PUDL in fonts/, so it does not depend on what the
     reader has installed; the system face stands in until it loads. */
  --font: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --font-display: var(--font);
  --mono: ui-monospace, "Cascadia Code", "SF Mono", Consolas, monospace;

  /* === Fixed by the language ================================================
     A theme does not change these. */

  /* The type scale. Every size PUDL uses is one of these. */
  --text-2xs: 11px;      /* section labels, meta lines, badges */
  --text-xs: 12px;       /* chips, small buttons, dock tabs */
  --text-sm: 13px;       /* labels, tabs, list rows, secondary text */
  --text-md: 14px;       /* controls, table values, dialog text */
  --text-base: 15px;     /* body text */
  --text-lg: 17px;       /* card and dialog titles, the brand, h4 */
  --text-xl: 20px;       /* h3 */
  --text-2xl: 24px;      /* h2 */
  --text-3xl: 30px;      /* h1 */

  /* The spacing grid, on a 4px base. Layout spacing, between and around
     components, comes from these. A control's own padding is part of the
     control's metrics and is fixed with the control. */
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 24px;
  --space-6: 32px;

  --radius: 12px;        /* cards, panels, windows */
  --radius-sm: 7px;      /* buttons, entries, tabs */
  --radius-pill: 999px;

  /* Glyphs. Every glyph PUDL draws in its chrome is one of these SVG masks,
     filled with the element's own colour, so it looks the same on every
     platform, can never turn into a colour emoji, and follows the theme. */
  --glyph-search: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Ccircle cx='6.5' cy='6.5' r='4.75' fill='none' stroke='black' stroke-width='2'/%3E%3Cpath d='M10.2 10.2L14.5 14.5' stroke='black' stroke-width='2.2' stroke-linecap='round'/%3E%3C/svg%3E");
  --glyph-caret: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M3.5 5.5h9L8 11z'/%3E%3C/svg%3E");
  --glyph-back: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M10 3L5 8l5 5' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  --glyph-triangle: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M8 2l7 12H1z'/%3E%3C/svg%3E");
  --glyph-square: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Crect x='3' y='3' width='10' height='10' rx='1'/%3E%3C/svg%3E");
  --glyph-circle: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Ccircle cx='8' cy='8' r='5'/%3E%3C/svg%3E");
  --glyph-ring: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Ccircle cx='8' cy='8' r='4.2' fill='none' stroke='black' stroke-width='1.6'/%3E%3C/svg%3E");
  --glyph-diamond: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M8 2l6 6-6 6-6-6z'/%3E%3C/svg%3E");
  --glyph-warning: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M8 1.8l6.6 12H1.4z' fill='none' stroke='black' stroke-width='1.6' stroke-linejoin='round'/%3E%3Cpath d='M8 6.2v3.6' stroke='black' stroke-width='1.8' stroke-linecap='round'/%3E%3Ccircle cx='8' cy='11.9' r='1'/%3E%3C/svg%3E");
  --glyph-open: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M6 3.5h6.5V10M12.5 3.5L4 12' fill='none' stroke='black' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  --glyph-minimize: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M3.5 8.5h9' stroke='black' stroke-width='2' stroke-linecap='round'/%3E%3C/svg%3E");
  --glyph-maximize: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Crect x='3.2' y='3.2' width='9.6' height='9.6' rx='1.5' fill='none' stroke='black' stroke-width='1.8'/%3E%3C/svg%3E");
  --glyph-restore: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Crect x='2.5' y='5.5' width='8' height='8' rx='1.2' fill='none' stroke='black' stroke-width='1.6'/%3E%3Cpath d='M5.5 5.5V3.6a1.1 1.1 0 0 1 1.1-1.1h5.8a1.1 1.1 0 0 1 1.1 1.1v5.8a1.1 1.1 0 0 1-1.1 1.1h-1.9' fill='none' stroke='black' stroke-width='1.6'/%3E%3C/svg%3E");
  --glyph-close: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M4.2 4.2l7.6 7.6M11.8 4.2l-7.6 7.6' stroke='black' stroke-width='2' stroke-linecap='round'/%3E%3C/svg%3E");
  --glyph-branch: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M5 1.5V9h8' fill='none' stroke='black' stroke-width='1.4'/%3E%3C/svg%3E");
  --glyph-sort: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M4.5 6.5L8 3l3.5 3.5zM4.5 9.5L8 13l3.5-3.5z'/%3E%3C/svg%3E");
  --glyph-sort-up: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M3.5 10L8 4.5l4.5 5.5z'/%3E%3C/svg%3E");
  --glyph-sort-down: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M3.5 6L8 11.5 12.5 6z'/%3E%3C/svg%3E");
  --glyph-info: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Ccircle cx='8' cy='8' r='6.2' fill='none' stroke='black' stroke-width='1.6'/%3E%3Ccircle cx='8' cy='5' r='1'/%3E%3Cpath d='M8 7.4v4.2' stroke='black' stroke-width='1.8' stroke-linecap='round'/%3E%3C/svg%3E");
  --glyph-check: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Ccircle cx='8' cy='8' r='6.2' fill='none' stroke='black' stroke-width='1.6'/%3E%3Cpath d='M5.2 8.2l2 2 3.8-4' fill='none' stroke='black' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  --glyph-empty: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M1.8 9.2l2-5.4h8.4l2 5.4v3.6a.8.8 0 0 1-.8.8H2.6a.8.8 0 0 1-.8-.8z' fill='none' stroke='black' stroke-width='1.3' stroke-linejoin='round'/%3E%3Cpath d='M1.8 9.2h3.6l.9 1.6h3.4l.9-1.6h3.6' fill='none' stroke='black' stroke-width='1.3' stroke-linejoin='round'/%3E%3C/svg%3E");
  /* Kinds of thing a list may hold, for file managers, document lists and
     attachments. The shape says the kind, so these carry no colour of
     their own. */
  --glyph-folder: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.4' stroke-linejoin='round'%3E%3Cpath d='M1.5 4.5a1 1 0 0 1 1-1h3.6l1.4 1.5h5.9a1 1 0 0 1 1 1v6.5a1 1 0 0 1-1 1h-10.9a1 1 0 0 1-1-1z'/%3E%3C/svg%3E");
  --glyph-file: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.4' stroke-linejoin='round'%3E%3Cpath d='M4 1.5h5.5l3 3v9.5a.5.5 0 0 1-.5.5H4a.5.5 0 0 1-.5-.5V2a.5.5 0 0 1 .5-.5z'/%3E%3Cpath d='M9.5 1.5v3h3'/%3E%3C/svg%3E");
  --glyph-document: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.4' stroke-linejoin='round' stroke-linecap='round'%3E%3Cpath d='M4 1.5h5.5l3 3v9.5a.5.5 0 0 1-.5.5H4a.5.5 0 0 1-.5-.5V2a.5.5 0 0 1 .5-.5z'/%3E%3Cpath d='M9.5 1.5v3h3M5.8 8h4.4M5.8 10.5h4.4M5.8 5.5h2'/%3E%3C/svg%3E");
  --glyph-app: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.4'%3E%3Crect x='2' y='2' width='5' height='5' rx='1'/%3E%3Crect x='9' y='2' width='5' height='5' rx='1'/%3E%3Crect x='2' y='9' width='5' height='5' rx='1'/%3E%3Crect x='9' y='9' width='5' height='5' rx='1'/%3E%3C/svg%3E");
  --glyph-script: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='1.5' y='2.5' width='13' height='11' rx='1.5'/%3E%3Cpath d='M4.5 6.5l2 1.75-2 1.75'/%3E%3Cpath d='M8 11h3.5'/%3E%3C/svg%3E");
  --glyph-link: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M7 3H3.5a1 1 0 0 0-1 1v8.5a1 1 0 0 0 1 1H12a1 1 0 0 0 1-1V9'/%3E%3Cpath d='M9.5 2.5h4v4M13.5 2.5l-6 6'/%3E%3C/svg%3E");
  --glyph-home: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M2 7.5l6-5 6 5'/%3E%3Cpath d='M3.5 6.5v7h3.5v-4h2v4h3.5v-7'/%3E%3C/svg%3E");
  /* The separator between the places in a path bar. */
  --glyph-slash: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M10.5 2.5l-5 11' stroke='black' stroke-width='1.4' stroke-linecap='round'/%3E%3C/svg%3E");
  --glyph-stop: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M5.4 1.8h5.2l3.6 3.6v5.2l-3.6 3.6H5.4l-3.6-3.6V5.4z' fill='none' stroke='black' stroke-width='1.6' stroke-linejoin='round'/%3E%3Cpath d='M8 4.8v4' stroke='black' stroke-width='1.8' stroke-linecap='round'/%3E%3Ccircle cx='8' cy='11' r='1'/%3E%3C/svg%3E");

  /* === Derived ==============================================================
     Computed from the palette. A theme should not need to set any of these. */

  /* Inputs are sunken, so they take the surface with only a touch of light. */
  --input-bg: color-mix(in srgb, var(--light) var(--lit), var(--surface));

  /* Raised controls: lit from the top, shading to the recessed tint. */
  --raise-top: color-mix(in srgb, var(--light) var(--lit), var(--surface-alt));
  --raise-grad: linear-gradient(var(--raise-top), var(--surface-alt));
  --raise-grad-hover: linear-gradient(
      color-mix(in srgb, var(--text) 3%, var(--raise-top)),
      color-mix(in srgb, var(--text) 6%, var(--surface-alt)));
  --raise-active-bg: color-mix(in srgb, var(--shade) calc(var(--depth) * 0.45), var(--surface-alt));
  --raise-border: color-mix(in srgb, var(--shade) calc(var(--depth) * 0.9), var(--surface-alt));
  --raise-border-hover: color-mix(in srgb, var(--shade) calc(var(--depth) * 1.6), var(--surface-alt));
  --raise-shadow:
      inset 0 1px color-mix(in srgb, var(--light) var(--lit), transparent),
      0 1px 2px color-mix(in srgb, var(--shade) var(--depth), transparent);
  --raise-active-shadow: inset 0 1px 3px color-mix(in srgb, var(--shade) calc(var(--depth) * 1.2), transparent);
  --entry-shadow: inset 0 1px 2px color-mix(in srgb, var(--shade) calc(var(--depth) * 0.7), transparent);

  /* A recessed band, such as the tab bar or a switch's track: darker than
     the surface around it and shaded from above. */
  --recess-bg: color-mix(in srgb, var(--shade) calc(var(--depth) * 0.35), var(--surface-alt));
  --recess-shadow: inset 0 2px 3px color-mix(in srgb, var(--shade) calc(var(--depth) * 0.6), transparent);

  --shadow-btn-primary:
      inset 0 1px rgb(255 255 255 / 0.25),
      0 1px 2px color-mix(in srgb, var(--shade) calc(var(--depth) * 1.2), transparent);
  --shadow-card:
      0 1px 3px color-mix(in srgb, var(--shade) calc(var(--depth) * 0.6), transparent),
      0 0 0 1px color-mix(in srgb, var(--shade) calc(var(--depth) * 0.25), transparent);

  /* Both rings derive from their colour tokens, so a theme's accent reaches
     the focus ring with no further work. */
  --focus-ring: color-mix(in srgb, var(--accent) 26%, transparent);
  --focus-ring-danger: color-mix(in srgb, var(--danger) 26%, transparent);

  /* The dialog panel is always one step brighter than the page, so it reads
     as a card lifted off it. On a light page that is --surface. */
  --dialog-bg: var(--surface);

  /* The current section tab opens into what lies below the tab bar, so it
     takes that colour. Under a master-detail layout's toolbar that is
     --surface, the default; a page whose content sits on --bg sets this to
     var(--bg) on the bar or above it. */
  --section-current-bg: var(--surface);

  /* Topbar chrome. */
  --tb-grad: linear-gradient(color-mix(in srgb, var(--tb-fg) 8%, var(--tb-bg)), var(--tb-bg));
  --tb-border: color-mix(in srgb, #000 40%, var(--tb-bg));
  --tb-hi: color-mix(in srgb, var(--tb-fg) 12%, transparent);
  --tb-brand: var(--tb-fg);
  --tb-chrome-fg: color-mix(in srgb, var(--tb-fg) 80%, var(--tb-bg));
  --tb-chrome-hover-fg: var(--tb-fg);
  /* Links on the topbar, the brand among them, hover in the accent mixed
     into the topbar's own foreground, which reads on a dark bar and on a
     light one alike. A theme may set it. */
  --tb-link-hover: color-mix(in srgb, var(--accent) 45%, var(--tb-fg));
  --tb-chip: linear-gradient(
      color-mix(in srgb, var(--tb-fg) 19%, transparent),
      color-mix(in srgb, var(--tb-fg) 10%, transparent));
  --tb-chip-shadow:
      inset 0 1px color-mix(in srgb, var(--tb-fg) 22%, transparent),
      0 1px 2px color-mix(in srgb, #000 35%, transparent);
}

[data-theme="dark"] {
  /* === The dark palette ==================================================== */
  --bg: #121417;
  --surface: #1a1d21;
  --surface-alt: #23272d;
  --text: #e6e8eb;
  --text-muted: #9aa3ad;
  --border: #353b43;

  --accent: #6b9bd1;
  --accent-hover: #8db3e0;
  --on-accent: #121417;
  --warn: #d9954a;
  --danger: #e06156;
  --danger-hover: #f0857b;
  --pr: #5fb37f;
  --positive: var(--pr);

  --light: #e8eef6;
  --shade: #000000;
  --lit: 7%;
  --depth: 48%;

  --tb-bg: #262b31;
  --tb-fg: #e6e8eb;

  --syntax-keyword: #f08dc0;
  --syntax-string: #85cf9b;
  --syntax-number: #efad6b;
  --syntax-comment: #9aa3ad;
  --syntax-name: #8fbcef;
  --syntax-tag: #c6a3f2;
  --syntax-attr: #e2c275;
  --syntax-heading: #e6e8eb;
  --syntax-link: #8db3e0;
  --syntax-error: #f0857b;

  /* On a dark page --surface is barely lighter than --bg, so the dialog
     takes the next step up to keep reading as lifted. */
  --dialog-bg: var(--surface-alt);
}

:root[data-theme="dark"] { color-scheme: dark; }
:root[data-theme="light"] { color-scheme: light; }

* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }

/* The hidden attribute always hides, even on a component that sets its own
   display, which would otherwise beat the browser's rule. hidden="until-found"
   is left to the browser, which hides it by other means so that in-page
   search can still reveal it. */
[hidden]:not([hidden="until-found"]) { display: none !important; }
body {
  font-family: var(--font);
  background: var(--bg);
  color: var(--text);
  font-size: var(--text-base);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

/* Headings, from the type scale. :where() gives these no weight at all, so
   any class a component or project puts on a heading wins over them. */
:where(h1, h2, h3, h4) {
  font-family: var(--font-display); font-weight: 700;
  line-height: 1.25; letter-spacing: -0.01em;
  margin: 0 0 var(--space-3);
}
:where(h1) { font-size: var(--text-3xl); }
:where(h2) { font-size: var(--text-2xl); }
:where(h3) { font-size: var(--text-xl); }
:where(h4) { font-size: var(--text-lg); }

/* Tabular numerals wherever numbers are data, so identifiers, counts, money
   and timestamps line up. Running prose keeps proportional figures, because
   Inter's tabular feature also widens the hyphen, which spaces out every
   hyphenated word. <time>, <data> and .num mark data inside prose. */
table, input, select, textarea, time, data, output, code, kbd, samp, .num,
.kv-table, .form-input, .badge, .chip, .filter-chip, .seg, .topbar-pill,
.md-item, .md-row {
  font-variant-numeric: tabular-nums;
}

/* === Glyphs ==============================================================
   Every chrome glyph is a pseudo-element drawn by this one rule: a square
   the size of the text, filled with the element's colour through an SVG
   mask named by --glyph. Each component below sets which glyph, and its
   size where the text's own size is not right. */
.badge::before, .form-error::before, .menu-btn::after, .md-back::before,
.notice::before, .toast::before, .data-table th[aria-sort] > :is(a, button)::after,
.notice-close::before, .toast-close::before, .empty-state::before,
.page-link[rel="prev"]::before, .page-link[rel="next"]::after {
  content: ""; flex: none;
  width: var(--glyph-size, 1em); height: var(--glyph-size, 1em);
  background: currentColor;
  -webkit-mask: var(--glyph) center / contain no-repeat;
  mask: var(--glyph) center / contain no-repeat;
}

/* === Topbar / brand chrome ============================================== */
.topbar {
  display: flex; align-items: center; justify-content: space-between;
  padding: 10px 20px;
  background: var(--tb-grad);
  border-bottom: 1px solid var(--tb-border);
  box-shadow: inset 0 1px var(--tb-hi);
}
.brand {
  display: inline-flex; align-items: center; gap: var(--space-2);
  font-family: var(--font-display);
  font-size: var(--text-lg); font-weight: 700; letter-spacing: -0.01em;
  color: var(--tb-brand); text-decoration: none;
}
/* The page's link colours are made for the page, not for the topbar, so a
   plain link on the topbar takes the topbar's colours instead, and the
   brand, like it, hovers in --tb-link-hover. */
.topbar a:not([class]) { color: var(--tb-chrome-fg); }
.brand:hover, .topbar a:not([class]):hover { color: var(--tb-link-hover); }
.topbar-chrome {
  display: inline-flex; align-items: center; gap: var(--space-2);
}
/* A topbar pill is raised, so it is always a link or a button, never a label. */
.topbar-pill {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 5px 12px; border-radius: var(--radius-sm);
  font-size: var(--text-sm); font-weight: 600;
  color: var(--tb-chrome-fg);
  background: var(--tb-chip); box-shadow: var(--tb-chip-shadow);
  text-decoration: none;
}
.topbar-pill:hover { color: var(--tb-chrome-hover-fg); }
/* The theme toggle shares the chip styling on the topbar chrome. */
.theme-toggle {
  display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 28px;
  padding: 0; border-radius: var(--radius-sm);
  font-size: var(--text-md); line-height: 1; font-family: var(--font);
  color: var(--tb-chrome-fg);
  background: var(--tb-chip); box-shadow: var(--tb-chip-shadow);
  border: 0; cursor: pointer;
}
.theme-toggle:hover { color: var(--tb-chrome-hover-fg); }

/* === Section tabs: a notebook ============================================
   After the notebook widget of desktop toolkits. The bar is a recessed
   band. Each tab the reader can go to is a raised tab standing on the
   band's baseline, because it can be pressed. The tab for where the reader
   is, marked aria-current, is flat and lit, a little taller, and open at
   the bottom into the content below, so the page visibly hangs from it.

   The bar's baseline is drawn as part of the bar's own background, and the
   current tab paints over it from inside the bar. Nothing reaches past the
   bar's edge, which matters because the bar scrolls sideways on a narrow
   screen, and a box that scrolls clips whatever reaches past its edges.

   The bar is always as tall as it is with a current tab. Its top padding
   holds the 4px the current tab stands above the others, and the current
   tab rises into it with a negative margin, so a page with no current tab
   has a bar of the same height and the content below does not jump. */
.app-section-bar, .tablist {
  display: flex; flex: none; align-items: flex-end; gap: var(--space-1);
  padding: 10px var(--space-4) 0;
  overflow-x: auto;
  background: var(--recess-bg);
  box-shadow: inset 0 -1px var(--border), var(--recess-shadow);
}
.section-tab, .tablist > [role="tab"] {
  padding: 5px 14px 4px;
  font: 600 var(--text-sm) var(--font);
  color: var(--text); text-decoration: none;
  white-space: nowrap; cursor: pointer;
  background: var(--raise-grad);
  border: 1px solid var(--raise-border); border-bottom: 0;
  border-radius: var(--radius-sm) var(--radius-sm) 0 0;
  box-shadow: inset 0 1px color-mix(in srgb, var(--light) var(--lit), transparent);
}
.section-tab:hover, .tablist > [role="tab"]:hover { background: var(--raise-grad-hover); border-color: var(--raise-border-hover); }
.section-tab:active, .tablist > [role="tab"]:active { background: var(--raise-active-bg); }
.section-tab:focus-visible, .tablist > [role="tab"]:focus-visible { outline: none; box-shadow: 0 0 0 3px var(--focus-ring); }
.section-tab[aria-current]:not([aria-current="false"]),
.section-tab.active,
.tablist > [role="tab"][aria-selected="true"] {
  padding: 7px 14px 6px;
  margin-top: -4px;
  color: var(--text);
  background: var(--section-current-bg);
  border-color: var(--border);
  box-shadow: none;
}
.section-tab[aria-current]:not([aria-current="false"]):focus-visible,
.section-tab.active:focus-visible,
.tablist > [role="tab"][aria-selected="true"]:focus-visible { box-shadow: 0 0 0 3px var(--focus-ring); }

/* === Tabs within a page ==================================================
   The ARIA tab pattern: a .tablist of role="tab" buttons over role="tabpanel"
   panels, inside a .tabs wrapper. They look like section tabs, since to the
   reader both are tabs; the difference is that section tabs go to other
   addresses and these switch panels in place. pudl-tabs.js shows the chosen
   panel, moves between tabs with the arrow keys, and keeps the chosen
   panel in the address's fragment, so it can be linked to and survives a
   reload. Without the script the tab list is hidden and every panel shows,
   one after another. */
.tabs { --section-current-bg: var(--surface); }
.tabs:not(.tabs-ready) > .tablist { display: none; }
.tabs > [role="tabpanel"] {
  padding: var(--space-4);
  background: var(--section-current-bg);
  border: 1px solid var(--border); border-top: 0;
}
.tabs:not(.tabs-ready) > [role="tabpanel"] { border-top: 1px solid var(--border); margin-bottom: var(--space-3); }
.tabs > [role="tabpanel"]:focus-visible { outline: none; box-shadow: inset 0 0 0 3px var(--focus-ring); }

/* === Document tabs =======================================================
   Tabs that come and go, one per open document, record or view, as in an
   editor: a .tablist.doc-tabs, so they look like every other tab. A tab
   may carry a .doc-tab-dirty mark, a dot whose words, "unsaved" say, are
   heard by assistive technology and hidden from the eye, and a
   .doc-tab-close, a small raised button drawn inside the tab and hidden
   from assistive technology, since a tab list may hold only tabs; the
   Delete key closes the focused tab for the keyboard. pudl-tabs.js turns
   both into pudl:tab-close, and the host decides. */
.doc-tabs > [role="tab"] { display: inline-flex; align-items: center; gap: 6px; }
.doc-tabs > [role="tab"]:has(.doc-tab-close) { padding-inline-end: 6px; }
.doc-tab-dirty {
  flex: none; width: 10px; height: 10px; overflow: hidden;
  font-size: 0; color: transparent;
  background: var(--accent);
  -webkit-mask: var(--glyph-circle) center / contain no-repeat;
  mask: var(--glyph-circle) center / contain no-repeat;
}
.doc-tab-close {
  flex: none; display: inline-flex; align-items: center; justify-content: center;
  width: 18px; height: 18px; border-radius: 50%;
  border: 1px solid var(--raise-border); background: var(--raise-grad); box-shadow: var(--raise-shadow);
  color: var(--text-muted); cursor: pointer;
}
.doc-tab-close::before {
  content: ""; width: 9px; height: 9px; background: currentColor;
  -webkit-mask: var(--glyph-close) center / contain no-repeat;
  mask: var(--glyph-close) center / contain no-repeat;
}
.doc-tab-close:hover { color: var(--danger); background: var(--raise-grad-hover); border-color: var(--raise-border-hover); }

/* A region that pudl-regions.js is replacing dims a little while the new
   content is on its way, after a moment, so a fast answer never flickers. */
[data-region][aria-busy="true"] { opacity: .6; transition: opacity .15s .12s; cursor: progress; }

/* === Buttons (raised; one look) ========================================= */
/* Every button is raised. There is no flat. Disabled is dimmed.
   At most one .btn-primary per visible context. */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  padding: 7px 14px; border-radius: var(--radius-sm);
  font-size: var(--text-md); font-weight: 600; font-family: var(--font);
  color: var(--text); cursor: pointer; text-decoration: none;
  white-space: nowrap;
  border: 1px solid var(--raise-border);
  background: var(--raise-grad);
  box-shadow: var(--raise-shadow);
  transition: background .1s, border-color .1s, box-shadow .1s;
}
.btn:hover {
  background: var(--raise-grad-hover);
  border-color: var(--raise-border-hover);
  text-decoration: none;
}
.btn:active {
  background: var(--raise-active-bg);
  box-shadow: var(--raise-active-shadow);
}
.btn:focus-visible {
  outline: none;
  box-shadow: var(--raise-shadow), 0 0 0 3px var(--focus-ring);
}
/* A toggle button latched on stays pressed in. aria-pressed carries the
   state to assistive technology as well as to the eye, and the state shows
   through elevation, not colour. A project flips the attribute. */
.btn[aria-pressed="true"],
.btn[aria-pressed="true"]:hover {
  background: var(--raise-active-bg);
  border-color: var(--raise-border-hover);
  box-shadow: var(--raise-active-shadow);
}
.btn[aria-pressed="true"]:focus-visible {
  box-shadow: var(--raise-active-shadow), 0 0 0 3px var(--focus-ring);
}
.btn[disabled], .btn.is-disabled {
  opacity: .45; cursor: not-allowed;
  box-shadow: var(--raise-shadow);
  background: var(--raise-grad);
}
/* A link styled as a button cannot be disabled, so it is marked
   aria-disabled, and it then looks disabled and takes no clicks. */
.btn[aria-disabled="true"] {
  opacity: .45; pointer-events: none;
  box-shadow: var(--raise-shadow);
  background: var(--raise-grad);
}
.btn-sm { padding: 4px 10px; font-size: var(--text-xs); }
.btn-primary {
  color: var(--on-accent);
  border-color: color-mix(in srgb, var(--accent) 62%, #000);
  background: linear-gradient(color-mix(in srgb, var(--accent) 78%, #fff), var(--accent));
  box-shadow: var(--shadow-btn-primary);
}
.btn-primary:hover {
  color: var(--on-accent);
  background: linear-gradient(color-mix(in srgb, var(--accent) 88%, #fff), color-mix(in srgb, var(--accent) 94%, #000));
}
.btn-primary:active {
  background: color-mix(in srgb, var(--accent) 86%, #000);
  box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.32);
}
/* A primary button latched on keeps its accent and its text, and is
   pressed in: the flat accent without the lit gradient, shaded from the
   top. It is not darkened, because the dark theme's accent carries dark
   text, which a darker fill would swallow; the change of elevation is
   what says it is on, as for any latched button. */
.btn-primary[aria-pressed="true"],
.btn-primary[aria-pressed="true"]:hover {
  color: var(--on-accent);
  background: var(--accent);
  border-color: color-mix(in srgb, var(--accent) 62%, #000);
  box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.38);
}
.btn-primary[aria-pressed="true"]:focus-visible {
  box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.38), 0 0 0 3px var(--focus-ring);
}
.btn-danger {
  color: var(--danger); border-color: color-mix(in srgb, var(--danger) 35%, var(--raise-border));
}
.btn-danger:hover {
  color: var(--on-accent);
  background: linear-gradient(color-mix(in srgb, var(--danger) 78%, #fff), var(--danger));
  border-color: color-mix(in srgb, var(--danger) 62%, #000);
}

/* Icon button: raised square for row actions (drag, remove, archive). */
.icon-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 28px; border-radius: var(--radius-sm);
  background: var(--raise-grad); border: 1px solid var(--raise-border);
  box-shadow: var(--raise-shadow);
  color: var(--text-muted); cursor: pointer;
}
.icon-btn:hover { color: var(--text); background: var(--raise-grad-hover); border-color: var(--raise-border-hover); }
.icon-btn:focus-visible { outline: none; box-shadow: var(--raise-shadow), 0 0 0 3px var(--focus-ring); }
.icon-btn.danger:hover { color: var(--danger); }

/* === Links ============================================================== */
a, .link {
  color: var(--accent); text-decoration: underline;
  text-decoration-thickness: 1px; text-underline-offset: 2px;
}
a:hover, .link:hover {
  color: var(--accent-hover);
  text-decoration-thickness: 2px;
}
.link-muted {
  color: var(--text-muted); text-decoration: underline;
  text-decoration-thickness: 1px; text-underline-offset: 2px;
}
.link-muted:hover { color: var(--text); }
button.link {
  border: 0; background: none; padding: 0; font: inherit; cursor: pointer;
  color: var(--accent); text-decoration: underline;
}

/* === Forms ============================================================== */
.form-group { display: flex; flex-direction: column; gap: 6px; margin-bottom: var(--space-4); }
.form-label { font-size: var(--text-sm); font-weight: 600; color: var(--text); }
.form-input, .form-select, .form-textarea {
  padding: 8px 11px; font-size: var(--text-md); font-family: var(--font);
  background: var(--input-bg); color: var(--text);
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  width: 100%;
  box-shadow: var(--entry-shadow);
}
/* A one-line field is one height whatever its type, because browsers draw
   a select or a date field a little taller than a text field, and fields
   side by side in a form should line up. A select with multiple or size
   shows a list, so it keeps its own height. */
.form-input, .form-select:not([multiple], [size]) { height: 36px; line-height: 1.25; }
.form-textarea { min-height: 5em; resize: vertical; line-height: 1.5; }
.form-input:focus, .form-select:focus, .form-textarea:focus {
  border-color: var(--accent); outline: none;
  box-shadow: var(--entry-shadow), 0 0 0 3px var(--focus-ring);
}
/* Read-only fields render as flat display, not input chrome. */
.form-input[readonly], .form-select[disabled] {
  background: transparent; border-color: transparent; box-shadow: none;
  padding-inline: 0; cursor: default;
}
::placeholder { color: var(--text-muted); opacity: .5; }

/* Checkbox + label as one inline control. */
.check {
  display: inline-flex; align-items: center; gap: var(--space-2);
  font-size: var(--text-md); cursor: pointer;
}
.check input { width: 16px; height: 16px; margin: 0; accent-color: var(--accent); }

/* Toggle switch (a boolean preference). The track is sunken, since it is
   the slot the thumb runs in, and the thumb is raised, since it is what the
   reader presses. When the switch is on the track fills with the accent
   and the thumb slides to the far end, so position says it as well as
   colour. The switch is a button with role="switch", which is what lets
   it carry aria-checked. */
.switch {
  display: inline-flex; align-items: center; gap: 10px;
  background: none; border: 0; padding: 0; margin: 0;
  font: inherit; color: var(--text); cursor: pointer;
}
.switch-track {
  position: relative; flex: none;
  width: 40px; height: 22px; border-radius: var(--radius-pill);
  background: var(--recess-bg); border: 1px solid var(--border);
  box-shadow: var(--entry-shadow);
  transition: background .15s, border-color .15s;
}
.switch-thumb {
  position: absolute; top: 2px; inset-inline-start: 2px;
  width: 16px; height: 16px; border-radius: 50%;
  background: var(--raise-grad);
  border: 1px solid var(--raise-border);
  box-shadow: var(--raise-shadow);
  transition: transform .15s;
}
.switch[aria-checked="true"] .switch-track {
  background: var(--accent); border-color: var(--accent-hover);
}
.switch[aria-checked="true"] .switch-thumb { transform: translateX(18px); }
.switch:dir(rtl)[aria-checked="true"] .switch-thumb { transform: translateX(-18px); }
.switch:hover .switch-thumb { border-color: var(--raise-border-hover); }
.switch:focus-visible { outline: none; }
.switch:focus-visible .switch-track {
  box-shadow: var(--entry-shadow), 0 0 0 3px var(--focus-ring);
}

.form-error {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: var(--text-sm); color: var(--danger); margin: var(--space-1) 0 var(--space-3);
}
.form-error::before { --glyph: var(--glyph-warning); }

/* A field whose value the server refused is marked aria-invalid="true",
   with its .form-error tied to it by aria-describedby. It takes a danger
   border and a danger focus ring, and the message beside it carries the
   glyph, so the state never rests on the border's colour alone. */
.form-input[aria-invalid="true"], .form-select[aria-invalid="true"], .form-textarea[aria-invalid="true"] {
  border-color: var(--danger);
}
.form-input[aria-invalid="true"]:focus, .form-select[aria-invalid="true"]:focus, .form-textarea[aria-invalid="true"]:focus {
  border-color: var(--danger);
  box-shadow: var(--entry-shadow), 0 0 0 3px var(--focus-ring-danger);
}
.form-input:disabled, .form-select:disabled, .form-textarea:disabled { opacity: .55; cursor: not-allowed; }

/* A required field's label carries an asterisk, drawn by the stylesheet
   from the field's own required attribute and hidden from assistive
   technology, which hears "required" from the field itself. */
.form-group:has(:required) > .form-label::after {
  content: "*" / "";
  margin-inline-start: 3px; color: var(--danger);
}
.form-help { margin: 0; font-size: var(--text-sm); color: var(--text-muted); }
.form-group > .form-error { margin: 0; }

.form-fieldset {
  margin: 0 0 var(--space-4); padding: var(--space-3) var(--space-4) var(--space-4);
  border: 1px solid var(--border); border-radius: var(--radius-sm);
}
.form-fieldset > legend { padding: 0 var(--space-1); font-size: var(--text-sm); font-weight: 600; }
.form-options { display: flex; flex-direction: column; gap: var(--space-2); }
.form-options.inline { flex-direction: row; flex-wrap: wrap; gap: var(--space-2) var(--space-5); }

/* A file field: its button is a raised button, the file name flat text. */
.form-file { font: inherit; font-size: var(--text-sm); color: var(--text-muted); max-width: 100%; }
.form-file::file-selector-button {
  margin-inline-end: var(--space-3);
  padding: 6px 12px; border-radius: var(--radius-sm);
  font: 600 var(--text-sm) var(--font); color: var(--text);
  border: 1px solid var(--raise-border);
  background: var(--raise-grad); box-shadow: var(--raise-shadow);
  cursor: pointer;
}
.form-file::file-selector-button:hover { background: var(--raise-grad-hover); border-color: var(--raise-border-hover); }

/* === Segmented control / view switcher ================================== */
/* An inset trough of choices, the chosen one raised in it. Use it for two
   to four options: a small scale, kg|lbs, Read|Edit. Its items are buttons,
   marked aria-pressed or aria-checked, or links to views, marked
   aria-current. */
.seg {
  display: inline-flex; padding: 3px; gap: 2px;
  background: var(--recess-bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  box-shadow: var(--entry-shadow);
}
/* The choices not taken are in the full text colour: the muted colour does
   not reach readable contrast on the recessed trough, and the chosen one
   is already marked by being raised, which is how PUDL shows state. */
.seg button, .seg a {
  display: inline-flex; align-items: center;
  padding: 4px 12px; border-radius: 4px;
  background: transparent; border: 1px solid transparent; cursor: pointer;
  font: 600 var(--text-sm) var(--font); color: var(--text);
  text-decoration: none;
}
.seg button:hover, .seg a:hover { background: color-mix(in srgb, var(--text) 7%, transparent); }
.seg button:focus-visible, .seg a:focus-visible { outline: none; box-shadow: 0 0 0 3px var(--focus-ring); }
.seg button[aria-pressed="true"], .seg button[aria-checked="true"],
.seg [aria-current]:not([aria-current="false"]),
.seg button.active {
  color: var(--text);
  background: var(--raise-grad);
  border-color: var(--raise-border);
  box-shadow: var(--raise-shadow);
}
.seg.pill { border-radius: var(--radius-pill); }
.seg.pill button, .seg.pill a { border-radius: var(--radius-pill); }
.seg.sm button, .seg.sm a { padding: 2px 9px; font-size: var(--text-xs); }

/* === Badges, chips and filter chips =====================================
   Three small labels, each looking like what it is:
     - a badge states an entity's state, and always leads with its glyph;
     - a chip names an attribute the thing carries, a tag or a category,
       and is a plain neutral label;
     - a filter chip is a filter in force, outlined, with a raised × that
       removes it. */
.badge {
  display: inline-flex; align-items: center; gap: var(--space-1);
  padding: 1px 8px; border-radius: var(--radius-pill);
  font-size: var(--text-2xs); font-weight: 700; line-height: 1.55;
  background: color-mix(in srgb, var(--text-muted) 18%, transparent);
  color: var(--text);
  --glyph-size: 9px;
}
/* A status badge's text is its status colour drawn toward the text colour,
   because a mid-tone status colour cannot reach 4.5:1 on a tint of itself,
   least of all in a selected table row, whose tint darkens it further.
   Mixing in the text colour darkens the text on a light theme and lightens
   it on a dark one, keeps the hue, and holds for a theme's own status
   colours, so the tokens themselves stay as the theme set them. */
.badge.warn { --badge-color: var(--warn); background: color-mix(in srgb, var(--warn) 22%, transparent); }
.badge.danger { --badge-color: var(--danger); background: color-mix(in srgb, var(--danger) 18%, transparent); }
.badge.positive, .badge.pr { --badge-color: var(--positive); background: color-mix(in srgb, var(--positive) 22%, transparent); }
.badge.accent { --badge-color: var(--accent); background: color-mix(in srgb, var(--accent) 18%, transparent); }
.badge:is(.warn, .danger, .positive, .pr, .accent) { color: color-mix(in srgb, var(--badge-color) 62%, var(--text)); }
.badge::before { content: none; }
.badge.warn::before { content: ""; --glyph: var(--glyph-triangle); }
.badge.danger::before { content: ""; --glyph: var(--glyph-square); }
.badge.positive::before, .badge.pr::before { content: ""; --glyph: var(--glyph-circle); }
.badge.accent::before { content: ""; --glyph: var(--glyph-diamond); }

.chip {
  display: inline-flex; align-items: center; gap: var(--space-1);
  padding: 1px 8px; border-radius: var(--radius-sm);
  font-size: var(--text-xs); font-weight: 600;
  background: color-mix(in srgb, var(--text) 8%, transparent);
  color: var(--text);
}

/* === Cards ============================================================== */
.card {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); padding: var(--space-4);
  box-shadow: var(--shadow-card);
}
.card-title { margin: 0 0 6px; font-size: var(--text-lg); font-weight: 700; }
.card-subtitle { margin: 0 0 6px; font-size: var(--text-sm); font-weight: 600; color: var(--text-muted); }
.card-desc { margin: 0 0 var(--space-3); font-size: var(--text-sm); color: var(--text-muted); }

/* === Key/value table for read-only record fields ======================== */
/* Detail panes use this for displayed-only fields, not fake input boxes
   (elevation = interactivity). */
.kv-table { width: 100%; border-collapse: collapse; font-size: var(--text-md); }
.kv-table th {
  text-align: start; font-weight: 600; color: var(--text-muted); font-size: var(--text-sm);
  padding-block: var(--space-2); padding-inline: 0 var(--space-4); white-space: nowrap; vertical-align: top; width: 1%;
}
.kv-table td { padding: var(--space-2) 0; color: var(--text); vertical-align: top; overflow-wrap: anywhere; }
.kv-table tr + tr th, .kv-table tr + tr td { border-top: 1px solid var(--border); }

/* === Code ================================================================
   A code block is for reading, so it is flat: a tinted panel with no
   shadow. An editor's surface takes input, so it is sunken like a field.
   Both set text in the monospace face, and highlighted code in either
   takes the --syntax colours, which dist/pudl-hljs.css maps onto
   highlight.js and the README onto CodeMirror. */
pre.code {
  margin: 0 0 var(--space-4); padding: var(--space-3) var(--space-4);
  overflow: auto;
  background: var(--surface-alt); color: var(--text);
  border-radius: var(--radius-sm);
  font: var(--text-sm)/1.55 var(--mono);
  tab-size: 4;
}
.code-surface {
  font-family: var(--mono); font-size: var(--text-sm);
  background: var(--input-bg); color: var(--text);
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  box-shadow: var(--entry-shadow);
}
.code-surface:focus-within { border-color: var(--accent); box-shadow: var(--entry-shadow), 0 0 0 3px var(--focus-ring); }

/* === Dialogs ==============================================================
   A dialog is a native <dialog class="dialog">, rendered by the server and
   shown with showModal(), or with a button carrying command="show-modal"
   and commandfor naming it. The browser then keeps focus inside it, closes
   it on Escape and draws its backdrop. The backdrop dims gently, to 40%,
   because the panel's shadow does the elevation work, and the panel is
   always one step brighter than the page. pudl-dialog.js adds the command
   buttons in browsers that lack them.

   The older markup, a .dialog panel inside a .dialog-backdrop shown by a
   project's own script, still works. */
.dialog {
  width: 100%; max-width: 480px;
  background: var(--dialog-bg, var(--surface));
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow:
      var(--shadow-card),
      0 24px 64px rgba(0, 0, 0, 0.32);
  padding: var(--space-5);
}
dialog.dialog { margin: auto; max-height: calc(100svh - 2 * var(--space-5)); overflow: auto; }
dialog.dialog:not([open]) { display: none; }
dialog.dialog::backdrop { background: color-mix(in srgb, #000 40%, transparent); }
.dialog-backdrop {
  position: fixed; inset: 0; z-index: 100;
  display: flex; align-items: center; justify-content: center; padding: var(--space-5);
  background: color-mix(in srgb, #000 40%, transparent);
}
.dialog-title {
  margin: 0 0 var(--space-2);
  font-size: var(--text-lg); font-weight: 700;
  color: var(--text);
}
.dialog-body {
  margin: 0 0 var(--space-5);
  color: var(--text-muted);
  font-size: var(--text-md); line-height: 1.55;
}
.dialog-actions {
  display: flex; justify-content: flex-end; gap: var(--space-2);
}

/* === Master-detail layout ================================================ */
/* body:has(> .md-layout) is full-viewport flex; the toolbar + chip row +
   body fill the page. Scoped to a DIRECT child so a reference demo or any
   other embedded .md-layout doesn't accidentally kill page scrolling. */
body:has(> .md-layout) {
  height: 100svh; display: flex; flex-direction: column; overflow: hidden;
}
.md-layout {
  display: flex; flex-direction: column; flex: 1; min-height: 0;
}
.md-toolbar {
  display: flex; align-items: center; gap: 10px;
  padding: var(--space-2) var(--space-4); background: var(--surface);
  border-bottom: 1px solid var(--border); flex: none;
  --ctl-h: 30px;
}
.md-toolbar .btn-sm,
.md-toolbar .seg,
.md-toolbar .md-filter {
  box-sizing: border-box; height: var(--ctl-h);
  display: inline-flex; align-items: center;
}
.md-toolbar .md-filter { padding: 4px 12px; }
.md-filter {
  box-sizing: border-box; flex: none; width: 220px;
  padding: 6px 11px; font-size: var(--text-sm);
  background: var(--input-bg); color: var(--text);
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  box-shadow: var(--entry-shadow);
}
.md-filter:focus {
  border-color: var(--accent); outline: none;
  box-shadow: var(--entry-shadow), 0 0 0 3px var(--focus-ring);
}

/* A filter that applies on submission pairs the input with a raised apply
   button, so the action that can be pressed looks pressable. The two are
   drawn joined, the sunken input meeting the raised button. */
.md-filter-group { display: inline-flex; align-items: stretch; margin: 0; }
.md-filter-group .md-filter {
  flex: 1; min-width: 0;
  border-start-end-radius: 0; border-end-end-radius: 0;
}
.md-filter-go {
  flex: none; width: 32px; height: auto;
  margin-inline-start: -1px;
  border-start-start-radius: 0; border-end-start-radius: 0;
  color: var(--text-muted);
}
.md-filter-go::before {
  content: ""; width: 14px; height: 14px;
  background: currentColor;
  -webkit-mask: var(--glyph-search) center / contain no-repeat;
  mask: var(--glyph-search) center / contain no-repeat;
}
.md-filter-go:focus-visible { outline: none; box-shadow: var(--raise-shadow), 0 0 0 3px var(--focus-ring); position: relative; }
.md-toolbar .md-filter-group { height: var(--ctl-h); }
.md-toolbar-spacer { flex: 1; }
.md-toolbar-cmds {
  flex: none; display: flex; align-items: center; gap: 6px;
}
.md-toolbar-sep {
  width: 1px; height: var(--ctl-h);
  background: var(--border); align-self: center; margin: 0 var(--space-1);
}

/* Active-filter chips, below the toolbar. */
.md-chips {
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
  padding: var(--space-2) var(--space-4); background: var(--surface);
  border-bottom: 1px solid var(--border); flex: none;
}
/* A chips row with no chips is hidden. It is tested by what it holds, not
   by :empty, because a server's template leaves whitespace in an empty row
   and :empty counts that as content. The row stays in the page, so it can
   still be a region that a swap replaces. */
.md-chips:not(:has(.filter-chip)) { display: none; }
.filter-chip {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 2px 3px 2px 10px;
  border-radius: var(--radius-pill);
  font-size: var(--text-xs); font-weight: 600;
  background: color-mix(in srgb, var(--accent) 8%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent) 55%, transparent);
  color: var(--accent);
}
/* .fc-kind is the old name for .filter-chip-kind. The kind reads as
   secondary by its size, weight and capitals; fading it with opacity
   would take it below readable contrast, whatever the theme's colours. */
.filter-chip-kind, .fc-kind {
  font-weight: 500; font-size: var(--text-2xs);
  text-transform: uppercase; letter-spacing: .04em;
}
/* The × removes the filter, so it is a small raised button. */
.filter-chip-x {
  display: inline-flex; align-items: center; justify-content: center;
  width: 18px; height: 18px; border-radius: 50%;
  background: var(--raise-grad); border: 1px solid var(--raise-border);
  box-shadow: var(--raise-shadow);
  color: var(--text-muted); text-decoration: none;
  font-size: var(--text-sm); line-height: 1;
}
.filter-chip-x:hover { color: var(--text); background: var(--raise-grad-hover); border-color: var(--raise-border-hover); }
.filter-chip-x:focus-visible { outline: none; box-shadow: var(--raise-shadow), 0 0 0 3px var(--focus-ring); }
.md-chips-clear {
  font-size: var(--text-xs); color: var(--accent); text-decoration: underline; cursor: pointer;
}

/* Body row: sidebar + resize + detail. */
.md-body { display: flex; flex: 1; min-height: 0; }
/* The sidebar's width is --md-sidebar-w, which pudl-md.js sets as the
   divider is dragged and a project may set from a stored preference. The
   stylesheet holds it between --md-sidebar-min and half the layout, so the
   limits apply however the width was set, and still apply when the layout
   narrows after a drag.

   --md-sidebar-min is registered as a length, so however a theme writes it,
   12rem say, its computed value is in pixels, which is what pudl-md.js
   reads to keep the divider's limits the same as the stylesheet's. */
@property --md-sidebar-min { syntax: "<length>"; inherits: true; initial-value: 180px; }
.md-sidebar {
  width: clamp(var(--md-sidebar-min, 180px), var(--md-sidebar-w, 260px), 50%);
  flex: none; min-height: 0; overflow-y: auto;
  background: var(--surface); border-inline-end: 1px solid var(--border);
}
.md-resize {
  flex: none; width: 6px; margin: 0 -3px; z-index: 3; cursor: col-resize;
  background: transparent; position: relative;
  touch-action: none;
}
.md-resize::before {
  content: ""; position: absolute; inset: 0 2px; border-radius: 2px;
  background: transparent; transition: background .12s;
}
.md-resize:hover::before,
.md-resize.dragging::before { background: var(--accent); }
.md-resize:focus-visible { outline: none; }
.md-resize:focus-visible::before { background: var(--accent); box-shadow: 0 0 0 3px var(--focus-ring); }
.md-detail {
  flex: 1; overflow-y: auto; padding: var(--space-5); min-width: 0;
  background: var(--bg);
}
/* A detail pane scrolls, so the server gives it tabindex="0" for a reader
   who scrolls by keyboard; its focus ring is drawn inside it. */
.md-detail:focus-visible { outline: none; box-shadow: inset 0 0 0 3px var(--focus-ring); }

/* The back link returns from a record to its list. It shows only when the
   layout is narrow enough to show one pane at a time. */
.md-back { display: none; }

/* === One pane at a time =================================================
   When the layout itself is 640px wide or less (a phone, or a narrow window
   or panel on a larger screen), it shows either the list or the record,
   never both. The server says which with data-md-pane on .md-layout:
   "detail" when the URL names a record, and "list", or nothing, otherwise.
   Each pane is its own URL, so Back, reload and sharing all work, and the
   detail's .md-back link returns to the list. */
.md-layout { container: md / inline-size; }

@container md (max-width: 640px) {
  .md-toolbar { flex-wrap: wrap; padding: var(--space-2) var(--space-3); }
  .md-toolbar .md-filter { flex: 1 1 100%; width: auto; }
  .md-toolbar .md-filter-group { flex: 1 1 100%; }
  .md-toolbar-spacer { display: none; }
  .md-toolbar-cmds { flex-wrap: wrap; }
  .md-resize { display: none; }

  /* The list pane fills the layout, and its rows are touch targets. */
  .md-sidebar { flex: 1; width: auto; border-inline-end: 0; }
  .md-sidebar .md-item { padding-top: 10px; padding-bottom: 10px; }
  .md-detail { display: none; padding: var(--space-4); }

  /* The detail pane: the list, its toolbar and its filters step aside. A
     toolbar of site-wide tools, such as a launcher or a window dock, is
     marked .md-site-tools and stays in both panes. */
  .md-layout[data-md-pane="detail"] .md-toolbar:not(.md-site-tools),
  .md-layout[data-md-pane="detail"] .md-chips,
  .md-layout[data-md-pane="detail"] .md-sidebar { display: none; }
  .md-layout[data-md-pane="detail"] .md-detail { display: block; }

  .md-back {
    display: inline-flex; align-items: center; gap: var(--space-1);
    margin-bottom: var(--space-3);
    font-size: var(--text-md); font-weight: 600;
  }
}
.md-back::before { --glyph: var(--glyph-back); --glyph-size: 14px; }
.md-back:dir(rtl)::before { transform: scaleX(-1); }

/* Sidebar rows: a compact list, not big touch targets except on a narrow
   layout. The current row, marked aria-current on its link, carries a 3px
   accent edge on the side facing the detail pane and a bold title. */
.md-section-label {
  padding: 14px 14px var(--space-1);
  font-size: var(--text-2xs); font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; color: var(--text-muted);
}
.md-row {
  display: flex; align-items: center;
  border-inline-end: 3px solid transparent;
  border-bottom: 1px solid var(--surface-alt);
}
.md-row:hover { background: var(--surface-alt); }
.md-row:has(> [aria-current]:not([aria-current="false"])),
.md-row.active {
  background: var(--surface-alt);
  border-inline-end-color: var(--accent);
}
.md-row:has(> [aria-current]:not([aria-current="false"])) .md-item,
.md-row.active .md-item { font-weight: 700; color: var(--text); }
.md-item {
  flex: 1; min-width: 0;
  padding: 5px 14px;
  font-size: var(--text-sm); line-height: 1.5;
  color: var(--text); text-decoration: none;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  cursor: pointer;
}
/* A row's title keeps to one line and ends in an ellipsis, but its
   metadata wraps: a description cut at the sidebar's edge would lose its
   end with nothing to say so, since the ellipsis marks only the title's
   line. An item may hold several .md-meta lines, a date and then a
   description say. */
.md-item .md-meta {
  display: block; font-size: var(--text-2xs); font-weight: 500; color: var(--text-muted);
  white-space: normal; overflow-wrap: anywhere;
}
.md-grip {
  flex: none; width: 22px; text-align: center; cursor: grab;
  color: var(--text-muted); font-size: var(--text-md); line-height: 1;
}

/* === Trees ===============================================================
   A hierarchy of places, such as folders or nested categories: nested
   lists of links, each a place with an address, so without script a tree
   is an indented list the reader can follow. The server marks a node that
   has children with aria-expanded on its link, and the current node with
   aria-current. pudl-tree.js turns it into the ARIA tree pattern, draws
   the toggles, and collapses what aria-expanded="false" says is closed.

   The nodes are flat, like list rows, because they are places to go; the
   current node carries the accent edge and bold of the current list row.
   Each toggle is a small raised button, since pressing it opens or closes
   its branch. */
.tree, .tree ul { list-style: none; margin: 0; padding: 0; }
.tree ul { padding-inline-start: var(--space-4); }
.tree a {
  display: flex; align-items: center; gap: 6px;
  padding: 3px 10px 3px 6px;
  border-inline-end: 3px solid transparent;
  font-size: var(--text-sm); line-height: 1.5;
  color: var(--text); text-decoration: none; white-space: nowrap;
}
.tree a:hover { background: var(--surface-alt); }
.tree a[aria-current]:not([aria-current="false"]) {
  background: var(--surface-alt); border-inline-end-color: var(--accent); font-weight: 700;
}
.tree a:focus-visible { outline: none; box-shadow: inset 0 0 0 3px var(--focus-ring); }
.tree-toggle, .tree-spacer { flex: none; width: 16px; height: 16px; }
.tree-toggle {
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 4px; border: 1px solid var(--raise-border);
  background: var(--raise-grad); box-shadow: var(--raise-shadow);
  color: var(--text-muted); cursor: pointer;
}
.tree-toggle:hover { color: var(--text); background: var(--raise-grad-hover); border-color: var(--raise-border-hover); }
.tree-toggle::before {
  content: ""; width: 10px; height: 10px;
  background: currentColor;
  -webkit-mask: var(--glyph-caret) center / contain no-repeat;
  mask: var(--glyph-caret) center / contain no-repeat;
  transform: rotate(-90deg);
}
.tree-toggle:dir(rtl)::before { transform: rotate(90deg); }
[aria-expanded="true"] > .tree-toggle::before { transform: none; }
.tree.tree-ready a[aria-expanded="false"] + ul { display: none; }

/* === Path bar ============================================================
   Where the reader is inside a hierarchy they are browsing, such as the
   folder a file manager shows: an ordered list of the places above it,
   each a link, ending with the place itself, marked aria-current. It is
   for a browsed hierarchy only. A site's own sections are its tabs, and a
   record's way back is its back link, so a path bar is never a site's
   breadcrumb trail. The separators are drawn, and say nothing to
   assistive technology, which hears the list. */
.path ol {
  display: flex; flex-wrap: wrap; align-items: center; gap: 0 2px;
  margin: 0; padding: 0; list-style: none; min-width: 0;
  font-size: var(--text-sm);
}
.path li { display: inline-flex; align-items: center; min-width: 0; }
.path li + li::before {
  content: ""; flex: none; width: 12px; height: 12px; margin-inline: 1px;
  background: var(--text-muted);
  -webkit-mask: var(--glyph-slash) center / contain no-repeat;
  mask: var(--glyph-slash) center / contain no-repeat;
}
.path li > * { max-width: 16em; padding: 2px 3px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.path [aria-current]:not([aria-current="false"]) { font-weight: 700; color: var(--text); }
.path.mono { font-family: var(--mono); }

/* === Menus ===============================================================
   A menu button opens a panel of choices: places to go, and actions. The
   button is raised and carries a caret, so it reads as opening something
   rather than doing something, and it shows pressed while its panel is
   open. The panel is lifted like a dialog, without a backdrop, because it
   is not modal. It is an HTML popover, so it opens, closes on Escape or an
   outside click, and sits above everything with no script; pudl-menu.js
   places it against its button and adds the arrow keys and filtering.

   Places in a panel are list rows, the .md-row and .md-item of the
   master-detail sidebar, grouped under .md-section-label headings. Actions
   are .menu-action buttons, set apart by an .menu-sep rule. */
.menu { display: inline-flex; position: relative; }
.menu-btn::after { --glyph: var(--glyph-caret); --glyph-size: 12px; opacity: .75; }
.menu:has(> .menu-panel:popover-open) > .menu-btn {
  background: var(--raise-active-bg);
  box-shadow: var(--raise-active-shadow);
}

.menu-panel {
  margin: auto; padding: 6px 0;
  min-width: 14rem; max-width: min(22rem, calc(100vw - 16px));
  max-height: min(32rem, calc(100svh - 16px));
  overflow: auto;
  background: var(--dialog-bg, var(--surface)); color: var(--text);
  border: 1px solid var(--border); border-radius: var(--radius);
  box-shadow:
      var(--shadow-card),
      0 16px 40px color-mix(in srgb, var(--shade) calc(var(--depth) * 1.6), transparent);
  font-family: var(--font);
}
/* The script hides a panel for the moment between opening and placing it,
   so it never flashes in the middle of the screen. */
.menu-panel.placing { visibility: hidden; }
/* On a narrow screen the script opens the panel as a sheet across the full
   width, so its rows stay large enough to tap. */
.menu-panel.sheet {
  width: 100%; max-width: none;
  border-width: 0 0 1px; border-radius: 0 0 var(--radius) var(--radius);
}
.menu-panel.sheet .md-item { padding-top: 10px; padding-bottom: 10px; }

.menu-panel .md-section-label { padding: 10px 14px var(--space-1); }
.menu-panel .md-section-label:first-child { padding-top: 6px; }
.menu-panel .md-row { border-bottom: 0; }
.menu-panel .md-item:focus-visible,
.menu-action:focus-visible {
  outline: none; background: var(--surface-alt);
  box-shadow: inset 3px 0 var(--accent);
}
.menu-panel .md-item:dir(rtl):focus-visible,
.menu-action:dir(rtl):focus-visible { box-shadow: inset -3px 0 var(--accent); }
.menu-panel .md-filter { display: block; width: calc(100% - 20px); margin: var(--space-1) 10px 6px; }
.menu-panel form { margin: 0; }
.menu-empty { margin: 0; padding: 6px 14px; font-size: var(--text-sm); color: var(--text-muted); }

.menu-sep { border: 0; border-top: 1px solid var(--border); margin: 6px 0; }
.menu-action {
  display: flex; align-items: center; gap: var(--space-2); width: 100%;
  padding: 6px 14px;
  background: none; border: 0; border-radius: 0;
  font: var(--text-sm)/1.5 var(--font); color: var(--text); text-align: start;
  cursor: pointer;
}
.menu-action:hover { background: var(--surface-alt); }
.menu-action.danger { color: var(--danger); }

/* === Data tables =========================================================
   A table of many records, one per row. It sits in a .data-table-wrap,
   which scrolls sideways when the columns outrun the space and keeps the
   header in view as the rows scroll under it.

   A column the reader can sort by has a header whose link, or button, is
   raised, because pressing it re-sorts, and it carries a glyph: two small
   arrows while the column is not the sort, one arrow for the direction it
   is. The header's aria-sort says which, and the link is an ordinary
   address such as ?sort=amount&dir=desc, so sorting works without script
   and a sorted list can be shared. Columns of numbers carry .num and align
   to the end. A selected row is marked aria-selected="true", or holds a
   checked checkbox, and carries an accent edge at its start as well as a
   tint. A .data-table-empty row says there is nothing to show.

   With .stack on the table, a wrap 560px wide or less shows each row as a
   small card of label and value pairs, the labels coming from each cell's
   data-label. */
.data-table-wrap {
  overflow: auto; max-width: 100%;
  background: var(--surface);
  border: 1px solid var(--border); border-radius: var(--radius);
  container: dt / inline-size;
}
.data-table {
  width: 100%; border-collapse: separate; border-spacing: 0;
  font-size: var(--text-md); font-variant-numeric: tabular-nums;
}
.data-table caption {
  caption-side: top; text-align: start;
  padding: var(--space-3) var(--space-4) var(--space-2);
  font-size: var(--text-sm); font-weight: 600; color: var(--text-muted);
}
.data-table th, .data-table td {
  padding: var(--space-2) var(--space-3);
  text-align: start; vertical-align: top;
  border-bottom: 1px solid var(--border);
}
.data-table thead th {
  position: sticky; top: 0; z-index: 1;
  background: var(--surface-alt);
  font-size: var(--text-sm); font-weight: 600; color: var(--text-muted);
  white-space: nowrap;
}
.data-table tbody tr:last-child > * { border-bottom: 0; }
.data-table tbody tr:hover { background: color-mix(in srgb, var(--text) 4%, transparent); }
.data-table .num { text-align: end; }
.data-table .data-table-check { width: 1%; }
.data-table .data-table-check input { width: 16px; height: 16px; margin: 0; accent-color: var(--accent); }
.data-table tr[aria-selected="true"],
.data-table tr:has(> .data-table-check input:checked) {
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  box-shadow: inset 3px 0 var(--accent);
}
.data-table tr[aria-selected="true"]:dir(rtl),
.data-table tr:has(> .data-table-check input:checked):dir(rtl) { box-shadow: inset -3px 0 var(--accent); }

/* A sortable column's header is a raised control filling its cell. */
.data-table th[aria-sort] { padding: 0; }
.data-table th[aria-sort] > :is(a, button) {
  display: flex; align-items: center; gap: 6px; width: 100%;
  padding: var(--space-2) var(--space-3);
  background: var(--raise-grad);
  border: 0; border-inline-end: 1px solid var(--raise-border);
  box-shadow: inset 0 1px color-mix(in srgb, var(--light) var(--lit), transparent);
  font: inherit; color: var(--text); text-decoration: none; cursor: pointer;
  --glyph: var(--glyph-sort); --glyph-size: 12px;
}
.data-table th[aria-sort].num > :is(a, button) { justify-content: flex-end; }
.data-table th[aria-sort] > :is(a, button):hover { background: var(--raise-grad-hover); }
.data-table th[aria-sort] > :is(a, button):active { background: var(--raise-active-bg); }
.data-table th[aria-sort] > :is(a, button):focus-visible { outline: none; box-shadow: inset 0 0 0 3px var(--focus-ring); }
.data-table th[aria-sort] > :is(a, button)::after { opacity: .55; }
.data-table th[aria-sort="ascending"] > :is(a, button)::after { --glyph: var(--glyph-sort-up); opacity: 1; }
.data-table th[aria-sort="descending"] > :is(a, button)::after { --glyph: var(--glyph-sort-down); opacity: 1; }
.data-table th[aria-sort="ascending"] > :is(a, button),
.data-table th[aria-sort="descending"] > :is(a, button) { font-weight: 700; }

.data-table .data-table-empty td {
  padding: var(--space-6) var(--space-4);
  text-align: center; color: var(--text-muted);
}

/* A table whose rows are choices, such as a file list or an inbox, is a
   grid: role="grid" on the table. pudl-grid.js makes it one stop in the
   Tab order, moves the selection with the arrow keys, and opens a row, by
   its first link, with Enter or a double-click. The selected row already
   looks selected; the row with focus also carries the ring. */
.data-table[role="grid"] tbody tr { cursor: default; }
.data-table[role="grid"] tbody tr:focus-visible { outline: none; box-shadow: inset 0 0 0 3px var(--focus-ring); }
.data-table[role="grid"] tbody tr[aria-selected="true"]:focus-visible { box-shadow: inset 3px 0 var(--accent), inset 0 0 0 3px var(--focus-ring); }
.data-table[role="grid"] tbody tr[aria-selected="true"]:dir(rtl):focus-visible { box-shadow: inset -3px 0 var(--accent), inset 0 0 0 3px var(--focus-ring); }

@container dt (max-width: 560px) {
  .data-table.stack thead {
    position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%);
  }
  .data-table.stack, .data-table.stack tbody, .data-table.stack tr, .data-table.stack td,
  .data-table.stack caption { display: block; }
  .data-table.stack tr { padding: var(--space-2) 0; border-bottom: 1px solid var(--border); }
  .data-table.stack tbody tr:last-child { border-bottom: 0; }
  /* The label keeps to the start and everything in the value, two badges
     say, stays together at the end. */
  .data-table.stack td {
    display: flex; flex-wrap: wrap; justify-content: flex-end; align-items: baseline; gap: var(--space-1) var(--space-2);
    padding: var(--space-1) var(--space-4); border-bottom: 0; text-align: end;
  }
  .data-table.stack .data-table-check { width: auto; }
  .data-table.stack td[data-label]::before {
    content: attr(data-label); margin-inline-end: auto; padding-inline-end: var(--space-2);
    font-size: var(--text-sm); font-weight: 600; color: var(--text-muted); text-align: start;
  }
}

/* === Notices and toasts ==================================================
   A notice is a message that stays on the page until it is dealt with: an
   error on submission, a warning about the record in view, a note of what
   has changed. A toast is a passing confirmation, "Saved", that leaves by
   itself. Both lead with the glyph of their kind (information, positive,
   warning, danger) and a coloured edge, so neither rests on colour alone.
   An error the reader must act on is a notice, never a toast, because a
   toast goes away. */
.notice {
  display: flex; align-items: flex-start; gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border: 1px solid color-mix(in srgb, var(--notice-color) 45%, var(--border));
  border-inline-start: 4px solid var(--notice-color);
  border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--notice-color) 9%, var(--surface));
  color: var(--text);
  font-size: var(--text-md);
  --notice-color: var(--accent);
  --glyph: var(--glyph-info); --glyph-size: 18px;
}
.notice::before { margin-top: 1px; color: var(--notice-color); background: var(--notice-color); }
.notice.positive { --notice-color: var(--positive); --glyph: var(--glyph-check); }
.notice.warn { --notice-color: var(--warn); --glyph: var(--glyph-warning); }
.notice.danger { --notice-color: var(--danger); --glyph: var(--glyph-stop); }
.notice-content { flex: 1; min-width: 0; }
.notice-title { margin: 0 0 2px; font-size: var(--text-md); font-weight: 700; }
.notice-body { margin: 0; }
.notice-actions { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-top: var(--space-2); }

/* The small raised button that dismisses a notice or a toast. */
.notice-close, .toast-close {
  flex: none; display: inline-flex; align-items: center; justify-content: center;
  width: 24px; height: 24px; padding: 0; margin-inline-start: auto;
  border-radius: 50%;
  background: var(--raise-grad); border: 1px solid var(--raise-border);
  box-shadow: var(--raise-shadow);
  color: var(--text-muted); cursor: pointer;
  --glyph: var(--glyph-close); --glyph-size: 12px;
}
.notice-close:hover, .toast-close:hover { color: var(--text); background: var(--raise-grad-hover); }
.notice-close:focus-visible, .toast-close:focus-visible { outline: none; box-shadow: var(--raise-shadow), 0 0 0 3px var(--focus-ring); }

/* Toasts gather in a live region at the foot of the window, which
   pudl-toast.js creates if the page has not rendered one. */
.toast-region {
  position: fixed; z-index: 1000;
  inset-block-end: var(--space-4); inset-inline-end: var(--space-4);
  display: flex; flex-direction: column; align-items: flex-end; gap: var(--space-2);
  width: min(24rem, calc(100vw - 2 * var(--space-4)));
  pointer-events: none;
}
.toast {
  pointer-events: auto;
  display: flex; align-items: flex-start; gap: var(--space-3);
  width: 100%;
  padding: var(--space-3) var(--space-3) var(--space-3) var(--space-4);
  background: var(--dialog-bg, var(--surface)); color: var(--text);
  border: 1px solid var(--border);
  border-inline-start: 4px solid var(--notice-color);
  border-radius: var(--radius-sm);
  box-shadow:
      var(--shadow-card),
      0 10px 28px color-mix(in srgb, var(--shade) calc(var(--depth) * 1.4), transparent);
  font-size: var(--text-md);
  transition: opacity .2s, transform .2s;
  --notice-color: var(--accent);
  --glyph: var(--glyph-info); --glyph-size: 18px;
}
.toast::before { margin-top: 1px; background: var(--notice-color); }
.toast.positive { --notice-color: var(--positive); --glyph: var(--glyph-check); }
.toast.warn { --notice-color: var(--warn); --glyph: var(--glyph-warning); }
.toast.danger { --notice-color: var(--danger); --glyph: var(--glyph-stop); }
.toast-text { flex: 1; min-width: 0; margin: 2px 0 0; }
.toast.leaving { opacity: 0; transform: translateY(8px); }

/* === Empty and loading states ============================================
   An empty state says there is nothing here yet, and what to do about it:
   a drawn glyph, a title, a line of text and perhaps an action. A loading
   state is a spinner with words beside it, in a role="status" element, so
   it is announced and never rests on the animation alone. A region being
   refreshed by pudl-regions.js dims as well. */
.empty-state {
  display: grid; justify-items: center; gap: var(--space-2);
  padding: var(--space-6) var(--space-4);
  text-align: center; color: var(--text-muted);
  --glyph: var(--glyph-empty); --glyph-size: 36px;
}
.empty-state::before { opacity: .7; margin-bottom: var(--space-1); }
.empty-state-title { margin: 0; font-size: var(--text-lg); font-weight: 700; color: var(--text); }
.empty-state-body { margin: 0; max-width: 42ch; font-size: var(--text-md); }
.empty-state-actions { display: flex; flex-wrap: wrap; justify-content: center; gap: var(--space-2); margin-top: var(--space-2); }

.spinner {
  display: inline-block; flex: none; vertical-align: middle;
  width: 1.2em; height: 1.2em; border-radius: 50%;
  border: 2px solid color-mix(in srgb, currentColor 22%, transparent);
  border-top-color: currentColor;
  animation: pudl-spin .8s linear infinite;
}
@keyframes pudl-spin { to { transform: rotate(360deg); } }
.loading {
  display: flex; align-items: center; justify-content: center; gap: var(--space-2);
  padding: var(--space-5) var(--space-4);
  font-size: var(--text-sm); color: var(--text-muted);
}

/* === Tooltips ============================================================
   A tooltip names a control that shows only a glyph, such as an icon
   button, a window button or a filter's apply button. pudl-tooltip.js
   shows one for any element with data-tooltip, or with an aria-label on a
   glyph-only control, after a moment of hover or at once on keyboard
   focus. It stays while the pointer moves onto it, and Escape dismisses
   it, as WCAG 1.4.13 asks. */
.tooltip {
  margin: 0; padding: 4px 8px;
  max-width: 18rem;
  border: 0; border-radius: 5px;
  background: var(--text); color: var(--bg);
  font: 600 var(--text-xs)/1.4 var(--font);
  box-shadow: 0 4px 12px color-mix(in srgb, var(--shade) calc(var(--depth) * 1.4), transparent);
  pointer-events: auto;
}
.tooltip.placing { visibility: hidden; }

/* === Pagination ==========================================================
   Pages of a long list, each an address such as ?page=3, so paging works
   without script and a page can be shared. The page links are raised
   small buttons; the current page, marked aria-current="page", is pressed
   in, because the reader is already there. Previous and next carry rel
   and a glyph, and at either end are marked aria-disabled with no href. */
.pagination { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-1); }
.pagination-summary { margin-inline-end: var(--space-3); font-size: var(--text-sm); color: var(--text-muted); }
.page-link {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--space-1);
  min-width: 32px; height: 32px; padding: 0 10px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--raise-border);
  background: var(--raise-grad); box-shadow: var(--raise-shadow);
  font: 600 var(--text-sm) var(--font); color: var(--text);
  text-decoration: none; font-variant-numeric: tabular-nums;
  --glyph: var(--glyph-back); --glyph-size: 12px;
}
.page-link:hover { background: var(--raise-grad-hover); border-color: var(--raise-border-hover); }
.page-link:focus-visible { outline: none; box-shadow: var(--raise-shadow), 0 0 0 3px var(--focus-ring); }
.page-link[aria-current]:not([aria-current="false"]) {
  background: var(--raise-active-bg); box-shadow: var(--raise-active-shadow);
  border-color: var(--raise-border-hover);
}
.page-link[aria-disabled="true"] { opacity: .45; pointer-events: none; }
.page-link[rel="next"]::after { transform: scaleX(-1); }
.page-link[rel="prev"]:dir(rtl)::before { transform: scaleX(-1); }
.page-link[rel="next"]:dir(rtl)::after { transform: none; }
.page-gap { padding: 0 var(--space-1); color: var(--text-muted); }
/* On a phone the summary takes a line of its own, so the links stay on one. */
@media (max-width: 560px) { .pagination-summary { flex-basis: 100%; margin-bottom: var(--space-1); } }

/* === Glyphs as elements ==================================================
   Any glyph token drawn as an element, in the colour of the text around
   it, at the size of that text unless --glyph-size says otherwise:

     <span class="glyph" style="--glyph: var(--glyph-folder)" aria-hidden="true"></span>

   A glyph beside words that already say what it shows is aria-hidden; one
   that stands alone carries role="img" and an aria-label. */
.glyph {
  display: inline-block; flex: none; vertical-align: -0.125em;
  width: var(--glyph-size, 1em); height: var(--glyph-size, 1em);
  background: currentColor;
  -webkit-mask: var(--glyph) center / contain no-repeat;
  mask: var(--glyph) center / contain no-repeat;
}

/* === Drop targets ========================================================
   What may be dropped where is the application's rule, so the behaviour
   is the host's; PUDL supplies only the look, so every application shows
   the same one. While something is dragged over a place it may land, the
   host sets data-drop-over on a .drop-zone, or data-drop-target on an item
   such as a folder's row, and PUDL rings it in the accent. A .drop-hint
   inside a zone says what dropping there will do, and shows only then. */
.drop-zone { position: relative; }
.drop-zone[data-drop-over], [data-drop-target] {
  box-shadow: inset 0 0 0 2px var(--accent);
  background-color: color-mix(in srgb, var(--accent) 7%, transparent);
}
.drop-hint { display: none; }
.drop-zone[data-drop-over] > .drop-hint {
  display: block; position: absolute; z-index: 1;
  inset-inline-start: 50%; bottom: var(--space-3); translate: -50% 0;
  margin: 0; padding: 4px 12px; border-radius: var(--radius-pill);
  background: var(--accent); color: var(--on-accent);
  font-size: var(--text-sm); font-weight: 600; white-space: nowrap;
  pointer-events: none;
}
.drop-zone[data-drop-over]:dir(rtl) > .drop-hint { translate: 50% 0; }

/* === Reduced motion ======================================================
   A reader who has asked their system for less motion gets none: every
   transition and animation completes at once. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    transition-duration: 0.01ms !important;
    transition-delay: 0s !important;
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    scroll-behavior: auto !important;
  }
}

/* === Forced colours ======================================================
   In Windows' high-contrast mode the browser replaces the page's colours
   with the reader's and removes every box-shadow. PUDL draws its focus
   rings, and much of its raised and sunken treatment, with shadows, so
   here focus becomes a real outline, controls keep visible borders, and
   selected and pressed states take the system's highlight colours, since
   an elevation change alone would not show. */
@media (forced-colors: active) {
  :focus-visible { outline: 2px solid Highlight !important; outline-offset: 2px !important; }
  .btn, .icon-btn, .seg, .form-input, .form-select, .form-textarea, .md-filter,
  .switch-track, .switch-thumb, .menu-panel, .dialog, .card, .section-tab,
  .filter-chip, .filter-chip-x { border: 1px solid CanvasText; }
  .seg button[aria-pressed="true"], .seg button[aria-checked="true"],
  .seg [aria-current]:not([aria-current="false"]), .seg button.active,
  .section-tab[aria-current]:not([aria-current="false"]), .section-tab.active,
  .md-row:has(> [aria-current]:not([aria-current="false"])), .md-row.active,
  .btn[aria-pressed="true"], .menu:has(> .menu-panel:popover-open) > .menu-btn {
    forced-color-adjust: none;
    background: Highlight; color: HighlightText; border-color: Highlight;
  }
  .md-row:has(> [aria-current]:not([aria-current="false"])) .md-item,
  .md-row:has(> [aria-current]:not([aria-current="false"])) .md-meta,
  .md-row.active .md-item, .md-row.active .md-meta { color: HighlightText; }
  .md-row:not(.active):not(:has(> [aria-current]:not([aria-current="false"]))) { border-inline-end-color: Canvas; }
  .switch[aria-checked="true"] .switch-track { forced-color-adjust: none; background: Highlight; border-color: Highlight; }
  .switch-thumb { forced-color-adjust: none; background: Canvas; }
  /* Glyphs are masks, exempt from the system's recolouring so that they
     still paint, so they are painted in the system's own colours here. */
  .badge::before, .form-error::before, .menu-btn::after, .md-back::before,
  .md-filter-go::before { forced-color-adjust: none; background: CanvasText; }
  .md-filter-go::before, .menu-btn::after { background: ButtonText; }
  .notice::before, .toast::before { forced-color-adjust: none; background: CanvasText; }
  .notice-close::before, .toast-close::before, .data-table th[aria-sort] > :is(a, button)::after {
    forced-color-adjust: none; background: ButtonText;
  }
  .notice, .toast, .notice-close, .toast-close, .data-table-wrap { border: 1px solid CanvasText; }
  .tablist > [role="tab"], .page-link, .tabs > [role="tabpanel"], .tooltip { border: 1px solid CanvasText; }
  .tablist > [role="tab"][aria-selected="true"], .page-link[aria-current]:not([aria-current="false"]) {
    forced-color-adjust: none; background: Highlight; color: HighlightText; border-color: Highlight;
  }
  .empty-state::before { forced-color-adjust: none; background: CanvasText; }
  .page-link[rel="prev"]::before, .page-link[rel="next"]::after { forced-color-adjust: none; background: ButtonText; }
  .spinner { border-color: GrayText; border-top-color: CanvasText; }
  .notice, .toast { border-inline-start-width: 4px; }
  .data-table th[aria-sort] > :is(a, button) { border: 1px solid ButtonText; }
  .data-table tr[aria-selected="true"], .data-table tr:has(> .data-table-check input:checked) {
    forced-color-adjust: none; background: Highlight; color: HighlightText;
  }
  .menu:has(> .menu-panel:popover-open) > .menu-btn::after { background: HighlightText; }
  .md-resize::before { background: CanvasText; inset: 0 2.5px; }
  .md-resize:hover::before, .md-resize.dragging::before, .md-resize:focus-visible::before { background: Highlight; }
  .badge { border: 1px solid CanvasText; }
  .tree a[aria-current]:not([aria-current="false"]) { forced-color-adjust: none; background: Highlight; color: HighlightText; }
  .tree-toggle { border: 1px solid ButtonText; }
  .tree-toggle::before { forced-color-adjust: none; background: ButtonText; }
  .glyph, .path li + li::before { forced-color-adjust: none; background: CanvasText; }
  .drop-zone[data-drop-over], [data-drop-target] { outline: 2px dashed Highlight; outline-offset: -2px; }
  .drop-zone[data-drop-over] > .drop-hint { forced-color-adjust: none; background: Highlight; color: HighlightText; }
  .doc-tab-dirty { forced-color-adjust: none; background: CanvasText; }
  .doc-tab-close { border: 1px solid ButtonText; }
  .doc-tab-close::before { forced-color-adjust: none; background: ButtonText; }
  .code-surface, pre.code { border: 1px solid CanvasText; }
}

/* === Print ===============================================================
   A printed page carries the content, on white, without the chrome: the
   topbar, the tab bar, the toolbars, the filter chips and the divider.
   A master-detail layout showing a record prints the record alone. */
@media print {
  :root, [data-theme="dark"] {
    color-scheme: light;
    --bg: #ffffff; --surface: #ffffff; --surface-alt: #f2f2f2;
    --text: #000000; --text-muted: #444444; --border: #999999;
    --accent: #1a3d6b; --accent-hover: #1a3d6b;
    --syntax-keyword: #9b2c63; --syntax-string: #1d6a39; --syntax-number: #8a4a00; --syntax-comment: #57606b;
    --syntax-name: #1d4f91; --syntax-tag: #6b36a3; --syntax-attr: #6d5500; --syntax-heading: #000000;
    --syntax-link: #1a3d6b; --syntax-error: #b42318;
  }
  .topbar, .app-section-bar, .md-toolbar, .md-chips, .md-resize, .md-back,
  .menu, .menu-panel, .dialog-backdrop, .theme-toggle,
  .toast-region, .notice-close, .tablist, .pagination, .tooltip { display: none !important; }
  .tabs > [role="tabpanel"] { display: block !important; border: 0; padding: 0 0 var(--space-4); }
  .data-table-wrap { overflow: visible; border: 0; }
  .data-table thead th { position: static; }
  .data-table th[aria-sort] > :is(a, button) { background: none; box-shadow: none; border: 0; }
  body:has(> .md-layout) { height: auto; overflow: visible; display: block; }
  .md-layout, .md-body { display: block; height: auto; }
  .md-sidebar, .md-detail { width: auto; overflow: visible; border: 0; }
  .md-layout[data-md-pane="detail"] .md-sidebar { display: none; }
  .btn, .icon-btn, .card, .dialog { box-shadow: none; }
  a { color: #000000; }
}
