/* AUTO-GENERATED by scripts/gen_ux_catalogue.py — do not edit by hand. */
/* Source: src/dazzle/page/runtime/static/css/{tokens,dz,dz}.css (+ HM components) + packages/hatchi-maxchi/components/*.css */
.dz-catalogue-preview {
  border: 1px solid var(--md-default-fg-color--lightest, #ddd);
  border-radius: 8px;
  padding: 1rem;
  margin: 0.5rem 0 1.5rem;
  background: #fff;
  overflow-x: auto;
}

/* --- tokens.css --- */
/*
 * tokens.css — Dazzle design system as CSS custom properties.
 *
 * Every length, colour, type-size, radius, shadow, and motion value
 * used in component CSS MUST resolve to a token defined here.
 * Literal values (px, rem, em, hex, hsl numerics) are permitted only
 * inside this file. Stylelint enforces this rule.
 *
 * UK English throughout: --colour-*, not --color-*.
 *
 * ─────────────────────────────────────────────────────────────────
 *
 * Portions of this file derived from Open Props by Adam Argyle (MIT).
 * Source: https://github.com/argyleink/open-props
 * Values have been renamed to Dazzle's vocabulary and selectively
 * adopted. Open Props is NOT a runtime dependency — values are
 * vendored in source.
 */

:root {
  /* ─────────────────────────────── spacing ─────────────────────────
   * 4px base. Geometric-ish progression: each step roughly 1.5x.
   * Use semantic names; fall back to scale when truly arbitrary.
   * ───────────────────────────────────────────────────────────────── */
  --space-xs: 0.25rem;   /* 4px */
  --space-sm: 0.5rem;    /* 8px */
  --space-md: 0.75rem;   /* 12px */
  --space-lg: 1rem;      /* 16px */
  --space-xl: 1.5rem;    /* 24px */
  --space-2xl: 2rem;     /* 32px */
  --space-3xl: 3rem;     /* 48px */

  /* ─────────────────────────── fluid spacing ───────────────────────
   * For responsive padding/gap that adapts smoothly. Lifted formulae
   * pattern from Open Props (props.sizes.css). */
  --space-fluid-sm: clamp(0.5rem, 1vw, 0.75rem);
  --space-fluid-md: clamp(0.75rem, 1.5vw, 1rem);
  --space-fluid-lg: clamp(1rem, 2.5vw, 1.5rem);

  /* ────────────────────────────── type scale ───────────────────────
   * Fluid via clamp(). Lifted from Open Props (props.fonts.css)
   * fluid-font-size block, renamed to Dazzle's vocabulary. The
   * clamp expressions are non-trivial to derive correctly.
   * ───────────────────────────────────────────────────────────────── */
  --text-xs: clamp(0.75rem, 0.7rem + 0.2vw, 0.875rem);
  --text-sm: clamp(0.875rem, 0.8rem + 0.25vw, 1rem);
  --text-base: clamp(1rem, 0.9rem + 0.3vw, 1.125rem);
  --text-lg: clamp(1.125rem, 1rem + 0.5vw, 1.375rem);
  --text-xl: clamp(1.375rem, 1.2rem + 0.7vw, 1.75rem);
  --text-2xl: clamp(1.75rem, 1.5rem + 1vw, 2.25rem);
  --text-3xl: clamp(2.25rem, 1.9rem + 1.5vw, 3rem);
  --text-display: clamp(3rem, 2.4rem + 2.5vw, 4.5rem);

  /* ─────────────────────────── heading scale (#983) ────────────────
   * Two scales for two contexts. The app shell is dense and
   * compact (h1 ~18px); the marketing site is editorial and
   * expansive (h1 ~3.5rem). Pre-#983 every renderer hard-coded its
   * own font-size, so promoting `.dz-table-title` from `--text-base`
   * to `--text-lg` was a one-off tweak instead of a tokenised
   * decision.
   *
   * The marketing tokens are also defined in `design-system.css`
   * (canonical home for site/marketing variables). Mirroring them
   * here keeps app-shell + marketing token discovery in one file
   * for adopters reading tokens.css to understand the system.
   */
  --dz-heading-app-page-title: var(--text-lg);
  --dz-heading-app-section-title: var(--text-base);
  --dz-heading-app-subsection-title: var(--text-sm);

  --dz-heading-marketing-hero: var(--dz-font-size-hero-headline, 3.5rem);
  --dz-heading-marketing-cta: var(--dz-font-size-cta-headline, 3rem);
  --dz-heading-marketing-section: var(--dz-font-size-section-headline, 2.25rem);

  /* ─────────────────────────── font families ─────────────────────
   * `--font-system` is the OS-default sans stack; `--font-mono` is
   * used by tabular numerics in chart summaries (bar_track etc.). */
  --font-system: "Geist", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --font-mono: "Geist Mono", ui-monospace, "SF Mono", "Cascadia Mono", "Roboto Mono",
               Consolas, "Liberation Mono", monospace;

  /* ─────────────────────────── line height ─────────────────────────
   * Unitless; multiplies the font-size of the same element. */
  --leading-tight: 1.2;
  --leading-normal: 1.5;
  --leading-loose: 1.8;

  /* ─────────────────────────── font weight ─────────────────────────
   * Variable-font friendly; only the weights we actually use. */
  --weight-regular: 400;
  --weight-medium: 500;
  --weight-semibold: 600;
  --weight-bold: 700;

  /* ─────────────────────────── radii ───────────────────────────────
   * Use semantic names. Full = pill. */
  /* Focus ring (HaTchi-MaXchi TASTE-1): accent at 40% alpha, 2px, offset
     2px — one ring vocabulary for every interactive element. */
  --focus-ring-color: color-mix(in oklab, var(--colour-brand) 40%, transparent);
  --focus-ring-width: 2px;
  --focus-ring-offset: 2px;

  --radius-sm: 0.25rem;  /* 4px — small inputs, badges */
  --radius-md: 0.375rem; /* 6px — cards, buttons */
  --radius-lg: 0.75rem;  /* 12px — large cards, modals */
  --radius-full: 9999px;

  /* ─────────────────────────── shadows ─────────────────────────────
   * Layered for depth. Sources: Open Props shadow blocks adjusted
   * for Dazzle's visual identity (lower contrast, warmer ambient). */
  --shadow-sm:
    0 1px 2px hsl(0 0% 0% / 0.04),
    0 1px 3px hsl(0 0% 0% / 0.06);
  --shadow-md:
    0 2px 4px hsl(0 0% 0% / 0.04),
    0 4px 8px hsl(0 0% 0% / 0.08);
  --shadow-lg:
    0 4px 8px hsl(0 0% 0% / 0.04),
    0 12px 24px hsl(0 0% 0% / 0.12);

  /* ─────────────────────────── motion ──────────────────────────────
   * Easings lifted from Open Props (props.easings.css). The
   * cubic-bezier values for spring-like easings are well-tuned. */
  --ease-in: cubic-bezier(0.4, 0, 1, 1);
  --ease-out: cubic-bezier(0, 0, 0.2, 1);
  --ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
  --ease-spring: cubic-bezier(0.5, -0.5, 0.5, 1.5);

  /* Spring-easing ladder, Open Props convention. Progressively
   * more bounce — pick the smallest one that conveys the affordance.
   * #960 layer 1. */
  --ease-spring-1: cubic-bezier(0.5, 0.75, 0.75, 1.25);
  --ease-spring-2: cubic-bezier(0.5, 1, 0.75, 1.25);
  --ease-spring-3: cubic-bezier(0.5, 1.25, 0.75, 1.25);
  --ease-spring-4: cubic-bezier(0.5, 1.5, 0.75, 1.25);
  --ease-spring-5: cubic-bezier(0.5, 1.75, 0.75, 1.25);

  /* Elastic easings — for dismiss/snap-back motion. */
  --ease-elastic-1: cubic-bezier(0.5, 0.75, 0.75, 1.25);
  --ease-elastic-2: cubic-bezier(0.5, 1, 0.75, 1.25);
  --ease-elastic-3: cubic-bezier(0.5, 1.25, 0.75, 1.25);

  --duration-fast: 80ms;
  --duration-base: 150ms;
  --duration-slow: 300ms;

  /* Named animation shorthands, Open Props convention. Each combines
   * keyframe + duration + easing in one var so templates can write
   * `animation: var(--animation-fade-in)` directly. The keyframes
   * themselves are defined below the :root block. #960 layer 1. */
  --animation-fade-in: dz-fade-in var(--duration-base) var(--ease-out);
  --animation-fade-out: dz-fade-out var(--duration-base) var(--ease-in);
  --animation-scale-up: dz-scale-up var(--duration-base) var(--ease-spring-2);
  --animation-scale-down: dz-scale-down var(--duration-base) var(--ease-in);
  --animation-slide-in-up: dz-slide-in-up var(--duration-base) var(--ease-out);
  --animation-slide-in-down: dz-slide-in-down var(--duration-base) var(--ease-out);
  --animation-slide-in-left: dz-slide-in-left var(--duration-base) var(--ease-out);
  --animation-slide-in-right: dz-slide-in-right var(--duration-base) var(--ease-out);
  --animation-slide-out-up: dz-slide-out-up var(--duration-base) var(--ease-in);
  --animation-slide-out-down: dz-slide-out-down var(--duration-base) var(--ease-in);
  --animation-slide-out-left: dz-slide-out-left var(--duration-base) var(--ease-in);
  --animation-slide-out-right: dz-slide-out-right var(--duration-base) var(--ease-in);
  --animation-shake-x: dz-shake-x 0.4s var(--ease-out);
  --animation-pulse: dz-pulse 2s var(--ease-in-out) infinite;

  /* ─────────────────────────── widths ──────────────────────────────
   * Common content widths. */
  --width-prose: 65ch;
  --width-content: 56rem;
  --width-page: 80rem;

  /* ─────────────────────────── touch targets ───────────────────────
   * Minimum hit-area on touch devices. Apple HIG calls for 44×44pt;
   * Material recommends 48×48dp. We use 44px as a good middle —
   * matches Apple, hits Material's 80% threshold, doesn't visually
   * inflate dense desktop UIs.
   *
   * The min-size is enforced under `@media (pointer: coarse)` only
   * (see `components/touch-targets.css`) so desktop pixel-perfect
   * sizing is preserved while touch users get a comfortable target.
   *
   * #958 cycle 1.
   */
  --dz-touch-target-min: 44px;

  /* ─────────────────────────── colour ramps ────────────────────────
   * Raw OKLCH ramps lifted from Open Props
   * (props.colors-oklch.css), renamed to Dazzle's vocabulary. Use
   * semantic tokens (--colour-*) below for almost all styling;
   * raw ramps only when no semantic token fits.
   * ───────────────────────────────────────────────────────────────── */
  --neutral-50: oklch(98.5% 0.002 247.84);
  --neutral-100: oklch(96.7% 0.003 264.54);
  --neutral-200: oklch(92.8% 0.006 264.53);
  --neutral-300: oklch(86.9% 0.012 252.89);
  --neutral-400: oklch(70.6% 0.022 261.32);
  --neutral-500: oklch(55.1% 0.027 264.36);
  --neutral-600: oklch(44.6% 0.030 256.80);
  --neutral-700: oklch(37.3% 0.034 259.73);
  --neutral-800: oklch(27.8% 0.030 256.85);
  --neutral-900: oklch(21.0% 0.034 264.66);
  --neutral-950: oklch(13.0% 0.028 261.69);

  --brand-50: oklch(97.0% 0.014 254.60);
  --brand-100: oklch(93.2% 0.032 255.59);
  --brand-200: oklch(88.2% 0.059 254.13);
  --brand-300: oklch(80.9% 0.105 251.81);
  --brand-400: oklch(70.7% 0.165 254.62);
  --brand-500: oklch(62.3% 0.214 259.81);
  --brand-600: oklch(54.6% 0.245 262.88);
  --brand-700: oklch(48.8% 0.243 264.05);
  --brand-800: oklch(42.4% 0.199 265.64);
  --brand-900: oklch(37.9% 0.146 265.52);
  --brand-950: oklch(28.2% 0.091 267.94);

  --success-500: oklch(64.8% 0.150 142.50);
  --success-600: oklch(53.0% 0.140 142.50); /* 4.5:1 on the soft wash AND white (axe-verified) */
  --warning-500: oklch(76.9% 0.188 70.08);
  --warning-600: oklch(52.0% 0.150 70.08); /* 4.5:1 on the soft wash AND white (axe-verified) */
  --danger-500: oklch(63.7% 0.208 25.33);
  --danger-600: oklch(51.0% 0.195 25.33); /* 4.5:1 as text on wash/white; fills carry white text */

  /* ─────────────────────────── colour: semantic ────────────────────
   * Semantic tokens that components reference. Project themes
   * override THESE, not the raw ramps. light-dark() handles
   * scheme switching when supported; @media fallback below.
   * ───────────────────────────────────────────────────────────────── */
  --colour-bg: light-dark(var(--neutral-50), var(--neutral-950));
  --colour-surface: light-dark(white, var(--neutral-900));
  --colour-surface-muted: light-dark(var(--neutral-100), var(--neutral-800));
  --colour-text: light-dark(var(--neutral-900), var(--neutral-50));
  --colour-text-muted: light-dark(var(--neutral-600), var(--neutral-400));
  --colour-border: light-dark(var(--neutral-200), var(--neutral-700));
  --colour-brand: var(--brand-600);
  /* Brand as TEXT (badges/avatars/tags on washes): scheme-aware, unlike
     --colour-brand which stays the vivid FILL accent in both schemes. */
  --colour-brand-text: light-dark(var(--brand-600), var(--brand-300));
  --colour-brand-contrast: white;  /* foreground on filled brand fills (CTAs) */
  --colour-accent: var(--brand-500);
  /* Tone TEXT semantics are scheme-aware: the -600 steps carry 4.5:1 on
   light washes/white; dark mode needs LIGHT tone text on dark washes.
   Solid fills that carry white text must use the fixed ramp steps
   (--success-600 etc.), never these flippable text tokens. */
  --colour-success: light-dark(var(--success-600), oklch(74% 0.13 142.50));
  --colour-warning: light-dark(var(--warning-600), oklch(80% 0.13 70.08));
  --colour-danger: light-dark(var(--danger-600), oklch(75% 0.15 25.33));
  /* Info: cyan-leaning so chart series stay distinguishable from
   * --colour-brand (hue ~263). Scheme-stable, like the tone hues. */
  --colour-info: oklch(60% 0.13 220);

  /* Tone tints (background washes for surfaces) — semantic tokens
   * the dz-tones replacements consume. Authors don't hand-craft
   * `hsl(var(--success) / 0.08)` strings any more. */
  --colour-success-soft: light-dark(oklch(from var(--colour-success) 96.5% 0.04 h), oklch(from var(--colour-success) 28% 0.06 h));
  --colour-warning-soft: light-dark(oklch(from var(--colour-warning) 96.5% 0.04 h), oklch(from var(--colour-warning) 28% 0.06 h));
  --colour-danger-soft: light-dark(oklch(from var(--colour-danger) 96.5% 0.04 h), oklch(from var(--colour-danger) 28% 0.06 h));
  --colour-brand-soft: light-dark(oklch(from var(--colour-brand) 96.5% 0.04 h), oklch(from var(--colour-brand) 28% 0.06 h));

  /* Default colour-scheme advertisement — lets browser-chrome
   * (scrollbars, form controls) pick appropriate variants. */
  color-scheme: light dark;
}

/* #938 — bind `data-theme="dark"` (the JS toggle's output) to the
 * actual `color-scheme` property so `light-dark()` flips when the
 * user toggles. Without this, the toggle would set `data-theme` but
 * `light-dark()` would still resolve based on `prefers-color-scheme`,
 * leaving framework components stuck on the light variant of
 * `--colour-bg` / `--colour-text` / `--colour-border` even though
 * the topbar swapped its sun/moon icon. The marketing-site `site.js`
 * sets `style.colorScheme` imperatively for the same reason; this
 * CSS rule covers the in-app shell whose Alpine controller only
 * sets `data-theme`. */
[data-theme="dark"] {
  color-scheme: dark;
}
[data-theme="light"] {
  color-scheme: light;
}

/* Fallback for browsers without light-dark() support. Modern
 * Chrome / Firefox / Safari all support it as of 2024 but the
 * @supports gate keeps the door open for outliers. */
@media (prefers-color-scheme: dark) {
  @supports not (color: light-dark(white, black)) {
    :root {
      --colour-bg: var(--neutral-950);
      --colour-surface: var(--neutral-900);
      --colour-surface-muted: var(--neutral-800);
      --colour-text: var(--neutral-50);
      --colour-text-muted: var(--neutral-400);
      --colour-border: var(--neutral-700);
      --colour-success: oklch(74% 0.13 142.50);
      --colour-warning: oklch(80% 0.13 70.08);
      --colour-danger: oklch(75% 0.15 25.33);
      --colour-brand-text: var(--brand-300);
      --colour-success-soft: oklch(from var(--colour-success) 28% 0.06 h);
      --colour-warning-soft: oklch(from var(--colour-warning) 28% 0.06 h);
      --colour-danger-soft: oklch(from var(--colour-danger) 28% 0.06 h);
      --colour-brand-soft: oklch(from var(--colour-brand) 28% 0.06 h);
    }
  }
}

/* ─────────────────────────── animation keyframes ────────────────────
 * Backing keyframes for the `--animation-*` named tokens above.
 * Prefixed `dz-` so they don't collide with project-defined
 * keyframes. Open Props convention; #960 layer 1.
 *
 * Honoured by `prefers-reduced-motion: reduce` further down. */
@keyframes dz-fade-in {
  from { opacity: 0; }
}
@keyframes dz-fade-out {
  to { opacity: 0; }
}
@keyframes dz-scale-up {
  from { transform: scale(0.92); opacity: 0; }
}
@keyframes dz-scale-down {
  to { transform: scale(0.92); opacity: 0; }
}
@keyframes dz-slide-in-up {
  from { transform: translateY(8%); opacity: 0; }
}
@keyframes dz-slide-in-down {
  from { transform: translateY(-8%); opacity: 0; }
}
@keyframes dz-slide-in-left {
  from { transform: translateX(8%); opacity: 0; }
}
@keyframes dz-slide-in-right {
  from { transform: translateX(-8%); opacity: 0; }
}
@keyframes dz-slide-out-up {
  to { transform: translateY(-8%); opacity: 0; }
}
@keyframes dz-slide-out-down {
  to { transform: translateY(8%); opacity: 0; }
}
@keyframes dz-slide-out-left {
  to { transform: translateX(-8%); opacity: 0; }
}
@keyframes dz-slide-out-right {
  to { transform: translateX(8%); opacity: 0; }
}
@keyframes dz-shake-x {
  0%, 100% { transform: translateX(0); }
  20%, 60% { transform: translateX(-4px); }
  40%, 80% { transform: translateX(4px); }
}
@keyframes dz-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.5; }
}

/* Reduced-motion guard: collapse all dz-* animations to a near-instant
 * fade so users who request reduced motion still get state changes
 * without parallax/translate/scale. Operates on the keyframe name
 * (`animation-name`) so component-local @keyframes definitions
 * (dashboard, etc.) aren't affected by this rule. */
@media (prefers-reduced-motion: reduce) {
  [style*="animation"], [class*="dz-anim-"], .dz-toast {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
  }
}
/* --- hatchi-maxchi/components/accordion.css --- */
/* HYPERPART: accordion */
/*
 * dz-accordion — native <details> disclosure group.
 *
 * Zero JS. Single-open exclusivity is the HTML `name=` attribute: items
 * sharing a name auto-close their siblings (the shadcn `type="single"`
 * behaviour, with no controller). Drop `name` for an independent
 * (multi-open) group. Keyboard + AT semantics are the browser's own
 * <details>/<summary> — nothing to re-implement.
 */

.dz-accordion {
  border: 1px solid var(--colour-border);
  border-radius: var(--radius-md);
  background: var(--colour-surface);
  overflow: hidden;
}

.dz-accordion__item + .dz-accordion__item {
  border-block-start: 1px solid var(--colour-border);
}

.dz-accordion__trigger {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-md);
  padding: var(--space-md) var(--space-lg);
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  color: var(--colour-text);
  cursor: pointer;
  list-style: none;
  transition: background var(--duration-fast) var(--ease-out);
}

.dz-accordion__trigger::-webkit-details-marker { display: none; }

.dz-accordion__trigger:hover {
  background: var(--colour-surface-muted);
}

.dz-accordion__trigger:focus-visible {
  outline: var(--focus-ring-width) solid var(--focus-ring-color);
  /* inset: the accordion clips its corners (overflow:hidden), so an
     outward offset ring would be cropped at the item edges. */
  outline-offset: calc(var(--focus-ring-offset) * -1);
}

/* CSS chevron — inherits currentColor, rotates when the item is open. */
.dz-accordion__trigger::after {
  content: "";
  flex: none;
  width: 1rem;
  height: 1rem;
  background: currentColor;
  opacity: 0.55;
  mask: center / contain no-repeat
    url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6"/></svg>');
  transition: transform var(--duration-base) var(--ease-out);
}

.dz-accordion__item[open] .dz-accordion__trigger::after {
  transform: rotate(180deg);
}

.dz-accordion__panel {
  padding: 0 var(--space-lg) var(--space-md);
  font-size: var(--text-sm);
  color: var(--colour-text-muted);
  line-height: var(--leading-normal);
}

@media (prefers-reduced-motion: reduce) {
  .dz-accordion__trigger,
  .dz-accordion__trigger::after {
    transition: none;
  }
}
/* --- hatchi-maxchi/components/action-grid.css --- */
/* HYPERPART: action-grid */
/* Promoted VERBATIM from Dazzle regions.css (W1, fleet convergence).
 * The Dazzle emitter contract predates the move and is unchanged. */

/* ─────────────────────────── action_grid region ─────────────────
 * Responsive 1/2/3-col grid of CTA cards. Each card carries a
 * label, optional icon, optional count badge — and a tone token
 * (positive / warning / destructive / accent / neutral) routed
 * through dz-tones.css via data-dz-tone / data-dz-tone-badge
 * (unchanged). The hover/cursor affordance is gated on the
 * <a>-vs-<div> wrapper choice in the template; the .dz-action-card
 * rule covers the shared shape. */

.dz-action-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-md);
  margin-block-start: var(--space-sm);
}

@media (min-width: 40rem) {
  .dz-action-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (min-width: 64rem) {
  .dz-action-grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

.dz-action-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  padding: var(--space-md);
  border-radius: var(--radius-md);
  border: 1px solid var(--colour-border);
  background: var(--colour-surface);
  text-decoration: none;
  color: var(--colour-text);
  transition: background var(--duration-fast) var(--ease-out),
              border-color var(--duration-fast) var(--ease-out);
}

a.dz-action-card {
  cursor: pointer;
}

a.dz-action-card:hover {
  background: var(--colour-bg);
}

.dz-action-card-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-sm);
}

.dz-action-card-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.25rem;
  height: 1.25rem;
  color: var(--colour-text-muted);
}

.dz-action-card-icon-spacer {
  width: 1.25rem;
  /* height too — a label-only card (no icon, no count) otherwise
   * collapses its row to 0 height (the zero-paint gate's catch; the
   * bug shipped in Dazzle for as long as the spacer existed). */
  height: 1.25rem;
  display: inline-block;
}

.dz-action-card-count {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  padding-inline: var(--space-sm);
  padding-block: 0.125rem;
  border-radius: var(--radius-full);
}

.dz-action-card-label {
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  color: var(--colour-text);
  line-height: var(--leading-tight);
}

/* Tone tints (moved from Dazzle dz-tones.css with the component —
 * the tinting IS the component's point). */
/* v0.61.74 (#906 cleanup): action_grid was the second template using
 * the buried-dynamic-class pattern. Card surface tint + count-badge
 * tint both route via `data-dz-tone` / `data-dz-tone-badge`.
 * Hardcoded `hsl(145,55%,45%)` / `hsl(40,90%,55%)` literals in the
 * template are gone — both now use design-system slots so theming
 * applies. */

.dz-action-card[data-dz-tone="positive"] {
  border-color: var(--colour-success-soft);
  background: var(--colour-success-soft);
}
.dz-action-card[data-dz-tone="positive"]:hover {
  background: var(--colour-success-soft);
}
.dz-action-card[data-dz-tone="warning"] {
  border-color: var(--colour-warning-soft);
  background: var(--colour-warning-soft);
}
.dz-action-card[data-dz-tone="warning"]:hover {
  background: var(--colour-warning-soft);
}
.dz-action-card[data-dz-tone="destructive"] {
  border-color: var(--colour-danger-soft);
  background: var(--colour-danger-soft);
}
.dz-action-card[data-dz-tone="destructive"]:hover {
  background: var(--colour-danger-soft);
}
.dz-action-card[data-dz-tone="accent"] {
  border-color: var(--colour-brand-soft);
  background: var(--colour-brand-soft);
}
.dz-action-card[data-dz-tone="accent"]:hover {
  background: var(--colour-brand-soft);
}
.dz-action-card[data-dz-tone="neutral"],
.dz-action-card:not([data-dz-tone]) {
  border-color: var(--colour-border);
  background: var(--colour-surface);
}
.dz-action-card[data-dz-tone="neutral"]:hover,
.dz-action-card:not([data-dz-tone]):hover {
  background: color-mix(in oklab, var(--colour-surface-muted) 40%, transparent);
}

/* Count-badge tints — same five tones, slightly different opacities
 * so the badge reads as denser than the card surface. */
.dz-action-card-count[data-dz-tone-badge="positive"] {
  background: var(--colour-success-soft);
  color: var(--colour-success);
}
.dz-action-card-count[data-dz-tone-badge="warning"] {
  background: var(--colour-warning-soft);
  color: var(--colour-warning);
}
.dz-action-card-count[data-dz-tone-badge="destructive"] {
  background: var(--colour-danger-soft);
  color: var(--colour-danger);
}
.dz-action-card-count[data-dz-tone-badge="accent"] {
  background: var(--colour-brand-soft);
  /* the scheme-aware TEXT token (brand-600 light / brand-300 dark) —
   * raw --colour-brand fails contrast on the dark soft tint (axe). */
  color: var(--colour-brand-text);
}
.dz-action-card-count[data-dz-tone-badge="neutral"],
.dz-action-card-count:not([data-dz-tone-badge]) {
  background: var(--colour-surface-muted);
  color: var(--colour-text-muted);
}

/* ── Metric delta arrow colour (#884 / sibling #906 cleanup) ──────── */
/* v0.61.74: the period-over-period delta arrow used to compute its
 * tone via a dynamic Tailwind class string. Now driven by
 * `data-dz-delta-tone` matching the standard tone palette. The
 * hardcoded `hsl(142 76% 36%)` literal is gone — `--success` is the
 * canonical green slot. */

.dz-metric-delta[data-dz-delta-tone="positive"] {
  color: var(--colour-success);
}
.dz-metric-delta[data-dz-delta-tone="destructive"] {
  color: var(--colour-danger);
}
.dz-metric-delta[data-dz-delta-tone="neutral"],
.dz-metric-delta:not([data-dz-delta-tone]) {
  color: var(--colour-text-muted);
}

/* ── Status list pill + icon colour (#3 / roadmap #3) ─────────────── */

/* Pill backgrounds (rendered only for non-neutral states; neutral
 * entries omit the pill via template branch). */
/* --- hatchi-maxchi/components/activity-feed.css --- */
/* HYPERPART: activity-feed */
/* Promoted VERBATIM from Dazzle regions.css (W2, fleet convergence).
 * The Dazzle emitter contract predates the move and is unchanged. */

/* ─────────────────────────── activity_feed region ───────────────
 * Vertical timeline: dot on a left rail, time on the left of each
 * row, message bubble on the right. Bubble gets a hover-shadow
 * when wired to an action_url (HTMX-driven detail drawer open). */

.dz-activity-feed {
  position: relative;
  padding-inline-start: var(--space-md);
  border-inline-start: 1px solid var(--colour-border);
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  list-style: none;
  margin: 0;
}

.dz-activity-row {
  position: relative;
}

.dz-activity-dot {
  position: absolute;
  inset-inline-start: -1.3125rem;
  top: 0.375rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 0.75rem;
  height: 0.75rem;
}

.dz-activity-dot > svg {
  width: 0.75rem;
  height: 0.75rem;
  color: var(--colour-brand);
}

.dz-activity-row-inner {
  display: flex;
  gap: var(--space-md);
}

.dz-activity-time {
  font-size: var(--text-xs);
  color: var(--colour-text-muted);
  min-width: 5rem;
  padding-block-start: 0.25rem;
}

/* Bubble — no border (the dashboard slot already owns card chrome,
 * adding a border re-creates a card-within-a-card per #852). Subtle
 * background tint provides visual grouping. */
.dz-activity-bubble {
  flex: 1 1 0;
  padding-inline: var(--space-sm);
  padding-block: var(--space-xs);
  border-radius: var(--radius-sm);
  background: color-mix(in oklch, var(--colour-bg) 70%, transparent);
  font-size: var(--text-sm);
  color: var(--colour-text);
}

.dz-activity-bubble.is-clickable {
  cursor: pointer;
  transition: background var(--duration-fast) var(--ease-out);
}

.dz-activity-bubble.is-clickable:hover {
  background: var(--colour-bg);
}

.dz-activity-actor {
  font-weight: var(--weight-medium);
}

.dz-activity-empty {
  text-align: center;
  font-size: var(--text-sm);
  color: var(--colour-text-muted);
  padding-block: var(--space-2xl);
}
/* --- hatchi-maxchi/components/alert.css --- */
/*
 * dz-alert — HaTchi-MaXchi tranche 1 (spec: hatchi-maxchi-standalone).
 *
 * Identity signals: tone wash surface (--colour-*-soft) + registry icon +
 * text — colour never carries meaning alone (WCAG 1.4.1 as aesthetic).
 *
 * Contract:
 *   <div class="dz-alert" data-dz-tone="warning" role="alert">
 *     <span class="dz-alert__icon" aria-hidden="true"><svg…/></span>
 *     <div class="dz-alert__body">
 *       <div class="dz-alert__title">Heads up</div>
 *       <div class="dz-alert__description">Something needs attention.</div>
 *     </div>
 *   </div>
 * Tones: info (default) | success | warning | destructive | neutral.
 */

.dz-alert {
  display: flex;
  gap: var(--space-md);
  align-items: flex-start;
  padding: var(--space-md) var(--space-lg);
  border: 1px solid var(--colour-border);
  border-radius: var(--radius-md);
  background: var(--colour-surface);
  font-size: var(--text-sm);
}

.dz-alert__icon {
  display: inline-flex;
  width: 1.125rem;
  height: 1.125rem;
  flex-shrink: 0;
  margin-block-start: 0.125rem;
}

.dz-alert__icon svg {
  width: 100%;
  height: 100%;
  display: block;
}

.dz-alert__title {
  font-weight: var(--weight-semibold);
  margin-block-end: 0.125rem;
}

.dz-alert__description {
  color: var(--colour-text-muted);
  line-height: var(--leading-normal);
}

.dz-alert[data-dz-tone="info"] {
  background: var(--colour-brand-soft);
  border-color: color-mix(in oklab, var(--colour-brand) 25%, var(--colour-border));
}

.dz-alert[data-dz-tone="info"] .dz-alert__icon { color: var(--colour-brand); }

.dz-alert[data-dz-tone="success"] {
  background: var(--colour-success-soft);
  border-color: color-mix(in oklab, var(--colour-success) 25%, var(--colour-border));
}

.dz-alert[data-dz-tone="success"] .dz-alert__icon { color: var(--colour-success); }

.dz-alert[data-dz-tone="warning"] {
  background: var(--colour-warning-soft);
  border-color: color-mix(in oklab, var(--colour-warning) 25%, var(--colour-border));
}

.dz-alert[data-dz-tone="warning"] .dz-alert__icon { color: var(--colour-warning); }

.dz-alert[data-dz-tone="destructive"] {
  background: var(--colour-danger-soft);
  border-color: color-mix(in oklab, var(--colour-danger) 25%, var(--colour-border));
}

.dz-alert[data-dz-tone="destructive"] .dz-alert__icon { color: var(--colour-danger); }

/* ── dz-separator ────────────────────────────────────────────────────── */
/* HYPERPART: separator */

.dz-separator {
  border: none;
  border-block-start: 1px solid var(--colour-border);
  margin-block: var(--space-lg);
  /* The UA gives <hr> margin-inline auto; in a column flexbox those auto
   * margins absorb ALL free space and the rule's width collapses to zero
   * (rendered as a blank line). Pin the inline margins. */
  margin-inline: 0;
}

.dz-separator--vertical {
  display: inline-block;
  width: 0;
  align-self: stretch;
  border-block-start: none;
  border-inline-start: 1px solid var(--colour-border);
  margin-block: 0;
  margin-inline: var(--space-md);
}

/* ── data-dz-tooltip — CSS-only attribute tooltip ────────────────────── */

[data-dz-tooltip] {
  position: relative;
}

[data-dz-tooltip]::after {
  content: attr(data-dz-tooltip);
  position: absolute;
  inset-block-end: calc(100% + 0.375rem);
  inset-inline-start: 50%;
  translate: -50% 0;
  padding: 0.25rem 0.5rem;
  border-radius: var(--radius-sm);
  background: var(--colour-text);
  color: var(--colour-bg);
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  line-height: 1.3;
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  transition: opacity var(--duration-fast) var(--ease-out);
  z-index: 50;
}

[data-dz-tooltip]:hover::after,
[data-dz-tooltip]:focus-visible::after {
  opacity: 1;
  transition-delay: 300ms;
}

@media (prefers-reduced-motion: reduce) {
  [data-dz-tooltip]::after { transition: none; }
}

/* ── dz-menu — details-based dropdown (the hypermedia answer) ────────── */

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

.dz-menu > summary {
  list-style: none;
  cursor: pointer;
}

.dz-menu > summary::-webkit-details-marker { display: none; }

.dz-menu__panel {
  position: absolute;
  inset-block-start: calc(100% + 0.25rem);
  inset-inline-end: 0;
  min-width: 11rem;
  padding: var(--space-xs);
  background: var(--colour-surface);
  border: 1px solid var(--colour-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
  z-index: 40;
}

.dz-menu__item {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  width: 100%;
  padding: var(--space-sm) var(--space-md);
  border: none;
  border-radius: var(--radius-sm);
  background: none;
  color: var(--colour-text);
  font-size: var(--text-sm);
  text-align: start;
  text-decoration: none;
  cursor: pointer;
}

.dz-menu__item:hover {
  background: var(--colour-bg);
}

.dz-menu__item[data-dz-tone="destructive"] { color: var(--colour-danger); }

.dz-menu__separator {
  border: none;
  border-block-start: 1px solid var(--colour-border);
  margin-block: var(--space-xs);
}

/* ── dz-alert-dialog — designed hx-confirm surface ───────────────────── */
/* HYPERPART: confirm */
dialog.dz-alert-dialog {
  width: min(26rem, calc(100vw - 2rem));
  padding: var(--space-2xl);
  border: 1px solid var(--colour-border);
  border-radius: var(--radius-lg);
  background: var(--colour-surface);
  color: var(--colour-text);
  box-shadow: var(--shadow-lg);
  text-align: center;
}

dialog.dz-alert-dialog::backdrop {
  background: oklch(13% 0.028 261.69 / 0.5);
  backdrop-filter: blur(2px);
}

.dz-alert-dialog__icon {
  display: inline-flex;
  width: 2rem;
  height: 2rem;
  color: var(--colour-danger);
  margin-block-end: var(--space-md);
}

.dz-alert-dialog__icon svg { width: 100%; height: 100%; }

.dz-alert-dialog__title {
  font-size: var(--text-lg);
  font-weight: var(--weight-semibold);
  margin: 0 0 var(--space-sm);
}

.dz-alert-dialog__message {
  color: var(--colour-text-muted);
  font-size: var(--text-sm);
  margin: 0 0 var(--space-xl);
}

.dz-alert-dialog__actions {
  display: flex;
  gap: var(--space-md);
  justify-content: center;
}
/* --- hatchi-maxchi/components/app-shell.css --- */
/* HYPERPART: app-shell */
/* app-shell — the modern SaaS/admin application shell: persistent left
 * navigation, optional sticky top bar, routed main workspace, and a
 * responsive/collapsible sidebar. Promoted VERBATIM from Dazzle's
 * fragments.css (2026-07-06 directive: all core UX expression from HM);
 * the emitted contract (`dz-app-shell` + `data-dz-sidebar` state on the
 * root) predates the move and is unchanged.
 *
 * NB this is a COMPONENT, not a layout primitive — the 64rem media query
 * is deliberate: "persistent-collapsible on desktop, off-canvas overlay
 * on narrow" is viewport policy, not intrinsic wrapping. The layout
 * primitives inside the shell stay media-query-free.
 *
 * State contract: `data-dz-sidebar="open|closed"` on `.dz-app-shell`
 * (server-rendered from the `dz_sidebar` cookie; the dz-app-shell.js
 * controller flips it and re-writes the cookie). */

/* ─────────────────────────── layouts/app_shell ──────────────────
 * Main application shell with collapsible left sidebar, sticky top
 * bar, and main content area. Sidebar open/collapsed state is
 * exposed on the shell root via `data-dz-sidebar="open"` so CSS
 * selectors can drive both the slide transform AND the content
 * area's left padding offset. */

.dz-app-shell {
  /* Root container — sidebarOpen attribute lives here. */
}

.dz-app-content {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  transition: padding var(--duration-base) var(--ease-out);
}

@media (min-width: 64rem) {
  [data-dz-sidebar="open"] .dz-app-content {
    padding-inline-start: 16rem;  /* matches sidebar width */
  }
}

/* Top bar */
.dz-topbar {
  position: sticky;
  inset-block-start: 0;
  z-index: 30;
  display: flex;
  align-items: center;
  height: 3.5rem;
  padding-inline: var(--space-md);
  gap: var(--space-md);
  background: color-mix(in oklch, var(--colour-bg) 85%, transparent);
  backdrop-filter: blur(8px);
  border-block-end: 1px solid
    color-mix(in oklch, var(--colour-border) 50%, transparent);
  box-shadow: 0 1px 3px oklch(0% 0 0 / 0.04);
}

@media (min-width: 64rem) {
  .dz-topbar {
    padding-inline: var(--space-lg);
  }
}

.dz-topbar-leading,
.dz-topbar-trailing {
  flex: none;
  display: flex;
  align-items: center;
  gap: var(--space-sm);
}

/* #1294 — sidebar toggle (hamburger) at the start of the topbar leading
 * area. Flips `data-dz-sidebar` on `.dz-app-shell` via dz-alpine.js so the
 * sidebar nav is reachable + collapsible at every viewport. */
.dz-sidebar-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.25rem;
  height: 2.25rem;
  padding: 0;
  border: none;
  border-radius: var(--radius-md);
  background: transparent;
  color: var(--colour-text);
  cursor: pointer;
}

.dz-sidebar-toggle:hover {
  background: var(--colour-surface);
}

.dz-sidebar-toggle__icon,
.dz-sidebar-toggle__icon::before,
.dz-sidebar-toggle__icon::after {
  display: block;
  width: 1.125rem;
  height: 2px;
  background: currentColor;
  border-radius: 1px;
}

.dz-sidebar-toggle__icon {
  position: relative;
}

.dz-sidebar-toggle__icon::before,
.dz-sidebar-toggle__icon::after {
  content: "";
  position: absolute;
  inset-inline-start: 0;
}

.dz-sidebar-toggle__icon::before {
  inset-block-start: -6px;
}

.dz-sidebar-toggle__icon::after {
  inset-block-start: 6px;
}

.dz-topbar-title {
  flex: 1 1 0;
  min-width: 0;
}

.dz-topbar-title-text {
  font-size: var(--text-base);
  font-weight: var(--weight-semibold);
  color: var(--colour-text);
}

.dz-topbar-user {
  font-size: var(--text-sm);
  color: var(--colour-text-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 12.5rem;
  display: none;
}

@media (min-width: 40rem) {
  .dz-topbar-user {
    display: inline;
  }
}

/* Generic icon button used in top bar + sidebar header */
.dz-icon-button {
  width: 2rem;
  height: 2rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-sm);
  color: var(--colour-text-muted);
  background: transparent;
  border: 0;
  cursor: pointer;
  transition: background var(--duration-fast) var(--ease-out),
              color var(--duration-fast) var(--ease-out);
}

.dz-icon-button:hover {
  background: var(--colour-bg);
  color: var(--colour-text);
}

.dz-icon-button > svg {
  width: 1.25rem;
  height: 1.25rem;
}

/* Mobile-only and desktop-only icon variants */
.dz-icon-button.is-mobile-only { display: inline-flex; }
@media (min-width: 64rem) {
  .dz-icon-button.is-mobile-only { display: none; }
}

.dz-icon-button.is-desktop-only { display: none; }
@media (min-width: 64rem) {
  .dz-icon-button.is-desktop-only { display: inline-flex; }
}

/* Main content area */
.dz-app-main {
  flex: 1 1 0;
  padding: var(--space-md);
}

@media (min-width: 64rem) {
  .dz-app-main {
    padding: var(--space-lg);
  }
}

.dz-page-purpose {
  font-size: var(--text-sm);
  color: var(--colour-text-muted);
  margin-block: 0.25rem var(--space-md);
}

/* Mobile backdrop overlay */
.dz-app-backdrop {
  position: fixed;
  inset: 0;
  background: oklch(0% 0 0 / 0.4);
  z-index: 30;
}

@media (min-width: 64rem) {
  .dz-app-backdrop {
    display: none !important;
  }
}

/* Sidebar */
.dz-sidebar {
  position: fixed;
  inset-block: 0;
  inset-inline-start: 0;
  z-index: 40;
  width: 16rem;
  background: var(--colour-surface);
  border-inline-end: 1px solid var(--colour-border);
  display: flex;
  flex-direction: column;
  transform: translateX(-100%);
  transition: transform var(--duration-base) var(--ease-out);
}

[data-dz-sidebar="open"] .dz-sidebar {
  transform: translateX(0);
}

.dz-sidebar-brand {
  padding: var(--space-md);
  border-block-end: 1px solid var(--colour-border);
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.dz-sidebar-brand-text {
  font-size: var(--text-base);
  font-weight: var(--weight-semibold);
  color: var(--colour-text);
  margin: 0;
}

.dz-sidebar-nav {
  flex: 1 1 0;
  overflow-y: auto;
}

.dz-sidebar-nav-list {
  padding: 0.75rem;
  display: flex;
  flex-direction: column;
  gap: 0.125rem;
  list-style: none;
  margin: 0;
}

.dz-sidebar-nav-link {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  padding-inline: var(--space-md);
  padding-block: 0.375rem;
  border-radius: var(--radius-sm);
  font-size: var(--text-sm);
  color: var(--colour-text-muted);
  text-decoration: none;
  transition: background var(--duration-fast) var(--ease-out),
              color var(--duration-fast) var(--ease-out);
}

.dz-sidebar-nav-link:hover {
  background: var(--colour-bg);
  color: var(--colour-text);
}

/* Active link — keys off aria-current="page" so the visual state
 * and the accessibility attribute share one source of truth. */
.dz-sidebar-nav-link[aria-current="page"] {
  background: var(--colour-bg);
  color: var(--colour-text);
  font-weight: var(--weight-medium);
}

.dz-sidebar-nav-icon {
  width: 1rem;
  height: 1rem;
  flex-shrink: 0;
}

.dz-sidebar-nav-label {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Collapsible nav group */
.dz-sidebar-nav-group-summary {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  padding-inline: var(--space-md);
  padding-block: 0.375rem;
  border-radius: var(--radius-sm);
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  color: var(--colour-text);
  cursor: pointer;
  list-style: none;
  transition: background var(--duration-fast) var(--ease-out);
}

.dz-sidebar-nav-group-summary::-webkit-details-marker {
  display: none;
}

.dz-sidebar-nav-group-summary:hover {
  background: var(--colour-bg);
}

.dz-sidebar-nav-group-children {
  padding-inline-start: var(--space-md);
  margin-block-start: 0.125rem;
  display: flex;
  flex-direction: column;
  gap: 0.125rem;
  list-style: none;
}

/* Sidebar footer */
.dz-sidebar-footer {
  margin-block-start: auto;
  border-block-start: 1px solid var(--colour-border);
}

.dz-sidebar-user-block {
  padding: 0.75rem;
  padding-block-end: 0.25rem;
}

.dz-sidebar-user-name {
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  color: var(--colour-text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  padding-inline: var(--space-md);
}

.dz-sidebar-action-row {
  margin-block-start: 0.25rem;
}

.dz-sidebar-action-button {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  width: 100%;
  padding-inline: var(--space-md);
  padding-block: 0.375rem;
  border-radius: var(--radius-sm);
  font-size: var(--text-sm);
  color: var(--colour-text-muted);
  background: transparent;
  border: 0;
  cursor: pointer;
  transition: background var(--duration-fast) var(--ease-out),
              color var(--duration-fast) var(--ease-out);
}

.dz-sidebar-action-button:hover {
  background: var(--colour-bg);
  color: var(--colour-text);
}

.dz-sidebar-action-button > svg {
  width: 1rem;
  height: 1rem;
  flex-shrink: 0;
}

.dz-sidebar-darkmode {
  padding: 0.75rem;
  padding-block-start: 0.25rem;
  display: none;
}

@media (min-width: 64rem) {
  .dz-sidebar-darkmode {
    display: block;
  }
}
/* --- hatchi-maxchi/components/badge.css --- */
/*
 * badge.css — status / state / enum badge component.
 *
 * Single semantic class (.dz-badge) plus tone modifier via
 * data-dz-tone attribute. Used by the render_status_badge macro
 * (templates/macros/status_badge.html) — 19 import sites across
 * tables, kanban cards, detail views, review queues.
 *
 * The macro owns the tone-resolution logic (Jinja `badge_tone`
 * filter resolves enum values to one of five tones); this CSS
 * file owns the visual styling. Authors don't think about
 * specific colour values — they invoke the macro with a value
 * and trust the design system.
 *
 * Wrapped in @layer components (declared in dazzle.css). All
 * lengths and colours route via design tokens — Stylelint enforces
 * this in components/.
 */

.dz-badge {
  display: inline-flex;
  align-items: center;
  border-radius: var(--radius-sm);
  font-weight: var(--weight-medium);
  /* Defaults match the macro's `size="md"` branch. The .dz-badge-sm
   * modifier overrides for dense regions (timeline, bar_chart). */
  padding-inline: var(--space-sm);
  height: 1.25rem;
  font-size: var(--text-xs);
}

.dz-badge-sm {
  padding-inline: var(--space-xs);
  height: 1rem;
  /* Slightly smaller than the default --text-xs for micro contexts. */
  font-size: 0.625rem;
  line-height: 1;
}

/* ── WCAG colour+icon+text (#1493 slice 2 part 3) ─────────────────
 * Non-neutral tones lead with a decorative glyph so state is never
 * conveyed by colour alone (WCAG 1.4.1). Emitted by `badge_icon_html`
 * as the first child of .dz-badge; neutral badges carry no icon. */

.dz-badge-icon {
  margin-inline-end: var(--space-xs);
  font-size: 0.875em;
  line-height: 1;
}

/* ── Tone tints ───────────────────────────────────────────────────
 * Five tone tokens matching the wider Dazzle palette: neutral
 * (default), success, warning, info, destructive. Selected via
 * `data-dz-tone` attribute emitted by the macro. */

.dz-badge[data-dz-tone="success"] {
  background: var(--colour-success-soft);
  color: var(--colour-success);
}

.dz-badge[data-dz-tone="warning"] {
  background: var(--colour-warning-soft);
  color: var(--colour-warning);
}

.dz-badge[data-dz-tone="info"] {
  background: var(--colour-brand-soft);
  color: var(--colour-brand-text);
}

.dz-badge[data-dz-tone="destructive"] {
  background: var(--colour-danger-soft);
  color: var(--colour-danger);
}

.dz-badge[data-dz-tone="neutral"],
.dz-badge:not([data-dz-tone]) {
  background: var(--colour-bg);
  color: var(--colour-text-muted);
}

/* ── Optional border ──────────────────────────────────────────────
 * The macro accepts a `bordered` boolean for emphasis (used in the
 * detail region). Border colour matches the tone. */

.dz-badge.bordered {
  border: 1px solid var(--colour-border);
}

.dz-badge.bordered[data-dz-tone="success"] {
  border-color: var(--colour-success);
}
.dz-badge.bordered[data-dz-tone="warning"] {
  border-color: var(--colour-warning);
}
.dz-badge.bordered[data-dz-tone="info"] {
  border-color: var(--colour-brand);
}
.dz-badge.bordered[data-dz-tone="destructive"] {
  border-color: var(--colour-danger);
}

/* ── Em-dash placeholder ──────────────────────────────────────────
 * When the macro is called with empty value, it renders a single-
 * char placeholder (—) for absent status. Same muted treatment as
 * any other "no value" token. */

.dz-badge-empty {
  color: var(--colour-text-muted);
  font-size: var(--text-xs);
}

/* Registry SVG badge glyphs (Phase 3, supersedes #1493 entity glyphs). */
.dz-badge-icon {
  display: inline-flex;
  width: 0.875em;
  height: 0.875em;
  flex-shrink: 0;
}

.dz-badge-icon svg {
  width: 100%;
  height: 100%;
  display: block;
}
/* --- hatchi-maxchi/components/bar-chart.css --- */
/* HYPERPART: bar-chart */
/* Promoted VERBATIM from Dazzle regions.css (Tier C-a, fleet
 * convergence). The Dazzle emitter contract is unchanged. */

/* ─────────────────────────── bar_chart region ───────────────────
 * Horizontal bar rows: label (right) / track / value (right). The
 * track has rounded-full chrome; the fill is brand-coloured and
 * its width is the inline value-derived percentage. */

.dz-bar-chart-bars {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  margin-block-start: var(--space-sm);
}

.dz-bar-chart-row {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
}

.dz-bar-chart-label {
  width: 6rem;
  text-align: end;
  font-size: var(--text-xs);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.dz-bar-chart-label.is-wide {
  width: 7rem;
  color: var(--colour-text-muted);
}

.dz-bar-chart-track {
  flex: 1 1 0;
  height: 1.25rem;
  background: var(--colour-bg);
  border-radius: var(--radius-full);
  overflow: hidden;
}

.dz-bar-chart-fill {
  height: 100%;
  background: var(--colour-brand);
  border-radius: var(--radius-full);
  transition: width var(--duration-base) var(--ease-out);
}

.dz-bar-chart-value {
  width: 2.5rem;
  text-align: end;
  font-size: var(--text-xs);
  font-family: var(--font-mono);
  color: var(--colour-text-muted);
}

.dz-bar-chart-value.is-wide {
  width: 2.5rem;
  color: var(--colour-text);
}

.dz-bar-chart-summary {
  font-size: var(--text-xs);
  color: var(--colour-text-muted);
  margin-block-start: var(--space-sm);
}
/* --- hatchi-maxchi/components/bar-track.css --- */
/* HYPERPART: bar-track */
/* Promoted VERBATIM from Dazzle regions.css (Tier C-b, fleet
 * convergence). The Dazzle emitter contract is unchanged. */

/* ─────────────────────────── bar_track region ───────────────────
 * Compact horizontal value bars, one per group. Three columns per
 * row: fixed-width label (right-aligned) | flex-grow track | value. */

.dz-bar-track-rows {
  display: flex;
  flex-direction: column;
  gap: 0.375rem;
  margin-block-start: var(--space-sm);
}

.dz-bar-track-row {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  font-size: var(--text-xs);
}

.dz-bar-track-label {
  width: 7rem;
  text-align: end;
  color: var(--colour-text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.dz-bar-track {
  flex: 1 1 0;
  position: relative;
  height: 1rem;
  background: var(--colour-bg);
  border-radius: var(--radius-full);
  overflow: hidden;
}

.dz-bar-track-fill {
  position: absolute;
  inset-block: 0;
  inset-inline-start: 0;
  background: var(--colour-brand);
  border-radius: var(--radius-full);
  transition: width var(--duration-base) var(--ease-out);
}

.dz-bar-track-value {
  width: 4rem;
  text-align: end;
  font-family: var(--font-mono);
  color: var(--colour-text-muted);
}

.dz-bar-track-summary {
  font-size: var(--text-xs);
  color: var(--colour-text-muted);
  margin-block-start: var(--space-sm);
  font-family: var(--font-mono);
}
/* --- hatchi-maxchi/components/box-plot.css --- */
/* HYPERPART: box-plot */
/* Promoted VERBATIM from Dazzle regions.css (Tier C-b, fleet
 * convergence). The Dazzle emitter contract is unchanged. */

/* ─────────────────────────── box_plot region ────────────────────
 * Server-rendered SVG quartile boxes per group. SVG fill/stroke
 * tokens stay inline (per-element shape colour); host wrapper sized
 * via .dz-box-plot-svg + summary uses .dz-box-plot-summary. */

.dz-box-plot-svg {
  width: 100%;
  height: auto;
}

.dz-box-plot-summary {
  font-size: var(--text-xs);
  color: var(--colour-text-muted);
  margin-block-start: var(--space-sm);
  font-family: var(--font-mono);
}
/* --- hatchi-maxchi/components/bullet.css --- */
/* HYPERPART: bullet */
/* Promoted VERBATIM from Dazzle regions.css (Tier C-b, fleet
 * convergence). The Dazzle emitter contract is unchanged. */

/* ─────────────────────────── bullet chart region ───────────────
 * Stephen Few bullet rows. Three fixed columns: label (right) /
 * track / value. Track contains: optional reference bands behind,
 * actual bar, target tick. Band/bar/tick are absolutely positioned
 * inside the relative track. */

.dz-bullet-rows {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  margin-block-start: var(--space-sm);
}

.dz-bullet-row {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  font-size: var(--text-xs);
}

.dz-bullet-label {
  width: 6rem;
  text-align: end;
  color: var(--colour-text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.dz-bullet-track {
  flex: 1 1 0;
  position: relative;
  height: 1.25rem;
  background: var(--colour-bg);
  border-radius: var(--radius-sm);
  overflow: hidden;
}

.dz-bullet-band {
  position: absolute;
  inset-block: 0;
  opacity: 0.18;
}

.dz-bullet-actual {
  position: absolute;
  inset-block: 0.25rem;
  inset-inline-start: 0;
  background: var(--colour-brand);
  border-radius: var(--radius-sm);
}

.dz-bullet-target {
  position: absolute;
  inset-block: 0;
  width: 2px;
  background: var(--colour-text);
}

.dz-bullet-value {
  width: 4rem;
  text-align: end;
  font-family: var(--font-mono);
  color: var(--colour-text-muted);
}

.dz-bullet-summary {
  font-size: var(--text-xs);
  color: var(--colour-text-muted);
  margin-block-start: var(--space-sm);
  font-family: var(--font-mono);
}
/* --- hatchi-maxchi/components/button.css --- */
/*
 * button.css — button component family.
 *
 * One semantic class `.dz-button` + a `data-dz-variant` attribute for the
 * visual variant (canonical, matching `data-dz-tone` on badges/alerts):
 *   data-dz-variant="ghost"        no border, hover-tint only (back, cancel)
 *   data-dz-variant="outline"      bordered neutral (default action button)
 *   data-dz-variant="destructive"  bordered danger (delete, revoke)
 *   data-dz-variant="primary"      filled brand (the recommended action)
 * and `data-dz-size="sm"` for the compact inline-row size.
 *
 * A variant NEVER drops the base class — `.dz-button` carries the shared
 * geometry, `[data-dz-variant]` layers the skin. (The standalone
 * `.dz-button-primary` rule in components/table.css is a separate, smaller
 * table-header variant and is intentionally left as a class.)
 *
 * The `<button>` element baseline in base.css already provides focus ring,
 * disabled state, font inheritance — this extends it with size + variant.
 *
 * Wrapped in @layer components.
 */

.dz-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 2rem;
  padding-inline: var(--space-md);
  border-radius: var(--radius-sm);
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  cursor: pointer;
  text-decoration: none;
  transition:
    background var(--duration-fast) var(--ease-out),
    border-color var(--duration-fast) var(--ease-out),
    color var(--duration-fast) var(--ease-out);
}

.dz-button[data-dz-variant="ghost"] {
  background: transparent;
  border: 1px solid transparent;
  color: var(--colour-text-muted);
}

.dz-button[data-dz-variant="ghost"]:hover {
  background: var(--colour-bg);
  color: var(--colour-text);
}

.dz-button[data-dz-variant="outline"] {
  background: var(--colour-surface);
  color: var(--colour-text);
  border: 1px solid var(--colour-border);
}

.dz-button[data-dz-variant="outline"]:hover {
  background: var(--colour-bg);
}

.dz-button[data-dz-variant="destructive"] {
  background: var(--colour-surface);
  color: var(--colour-danger);
  border: 1px solid var(--colour-danger);
}

.dz-button[data-dz-variant="destructive"]:hover {
  background: var(--colour-danger-soft);
}

.dz-button[data-dz-variant="primary"] {
  background: var(--colour-brand);
  color: var(--colour-brand-contrast);
  border: 1px solid var(--colour-brand);
  box-shadow: var(--shadow-sm);
}

/* TASTE-2/8: explicit hover step from the ramp (not a brightness filter)
   and a physical press — shadow collapses, button settles 0.5px. */
.dz-button[data-dz-variant="primary"]:hover {
  background: var(--brand-700);
  border-color: var(--brand-700);
}

.dz-button[data-dz-variant="primary"]:active {
  box-shadow: none;
  transform: translateY(0.5px);
}

.dz-button[data-dz-variant="primary"]:disabled,
.dz-button[data-dz-variant="primary"][aria-disabled="true"] {
  opacity: 0.6;
  pointer-events: none;
}

/* Size modifier — smaller button used by inline action rows
 * (e.g. 2FA settings row buttons). Composes with .dz-button base. */
.dz-button[data-dz-size="sm"] {
  height: 1.75rem;
  padding-inline: var(--space-md);
  font-size: var(--text-xs);
}
/* --- hatchi-maxchi/components/chart-legend.css --- */
/* HYPERPART: chart-legend */
/* Promoted VERBATIM from Dazzle regions.css (Tier C-a, fleet
 * convergence). The Dazzle emitter contract is unchanged. */

/* ─────────────────────────── chart legend / summary ─────────────
 * Shared across the SVG chart family (line / area / radar). Each
 * chart template renders its own SVG body but ends with a legend
 * row of (swatch + series-name) chips and a summary line of
 * sample/series counts. Single class set instead of duplicating
 * inline `flex flex-wrap gap-x-3 ...` Tailwind across every
 * template. */

.dz-chart-svg {
  width: 100%;
  height: auto;
}

/* Radar-specific: long axis labels at the -90°/90° spokes can extend
 * past the SVG viewBox because the rotation pivot of the existing
 * spoke-label markup doesn't perfectly counter-translate. Allow the
 * labels to render past the SVG bounds rather than clipping mid-word
 * (closes #917). The radar SVG is always the rightmost element in its
 * card, and CSS Grid gives each card an isolated overflow region, so
 * letting labels spill past the SVG won't bleed into neighbouring
 * regions.
 *
 * Note: trig-placed labels (per the issue's "option 1") would
 * eliminate the need for this rule entirely. Worth a follow-up; this
 * is the smaller-blast-radius fix while the template still uses the
 * nested-rotate workaround. */
.dz-radar-svg {
  overflow: visible;
}

.dz-chart-legend {
  display: flex;
  flex-wrap: wrap;
  column-gap: var(--space-md);
  row-gap: 0.25rem;
  margin-block-start: var(--space-sm);
  font-size: var(--text-xs);
  color: var(--colour-text-muted);
}

.dz-chart-legend-item {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
}

.dz-chart-legend-swatch {
  display: inline-block;
  width: 0.625rem;
  height: 0.625rem;
  border-radius: var(--radius-sm);
}

.dz-chart-legend-name {
  font-family: var(--font-mono);
}

.dz-chart-summary {
  font-size: var(--text-xs);
  color: var(--colour-text-muted);
  margin-block-start: var(--space-sm);
  font-family: var(--font-mono);
}

/* The dz-chart-degenerate-* fallback family that lived here was DEAD
 * on arrival (Jinja-era shapes retired with ADR-0023; zero emitters in
 * the typed substrate) — dropped rather than published as fake API. */
/* --- hatchi-maxchi/components/combobox.css --- */
/* HYPERPART: combobox */
/* dz-combobox — HM-native searchable enum single-select (HMC-018 slice 1).
 *
 * Progressive enhancement over a real <select data-dz-combobox>: with JS
 * off the native select is fully usable (submits, native required). On
 * first interaction controllers/dz-combobox.js wraps it in a `.dz-combobox`
 * root — a role=combobox text input + a role=listbox <ul> of options — and
 * hides the native select (kept in the DOM as the submit value + no-JS
 * fallback). State lives in the DOM as `data-dz-open` on the ROOT; the
 * sibling rule below hides the listbox off the attribute (same idiom as
 * search-select.css). Chrome mirrors the search-select family. */

.dz-combobox {
  position: relative;
  width: 100%;
}

/* Native select is the no-JS control; once the controller wraps it and
 * marks the root `data-dz-enhanced`, the visible combobox input takes
 * over and the native select is removed from the visual layer (it stays
 * in the DOM to carry the submitted value). */
.dz-combobox[data-dz-enhanced] > select[data-dz-combobox] {
  display: none;
}

.dz-combobox-input {
  width: 100%;
  height: 2rem;
  padding-inline-start: var(--space-md);
  padding-inline-end: 2rem;
  border-radius: var(--radius-sm);
  background: var(--colour-surface);
  border: 1px solid var(--colour-border);
  font-size: var(--text-sm);
  color: var(--colour-text);
  outline: none;
  transition:
    box-shadow var(--duration-base) var(--ease-out),
    border-color var(--duration-base) var(--ease-out);
}

.dz-combobox-input::placeholder {
  color: var(--colour-text-muted);
}

.dz-combobox-input:focus-visible {
  box-shadow: 0 0 0 1px var(--colour-brand);
  border-color: var(--colour-brand);
}

.dz-combobox-input[aria-invalid="true"] {
  border-color: var(--colour-danger);
}

.dz-combobox-input[aria-invalid="true"]:focus-visible {
  box-shadow: 0 0 0 1px var(--colour-danger);
}

.dz-combobox-listbox {
  position: absolute;
  z-index: 50;
  inset-inline: 0;
  margin-block-start: 0.25rem;
  padding: 0;
  max-height: 15rem;
  overflow-y: auto;
  list-style: none;
  background: var(--colour-surface);
  border: 1px solid var(--colour-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
}

.dz-combobox-option {
  display: block;
  padding-inline: var(--space-md);
  padding-block: var(--space-sm);
  font-size: var(--text-sm);
  color: var(--colour-text);
  cursor: pointer;
}

/* A filtered-out option is removed from flow (the `hidden` attribute the
 * controller toggles) — restated because the row's own `display` would
 * otherwise win over the UA `[hidden]` rule. */
.dz-combobox-option[hidden] {
  display: none;
}

/* Hover + keyboard active-descendant highlight share one wash; the chosen
 * option carries aria-selected for the persistent tick of state. */
.dz-combobox-option:hover,
.dz-combobox-option[data-dz-active="true"] {
  background: var(--colour-surface-muted);
}

.dz-combobox-option[aria-selected="true"] {
  color: var(--colour-brand);
  font-weight: 500;
}

.dz-combobox-empty {
  padding-inline: var(--space-md);
  padding-block: var(--space-md);
  font-size: var(--text-sm);
  color: var(--colour-text-muted);
}

/* Listbox visibility rides `data-dz-open` on the ROOT (set by
 * dz-combobox.js) — closed until focus/typing opens it, closed 200ms
 * after focus leaves the widget. aria-expanded on the input is the a11y
 * mirror. */
.dz-combobox:not([data-dz-open="true"]) .dz-combobox-listbox {
  display: none;
}
/* --- hatchi-maxchi/components/confirm-panel.css --- */
/* HYPERPART: confirm-panel */
/* Promoted from Dazzle regions.css (Tier F, fleet convergence). Two
 * deliberate deltas from the source: (1) the
 * `.dz-confirm-primary.is-disabled` selector was dropped — the gate
 * state now lives SOLELY in `aria-disabled` (state-in-DOM, set by
 * controllers/dz-confirm-gate.js, which replaces the Alpine
 * `dzConfirmGate` island); (2) `.dz-confirm-title`/`.dz-confirm-caption`
 * gained `display: block` — the emitter's divs-in-label became spans
 * (a <label> only admits phrasing content; the gallery vnu gate caught
 * the invalid nesting the legacy template shipped). */

/* ─────────────────────────── confirm panel ──────────────────────
 * Irreversible-action consent primitive. Renders a checklist of
 * obligations + dual primary/draft buttons in the off state, a
 * "Currently live" summary + revoke in the live state, and an audit
 * footer + (optional) re-enable button in the revoked state. State
 * branch keys off the panel root's data-dz-state-value so future
 * enrichments (e.g. tone tinting) can key off it without touching
 * every branch's class string. */

/* #1546: the wrapper's card chrome — previously an accidental cascade
 * dependency on a DORMANT Dazzle block (dead since the Jinja teardown);
 * the Hyperpart is self-sufficient now. Values verbatim from the
 * shipped bundle, so in-app pixels are unchanged. */
.dz-confirm-panel {
  background: var(--colour-surface);
  border: 1px solid var(--colour-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
  max-inline-size: 24rem;
  inline-size: 100%;
  padding: var(--space-lg);
}

.dz-confirm-summary {
  padding: var(--space-md);
  border-radius: var(--radius-md);
  border: 1px solid var(--colour-border);
  margin-block-end: var(--space-md);
}

.dz-confirm-summary[data-dz-confirm-tone="success"] {
  border-color: color-mix(in oklch, var(--colour-success) 40%, transparent);
  background: color-mix(in oklch, var(--colour-success) 6%, transparent);
}

.dz-confirm-summary[data-dz-confirm-tone="muted"] {
  background: color-mix(in oklch, var(--colour-bg) 60%, transparent);
}

.dz-confirm-summary-title {
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  color: var(--colour-text);
}

.dz-confirm-summary-body {
  font-size: var(--text-xs);
  color: var(--colour-text-muted);
  margin-block-start: 0.125rem;
}

.dz-confirm-checklist {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  list-style: none;
  margin: 0 0 var(--space-md);
  padding-inline-start: 0;
}

.dz-confirm-row {
  display: flex;
  align-items: flex-start;
  gap: var(--space-sm);
}

.dz-confirm-checkbox {
  margin-block-start: 0.125rem;
  flex-shrink: 0;
  width: 1rem;
  height: 1rem;
  border-radius: var(--radius-sm);
  border: 1px solid var(--colour-border);
  accent-color: var(--colour-brand);
}

.dz-confirm-row-label {
  flex: 1 1 0;
  cursor: pointer;
}

.dz-confirm-title {
  /* span (a <label> only admits phrasing content) styled as block */
  display: block;
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold); /* #1546: folded from the dormant block (shipped look) */
  line-height: var(--leading-tight);
  color: var(--colour-text);
  margin-block-end: var(--space-xs);
}

.dz-confirm-caption {
  display: block;
  font-size: var(--text-xs);
  line-height: var(--leading-tight);
  color: var(--colour-text-muted);
  margin-block-start: 0.125rem;
}

.dz-confirm-actions {
  display: flex;
  justify-content: flex-end;
  gap: var(--space-sm);
  margin-block-start: var(--space-sm);
  list-style: none;
}

.dz-confirm-primary {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding-inline: var(--space-md);
  padding-block: var(--space-sm);
  border-radius: var(--radius-sm);
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  background: var(--colour-brand);
  color: var(--colour-brand-contrast);
  text-decoration: none;
  border: 0;
  cursor: pointer;
  transition: opacity var(--duration-fast) var(--ease-out);
}

.dz-confirm-primary:hover {
  opacity: 0.9;
}

/* While the gate is unsatisfied the controller keeps aria-disabled on
 * the primary (and withholds its href) — muted-tone surface, pointer
 * events off, so the a-tag-as-button affordance falls inert. */
.dz-confirm-primary[aria-disabled="true"] {
  background: var(--colour-bg);
  color: var(--colour-text-muted);
  cursor: not-allowed;
  pointer-events: none;
}

.dz-confirm-secondary {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding-inline: var(--space-md);
  padding-block: var(--space-sm);
  border-radius: var(--radius-sm);
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  border: 1px solid var(--colour-border);
  background: transparent;
  color: var(--colour-text);
  text-decoration: none;
  cursor: pointer;
  transition: background var(--duration-fast) var(--ease-out);
}

.dz-confirm-secondary:hover {
  background: var(--colour-bg);
}

.dz-confirm-revoke {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding-inline: var(--space-md);
  padding-block: var(--space-sm);
  border-radius: var(--radius-sm);
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  border: 1px solid color-mix(in oklch, var(--colour-danger) 50%, transparent);
  background: transparent;
  color: var(--colour-danger);
  text-decoration: none;
  cursor: pointer;
  transition: background var(--duration-fast) var(--ease-out);
}

.dz-confirm-revoke:hover {
  background: color-mix(in oklch, var(--colour-danger) 8%, transparent);
}

.dz-confirm-audit {
  font-size: var(--text-xs);
  color: var(--colour-text-muted);
  margin-block-start: var(--space-md);
  line-height: var(--leading-tight);
}
/* --- hatchi-maxchi/components/dashboard-card.css --- */
/* Internal dashboard chrome (not a standalone gallery Hyperpart — no HYPERPART
 * marker, matching the other migrated chrome components detail/fragments/etc.). */
/* Moved from Dazzle dashboard.css (HMC-007d) — the dashboard card grid +
 * card chrome (wrapper/header/actions/titles/notice/body/skeleton/resize) +
 * add-card CTA. Byte-faithful, HM-tokenised. The dz-pulse keyframe used by the
 * skeleton is HM's (tokens.css); .dz-card base is unique to this file. */

/* ─────────────────────────── card grid ──────────────────────────
 * 1-col on mobile, 12-col on >=md. items-start to collapse to
 * intrinsic content height (#844). */

.dz-dashboard-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-md);
  align-items: start;
}

@media (min-width: 48rem) {
  .dz-dashboard-grid {
    grid-template-columns: repeat(12, minmax(0, 1fr));
  }
}

/* ─────────────────────────── card wrapper ───────────────────────
 * Layout/positioning element only. Visible card chrome lives on
 * the inner <article>. Wrapper stays square (no rounding) so the
 * focus ring traces a clean rectangle (#794). */

.dz-card-wrapper {
  position: relative;
  outline: none;
}

.dz-card-wrapper:focus {
  box-shadow: 0 0 0 2px var(--colour-surface), 0 0 0 4px var(--colour-brand);
}

/* Smooth transitions on all reposition/resize except while dragging
 * (the wrapper sets style.transform directly during drag). Toggled
 * via .is-animating class (Alpine sets it when not dragging and not
 * mid-drag). */
.dz-card-wrapper.is-animating {
  transition: all var(--duration-base) var(--ease-out);
}

/* ─────────────────────────── card chrome ────────────────────────
 * The visible card surface. Default border + bg + clipping. When
 * resizing, swap to a dashed brand-coloured border. */

.dz-card {
  border-radius: var(--radius-md);
  border: 1px solid var(--colour-border);
  background: var(--colour-surface);
  /* TASTE-2: quiet stacked shadow at rest — elevation reads as a surface,
     not decoration; hover transition already declared below. */
  box-shadow: var(--shadow-sm);
  overflow: hidden;
  transition:
    border-color var(--duration-fast) var(--ease-out),
    box-shadow var(--duration-fast) var(--ease-out);
}

.dz-card.is-resizing {
  border-style: dashed;
  border-color: var(--colour-brand);
}

/* Card header — drag handle zone. */
.dz-card-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-inline: var(--space-md);
  padding-block: var(--space-sm);
  cursor: grab;
  min-height: 2.25rem;
}

.dz-card-header:active {
  cursor: grabbing;
}

.dz-card-header.is-dragging {
  cursor: grabbing;
}

.dz-card-titles {
  display: flex;
  flex-direction: column;
}

.dz-card-eyebrow {
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--colour-text-muted);
  line-height: 1;
}

.dz-card-title {
  font-size: var(--text-base);
  font-weight: var(--weight-medium);
  color: var(--colour-text);
  user-select: none;
  margin: 0;
}

/* Header actions — fade in on hover/focus-within of the wrapper.
 * Was opacity-0 + group-hover only; #799 set rest opacity to 0.6 so
 * touch + keyboard users can still see them. */
.dz-card-actions {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  opacity: 0.6;
  transition: opacity var(--duration-fast) var(--ease-out);
}

.dz-card-wrapper:hover .dz-card-actions,
.dz-card-wrapper:focus-within .dz-card-actions {
  opacity: 1;
}

.dz-card-action-button {
  width: 1.5rem;
  height: 1.5rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-sm);
  color: var(--colour-text-muted);
  background: transparent;
  border: 0;
  cursor: pointer;
  transition:
    background var(--duration-fast) var(--ease-out),
    color var(--duration-fast) var(--ease-out);
}

.dz-card-action-button:hover {
  background: var(--colour-bg);
  color: var(--colour-text);
}

/* Notice band — between header and body. Layout shape here; the tone tints
 * (below) travel with the component (migrated from Dazzle dz-tones.css, HMC-005b
 * 2026-07-09 — the last of the dz-tones tint families). `.dz-notice-band` is the
 * visual carrier the emitter co-emits with `.dz-card-notice`. */
.dz-card-notice {
  margin-inline: var(--space-md);
  margin-block-end: var(--space-sm);
  padding-inline: var(--space-sm);
  padding-block: var(--space-xs);
  border-radius: var(--radius-sm);
  border-inline-start-width: 2px;
  border-inline-start-style: solid;
}

/* Notice-band tone tints — background + left-rail colour per data-dz-notice-tone.
 * Static rules (not Tailwind JIT) so IR-render-time tone class strings still
 * paint; tokens follow any project theme override. */
.dz-notice-band[data-dz-notice-tone="positive"] {
  background: color-mix(in oklab, var(--colour-success) 8%, transparent);
  border-left-color: var(--colour-success);
}
.dz-notice-band[data-dz-notice-tone="warning"] {
  background: color-mix(in oklab, var(--colour-warning) 10%, transparent);
  border-left-color: var(--colour-warning);
}
.dz-notice-band[data-dz-notice-tone="destructive"] {
  background: color-mix(in oklab, var(--colour-danger) 8%, transparent);
  border-left-color: var(--colour-danger);
}
.dz-notice-band[data-dz-notice-tone="accent"] {
  background: color-mix(in oklab, var(--colour-brand) 8%, transparent);
  border-left-color: var(--colour-brand);
}
.dz-notice-band[data-dz-notice-tone="neutral"],
.dz-notice-band:not([data-dz-notice-tone]) {
  background: color-mix(in oklab, var(--colour-surface-muted) 40%, transparent);
  border-left-color: var(--colour-border);
}

.dz-card-notice-title {
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  line-height: var(--leading-tight);
  color: var(--colour-text);
}

.dz-card-notice-body {
  font-size: var(--text-xs);
  line-height: var(--leading-tight);
  color: var(--colour-text-muted);
  margin-block-start: 0.125rem;
}

/* Card body — HTMX-loaded region content slot.
 *
 * #962 — `min-height` per display mode reserves space before the htmx
 * region fetch lands so the page doesn't bounce as content arrives.
 * Web Vitals classifies CLS > 0.1 as "needs improvement"; pre-fix
 * surveyed surfaces hit 0.18–0.37. If content exceeds the
 * reservation, only that card grows (one shift, bounded). If content
 * fits, no shift.
 *
 * Selector values match `DisplayMode` (src/dazzle/core/ir/workspaces.py)
 * — lowercased by the template's `| lower` filter. Adding a new
 * display mode? Either add a rule here OR add the value to the
 * fall-through allow-list in test_workspace_cls_reservation.py.
 */
.dz-card-body {
  padding-inline: var(--space-md);
  padding-block-end: var(--space-md);
  min-height: 280px; /* default — matches LIST / TABBED_LIST shape */
}

.dz-card-body[data-display="summary"],
.dz-card-body[data-display="summary_row"] {
  min-height: 96px;
}

.dz-card-body[data-display="metrics"],
.dz-card-body[data-display="metric"] {
  min-height: 140px;
}

.dz-card-body[data-display="bar_chart"],
.dz-card-body[data-display="chart"],
.dz-card-body[data-display="line_chart"],
.dz-card-body[data-display="bar_track"],
.dz-card-body[data-display="bullet"],
.dz-card-body[data-display="funnel_chart"],
.dz-card-body[data-display="heatmap"],
.dz-card-body[data-display="radar"] {
  min-height: 280px;
}

.dz-card-body[data-display="kanban"] {
  min-height: 480px;
}

.dz-card-body[data-display="diagram"] {
  min-height: 360px;
}

.dz-card-body[data-display="profile_card"] {
  min-height: 200px;
}

.dz-card-body[data-display="action_grid"],
.dz-card-body[data-display="pipeline_steps"],
.dz-card-body[data-display="status_list"] {
  min-height: 200px;
}

.dz-card-body[data-display="pivot_table"],
.dz-card-body[data-display="timeline"] {
  min-height: 320px;
}

/* Skeleton loader (shown until HTMX swap completes) */
.dz-card-skeleton {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  padding-block: var(--space-sm);
  animation: dz-pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
}

.dz-card-skeleton-line {
  height: 1rem;
  background: var(--colour-bg);
  border-radius: var(--radius-sm);
}

.dz-card-skeleton-line.is-thin {
  height: 0.75rem;
}

.dz-card-skeleton-line.w-3-4 { width: 75%; }
.dz-card-skeleton-line.w-5-6 { width: 83.333%; }

/* @keyframes dz-pulse dropped — HM tokens.css owns it (identical). */

/* Resize handle — right edge. Same fade-on-hover pattern as the
 * action buttons. */
.dz-card-resize {
  position: absolute;
  top: 0;
  inset-inline-end: 0;
  width: 0.375rem;
  height: 100%;
  cursor: col-resize;
  opacity: 0;
  transition:
    opacity var(--duration-fast) var(--ease-out),
    background var(--duration-fast) var(--ease-out);
  border-end-end-radius: var(--radius-md);
  border-start-end-radius: var(--radius-md);
}

.dz-card-wrapper:hover .dz-card-resize,
.dz-card-wrapper:focus-within .dz-card-resize {
  opacity: 1;
}

.dz-card-resize:hover {
  background: color-mix(in oklch, var(--colour-brand) 30%, transparent);
}

/* ─────────────────────────── add-card CTA ───────────────────────
 * Centred dashed-border button below the grid. Picker pops up
 * relative to it (positioning lives in the picker template). */

.dz-add-card-row {
  position: relative;
  display: flex;
  justify-content: center;
  margin-block-start: var(--space-md);
}

.dz-add-card-button {
  display: inline-flex;
  align-items: center;
  gap: var(--space-sm);
  height: 2rem;
  padding-inline: var(--space-lg);
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  border-radius: var(--radius-md);
  border: 2px dashed var(--colour-border);
  color: var(--colour-text-muted);
  background: transparent;
  cursor: pointer;
  transition:
    border-color var(--duration-fast) var(--ease-out),
    color var(--duration-fast) var(--ease-out);
}

.dz-add-card-button:hover {
  border-color: var(--colour-brand);
  color: var(--colour-text);
}
/* --- hatchi-maxchi/components/date-range.css --- */
/* HYPERPART: date-range */
/* Promoted VERBATIM from Dazzle fragments.css (Tier F3, fleet
 * convergence). The Dazzle emitter contract is unchanged. */

/* ─────────────────────────── date_range_picker fragment ─────────
 * From / To date inputs in a horizontal bar. Inputs use the form
 * input chrome from form.css already; this owns only the row layout
 * and label sizing. */

.dz-date-range-picker {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  margin-block-end: var(--space-sm);
}

.dz-date-range-label {
  font-size: var(--text-xs);
  color: var(--colour-text-muted);
}

.dz-date-range-input {
  height: 1.75rem;
  padding-inline: var(--space-sm);
  width: 9rem;
  border-radius: var(--radius-sm);
  background: var(--colour-surface);
  border: 1px solid var(--colour-border);
  font-size: var(--text-xs);
  color: var(--colour-text);
}

.dz-date-range-input:focus-visible {
  outline: none;
  box-shadow: 0 0 0 1px var(--colour-brand);
  border-color: var(--colour-brand);
}
/* --- hatchi-maxchi/components/detail.css --- */
/*
 * detail.css — entity detail-view component family (HaTchi-MaXchi).
 *
 * The record/detail view: an entity's fields as a definition list inside a
 * card, header (back / title / actions), state-machine transition buttons, and
 * related-entity groups beneath; plus the workspace `display: detail` region
 * grid and the shared in-region empty placeholder. All values route through HM
 * tokens. Migrated from Dazzle `page/runtime/static/css/components/detail.css`
 * into HM (HMC-012, aggressive-mode convergence 2026-07-08).
 */
/* ─────────────────────────── outer wrapper ──────────────────────
 * Width changes when related-groups are present (more horizontal
 * room for the grouped tables). The component template branches on
 * `detail.related_groups` to apply .dz-detail-wide. */

.dz-detail {
  max-width: 48rem;  /* ~max-w-3xl */
  margin-inline: auto;
}

.dz-detail-wide {
  max-width: 64rem;  /* ~max-w-5xl — when related groups present */
}

/* ─────────────────────────── header row ─────────────────────────
 * Back-button + title on the left, action buttons on the right. */

.dz-detail-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-block-end: var(--space-xl);
}

.dz-detail-header-title {
  display: flex;
  align-items: center;
  gap: var(--space-md);
}

.dz-detail-title {
  font-size: var(--text-lg);
  font-weight: var(--weight-semibold);
  color: var(--colour-text);
  margin: 0;
}

.dz-detail-actions {
  display: flex;
  gap: var(--space-sm);
}

/* ─────────────────────────── transition / external rows ──────────
 * State-machine transitions, integration actions, external link
 * actions — all share the same horizontal-button-row shape with
 * a small bottom margin. */

.dz-detail-toolbar {
  display: flex;
  gap: var(--space-sm);
  margin-block-end: var(--space-lg);
}

/* ─────────────────────────── detail card ────────────────────────
 * The main field-list lives inside a card. */

.dz-detail-card {
  background: var(--colour-surface);
  border: 1px solid var(--colour-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-sm);
}

.dz-detail-card-body {
  padding: var(--space-lg);
}

/* ─────────────────────────── definition list ───────────────────
 * Each row is a 3-column grid on wider viewports (label takes 1,
 * value takes 2). On narrow viewports rows stack with a small gap.
 * Uses container queries so the grid adapts to the container's
 * width, not the viewport. */

.dz-detail-list {
  container-type: inline-size;
}

.dz-detail-list > * + * {
  border-block-start: 1px solid var(--colour-border);
}

.dz-detail-row {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  padding-block: var(--space-md);
}

@container (width > 32rem) {
  .dz-detail-row {
    display: grid;
    grid-template-columns: 1fr 2fr;
    gap: var(--space-lg);
  }
}

.dz-detail-label {
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  color: var(--colour-text-muted);
}

.dz-detail-value {
  font-size: var(--text-sm);
  color: var(--colour-text);
}

/* ─────────────────────────── file value link ────────────────────
 * `field.type == "file"` renders an icon + filename link. */

.dz-detail-file-link {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
  color: var(--colour-brand);
  text-decoration: none;
}

.dz-detail-file-link:hover {
  text-decoration: underline;
}

.dz-detail-file-icon {
  width: 1rem;
  height: 1rem;
}

/* ─────────────────────────── related groups ─────────────────────
 * After the main field card, related entities (one-to-many or
 * many-to-many) render as separate groups with their own labels
 * and content (table / status_cards / file_list). */

.dz-detail-related-group {
  margin-block-start: var(--space-xl);
}

.dz-detail-related-label {
  font-size: var(--text-base);
  font-weight: var(--weight-semibold);
  color: var(--colour-text);
  margin-block-end: var(--space-md);
}

/* ─────────────────────────── workspace detail region ───────────
 * The workspace `display: detail` region shape is different from
 * the standalone detail_view component above — it renders inside
 * a region card slot, so the chrome (border, shadow, padding) is
 * provided by .region-card; here we only style the dl grid and
 * the empty placeholder. Two-column label/value grid (label left,
 * value right) — distinct from the row-stacked .dz-detail-row above. */

.dz-detail-region-grid {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: var(--space-xs) var(--space-md);
  margin: 0;
}

.dz-detail-region-grid > dt.dz-detail-label {
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  color: var(--colour-text-muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  margin: 0;
}

.dz-detail-region-grid > dd.dz-detail-value {
  font-size: var(--text-sm);
  color: var(--colour-text);
  margin: 0;
}

/* ─────────────────────────── shared empty placeholder ──────────
 * `.dz-empty-dense` is the in-region "no data" line used by the
 * detail region and ~20 other region templates. Centralised here
 * during the detail migration; remains in this file until the
 * region migrations proper move it to a shared location. */

.dz-empty-dense {
  font-size: var(--text-sm);
  color: var(--colour-text-muted);
  margin: 0;
}
/* --- hatchi-maxchi/components/diagram.css --- */
/* HYPERPART: diagram */
/* Promoted VERBATIM from Dazzle regions.css (Tier F, fleet
 * convergence). The Dazzle emitter contract is unchanged. */

/* ─────────────────────────── diagram region ─────────────────────
 * Mermaid-rendered SVG inside a horizontal-scroll container.
 * Mermaid's own CSS handles the diagram chrome; we own the wrapper. */

.dz-diagram-scroll {
  overflow-x: auto;
}

.dz-diagram-source {
  font-size: var(--text-sm);
  color: var(--colour-text);
  /* Mermaid replaces this <pre> with rendered SVG once initialised;
   * the source-text styling matters only on the initial paint flash. */
}

.dz-diagram-empty {
  font-size: var(--text-sm);
  color: var(--colour-text-muted);
  margin: 0;
}
/* --- hatchi-maxchi/components/dialog.css --- */
/* HYPERPART: dialog */
/*
 * dz-dialog — modal built on the native <dialog>. Opening is the only
 * behaviour that needs script: dz-dialog.js calls showModal() when a
 * [data-dz-dialog-open="id"] trigger is clicked. Everything else is
 * native — CLOSE is a <form method="dialog"> submit (the ✕ and footer
 * buttons), Esc, or a backdrop tap (closedby="any"); focus trapping and
 * the inert background come free; showModal() moves focus into the dialog.
 *
 * Instance-safe: the trigger names its dialog by id, so any number of
 * dialogs coexist on a page (no shared global handle).
 */

dialog.dz-dialog {
  width: min(32rem, calc(100vw - 2rem));
  padding: 0;
  border: 1px solid var(--colour-border);
  border-radius: var(--radius-lg);
  background: var(--colour-surface);
  color: var(--colour-text);
  box-shadow: var(--shadow-lg);
}

dialog.dz-dialog::backdrop {
  background: oklch(13% 0.028 261.69 / 0.4);
  backdrop-filter: blur(2px);
}

.dz-dialog__header {
  display: flex;
  align-items: flex-start;
  gap: var(--space-md);
  padding: var(--space-lg) var(--space-lg) var(--space-md);
}

.dz-dialog__title {
  flex: 1;
  min-width: 0;
  margin: 0;
  font-size: var(--text-lg);
  font-weight: var(--weight-semibold);
  color: var(--colour-text);
}

/* Always-visible dismiss affordance — a native submit inside the
   method="dialog" form, so the tap closes with no JS (touch-safe). */
.dz-dialog__close {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  margin: calc(var(--space-xs) * -1) calc(var(--space-xs) * -1) 0 0;
  padding: 0;
  border: none;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--colour-text-muted);
  cursor: pointer;
  transition: background var(--duration-fast) var(--ease-out);
}

.dz-dialog__close:hover {
  background: var(--colour-surface-muted);
  color: var(--colour-text);
}

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

.dz-dialog__close svg {
  width: 1rem;
  height: 1rem;
}

.dz-dialog__body {
  padding: 0 var(--space-lg);
  font-size: var(--text-sm);
  color: var(--colour-text-muted);
  line-height: var(--leading-normal);
}

.dz-dialog__body p {
  margin: 0;
}

.dz-dialog__footer {
  display: flex;
  justify-content: flex-end;
  gap: var(--space-sm);
  padding: var(--space-lg);
}

@media (prefers-reduced-motion: reduce) {
  .dz-dialog__close { transition: none; }
}
/* --- hatchi-maxchi/components/drawer.css --- */
/* HYPERPART: drawer */
/*
 * dz-drawer — an edge-anchored panel built on the native <dialog>, so it is
 * a drawer with a modal's guarantees: focus is trapped, the background goes
 * inert, Esc and a backdrop tap dismiss it — none of which a plain <aside>
 * toggle gets for free. It shares the dialog's opener (dz-dialog.js: a
 * [data-dz-dialog-open="id"] trigger calls showModal()) and native close
 * (<form method="dialog"> + Esc + closedby="any") — no drawer-specific JS.
 *
 * `data-dz-side="right"` (default) | `"left"` anchors the edge; the panel
 * slides in from that edge (native <dialog> enter transition via
 * @starting-style), honouring prefers-reduced-motion.
 */

dialog.dz-drawer {
  width: min(24rem, calc(100vw - 3rem));
  max-width: none;
  height: 100dvh;
  max-height: none;
  padding: 0;
  border: none;
  border-radius: 0;
  background: var(--colour-surface);
  color: var(--colour-text);
  box-shadow: var(--shadow-lg);
  /* right edge (default): pin to the inline-end */
  margin-block: 0;
  margin-inline: auto 0;
  border-inline-start: 1px solid var(--colour-border);
  /* enter transition: slide from the anchored edge. `allow-discrete` lets
     the dialog animate on open despite the display:none→block flip. */
  translate: 0 0;
  transition:
    translate var(--duration-base) var(--ease-out),
    overlay var(--duration-base) var(--ease-out) allow-discrete,
    display var(--duration-base) var(--ease-out) allow-discrete;
}

/* Width presets (data-dz-width) — promoted from Dazzle's peek
 * slide_over feature (Tier F2): the author's declared panel width.
 * Unset/other values keep the drawer's default width above; presets
 * only widen on viewports that can afford it. */
@media (min-width: 40rem) {
  dialog.dz-drawer[data-dz-width="sm"] { width: min(24rem, calc(100vw - 3rem)); }
  dialog.dz-drawer[data-dz-width="md"] { width: min(32rem, calc(100vw - 3rem)); }
  dialog.dz-drawer[data-dz-width="lg"] { width: min(42rem, calc(100vw - 3rem)); }
  dialog.dz-drawer[data-dz-width="xl"] { width: min(56rem, calc(100vw - 3rem)); }
  dialog.dz-drawer[data-dz-width="full"] { width: calc(100vw - 3rem); }
}

dialog.dz-drawer[data-dz-side="left"] {
  margin-inline: 0 auto;
  border-inline: none;
  border-inline-end: 1px solid var(--colour-border);
}

/* start state (opening) + exit state (closing) sit off the anchored edge.
   NOTE: the slide uses a physical `translate` while anchoring uses logical
   margins — correct for LTR; under RTL a right-anchored panel would slide
   from the wrong edge. The package has no RTL/dir support today, so this is
   a tracked follow-up, not a live defect. */
@starting-style {
  dialog.dz-drawer[open] { translate: 100% 0; }
  dialog.dz-drawer[data-dz-side="left"][open] { translate: -100% 0; }
}
dialog.dz-drawer:not([open]) { translate: 100% 0; }
dialog.dz-drawer[data-dz-side="left"]:not([open]) { translate: -100% 0; }

dialog.dz-drawer::backdrop {
  background: oklch(13% 0.028 261.69 / 0.4);
  backdrop-filter: blur(2px);
}

/* The open drawer is a flex column (pinned header + footer, scrolling body).
   Scoped to [open] so a CLOSED dialog falls through to the UA
   `dialog:not([open]){display:none}` — an unconditional `display:flex` would
   keep the closed dialog rendered off-screen, leaking its controls into the
   tab order + a11y tree. The base `transition: display allow-discrete` still
   animates the exit. */
dialog.dz-drawer[open] {
  display: flex;
  flex-direction: column;
}

/* header/body/footer are the dialog's flex column whether they sit directly
   in the <dialog> (a JS-driven drawer, e.g. a detail panel) or are wrapped in
   a <form method="dialog"> (the native-close form). display:contents makes the
   optional form transparent to layout so both shapes stack the same. */
dialog.dz-drawer > form {
  display: contents;
}

.dz-drawer__header {
  display: flex;
  align-items: flex-start;
  gap: var(--space-md);
  flex: none;
  padding: var(--space-lg);
  border-block-end: 1px solid var(--colour-border);
}

.dz-drawer__title {
  flex: 1;
  min-width: 0;
  margin: 0;
  font-size: var(--text-lg);
  font-weight: var(--weight-semibold);
  color: var(--colour-text);
}

.dz-drawer__close {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  margin: calc(var(--space-xs) * -1) calc(var(--space-xs) * -1) 0 0;
  padding: 0;
  border: none;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--colour-text-muted);
  cursor: pointer;
  transition: background var(--duration-fast) var(--ease-out);
}

.dz-drawer__close:hover {
  background: var(--colour-surface-muted);
  color: var(--colour-text);
}

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

.dz-drawer__close svg {
  width: 1rem;
  height: 1rem;
}

.dz-drawer__body {
  flex: 1;
  overflow-y: auto;
  padding: var(--space-lg);
  font-size: var(--text-sm);
  color: var(--colour-text-muted);
  line-height: var(--leading-normal);
}

.dz-drawer__body p { margin: 0; }

.dz-drawer__footer {
  display: flex;
  justify-content: flex-end;
  gap: var(--space-sm);
  flex: none;
  padding: var(--space-lg);
  border-block-start: 1px solid var(--colour-border);
}

/* Flat-named internal chrome (moved from Dazzle dashboard.css, HMC-007b —
   the drawer's chrome travels with the drawer component). These are the class
   names the detail-drawer emitter produces (distinct from the __header/__body
   BEM parts above); kept verbatim, already tokenised. */
.dz-drawer-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-inline: var(--space-md);
  padding-block: var(--space-sm);
  border-block-end: 1px solid var(--colour-border);
  flex-shrink: 0;
}

.dz-drawer-button {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
  height: 2rem;
  padding-inline: var(--space-md);
  border-radius: var(--radius-sm);
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  color: var(--colour-text-muted);
  background: transparent;
  border: 0;
  cursor: pointer;
  text-decoration: none;
  transition: background var(--duration-fast) var(--ease-out);
}

.dz-drawer-button:hover {
  background: var(--colour-bg);
}

.dz-drawer-content {
  flex: 1;
  overflow-y: auto;
  padding: var(--space-md);
}

@media (prefers-reduced-motion: reduce) {
  dialog.dz-drawer { transition: none; }
  dialog.dz-drawer[open],
  dialog.dz-drawer:not([open]) { translate: 0 0; }
  .dz-drawer__close { transition: none; }
}

/* Detail-drawer width instance (migrated from Dazzle dashboard.css, HMC-020):
   the entity detail slide-over is wider than the HM drawer default. */
#dz-detail-drawer.dz-drawer {
  width: min(42rem, calc(100vw - 3rem));
}
/* --- hatchi-maxchi/components/feedback-widget.css --- */
@layer framework {
/* HaTchi-MaXchi — Feedback widget (floating feedback FAB + panel: categories, severities, form). Ported from Dazzle feedback-widget.css (HM-sitespec 1C, 2026-07-09) — was orphaned (unlinked) on the Dazzle side; now served via the HM dist. Authored with dz- prefix. */

/* Floating button */
.dz-feedback-btn {
  position: fixed;
  bottom: 1.5rem;
  right: 1.5rem;
  width: 3rem;
  height: 3rem;
  border-radius: 9999px;
  background: var(--colour-brand);
  color: var(--colour-brand-contrast);
  border: none;
  cursor: pointer;
  z-index: 9998;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 4px 12px rgb(0 0 0 / 0.15);
  transition: transform 0.15s ease, box-shadow 0.15s ease;
}
.dz-feedback-btn:hover {
  transform: scale(1.08);
  box-shadow: 0 6px 16px rgb(0 0 0 / 0.2);
}
.dz-feedback-btn svg {
  width: 1.25rem;
  height: 1.25rem;
}

/* Slide-out panel */
.dz-feedback-panel {
  position: fixed;
  top: 0;
  right: 0;
  width: 24rem;
  max-width: 100vw;
  height: 100dvh;
  background: var(--colour-surface);
  border-left: 1px solid var(--colour-border);
  box-shadow: -4px 0 12px rgba(0, 0, 0, 0.1);
  z-index: 9999;
  transform: translateX(100%);
  transition: transform 0.25s ease;
  display: flex;
  flex-direction: column;
  overflow-y: auto;
}
.dz-feedback-panel.open {
  transform: translateX(0);
}

/* Panel header */
.dz-feedback-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 1rem 1.25rem;
  border-bottom: 1px solid var(--colour-border);
}
.dz-feedback-header h3 {
  margin: 0;
  font-size: 1rem;
  font-weight: 600;
  color: var(--colour-text);
}
.dz-feedback-close {
  background: none;
  border: none;
  cursor: pointer;
  color: var(--colour-text);
  font-size: 1.25rem;
  padding: 0.25rem;
}

/* Panel body */
.dz-feedback-body {
  padding: 1.25rem;
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

/* Category buttons */
.dz-feedback-categories {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0.5rem;
}
.dz-feedback-cat-btn {
  padding: 0.5rem 0.75rem;
  border: 1px solid var(--colour-border);
  border-radius: 0.375rem;
  background: var(--colour-surface-muted);
  color: var(--colour-text);
  cursor: pointer;
  font-size: 0.8125rem;
  text-align: center;
  transition: border-color 0.15s ease, background 0.15s ease;
}
.dz-feedback-cat-btn:hover {
  border-color: var(--colour-brand);
}
.dz-feedback-cat-btn.selected {
  background: var(--colour-brand);
  color: var(--colour-brand-contrast);
  border-color: var(--colour-brand);
}

/* Severity toggles */
.dz-feedback-severities {
  display: flex;
  gap: 0.5rem;
}
.dz-feedback-sev-btn {
  flex: 1;
  padding: 0.375rem 0.5rem;
  border: 1px solid var(--colour-border);
  border-radius: 0.375rem;
  background: var(--colour-surface-muted);
  color: var(--colour-text);
  cursor: pointer;
  font-size: 0.75rem;
  text-align: center;
  transition: border-color 0.15s ease, background 0.15s ease;
}
.dz-feedback-sev-btn.selected[data-severity="blocker"] {
  background: var(--danger-600);
  color: white;
  border-color: var(--danger-600);
}
.dz-feedback-sev-btn.selected[data-severity="annoying"] {
  background: var(--warning-600);
  color: white;
  border-color: var(--warning-600);
}
.dz-feedback-sev-btn.selected[data-severity="minor"] {
  background: var(--success-600);
  color: white;
  border-color: var(--success-600);
}

/* Description textarea */
.dz-feedback-description {
  width: 100%;
  min-height: 6rem;
  padding: 0.75rem;
  border: 1px solid var(--colour-border);
  border-radius: 0.375rem;
  background: var(--colour-surface);
  color: var(--colour-text);
  font-family: inherit;
  font-size: 0.875rem;
  resize: vertical;
}
.dz-feedback-description:focus {
  outline: 2px solid var(--colour-brand);
  outline-offset: -1px;
}

/* Submit button */
.dz-feedback-submit {
  width: 100%;
  padding: 0.625rem 1rem;
  border: none;
  border-radius: 0.375rem;
  background: var(--colour-brand);
  color: var(--colour-brand-contrast);
  font-weight: 600;
  font-size: 0.875rem;
  cursor: pointer;
  transition: opacity 0.15s ease;
}
.dz-feedback-submit:hover {
  opacity: 0.9;
}
.dz-feedback-submit:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/* Label */
.dz-feedback-label {
  font-size: 0.8125rem;
  font-weight: 500;
  color: var(--colour-text);
}

/* Toast notification */
.dz-feedback-toast {
  position: fixed;
  bottom: 5rem;
  right: 1.5rem;
  padding: 0.75rem 1.25rem;
  border-radius: 0.375rem;
  background: var(--success-600);
  color: white;
  font-size: 0.875rem;
  z-index: 10000;
  opacity: 0;
  transform: translateY(0.5rem);
  transition: opacity 0.2s ease, transform 0.2s ease;
  pointer-events: none;
}
.dz-feedback-toast.visible {
  opacity: 1;
  transform: translateY(0);
}

/* Backdrop overlay */
.dz-feedback-backdrop {
  position: fixed;
  inset: 0;
  background: rgb(0 0 0 / 0.3);
  z-index: 9998;
  opacity: 0;
  transition: opacity 0.25s ease;
  pointer-events: none;
}
.dz-feedback-backdrop.visible {
  opacity: 1;
  pointer-events: auto;
}

} /* end @layer framework */
/* --- hatchi-maxchi/components/form-chrome.css --- */
/* HYPERPART: form-chrome */
/* Promoted from Dazzle fragments.css (Tier F3, fleet convergence):
 * the form SECTION chrome, the form_errors validation summary, the
 * form_stepper progress list, and the steps_indicator (experience
 * wizard). Emitter contracts unchanged. NOT promoted (deleted at the
 * source instead): .dz-form-shell/-header/-title/-body/-actions — that
 * wrapper family's producer was the legacy form_renderer.py deleted in
 * ADR-0049 Phase 3b; the substrate form path never emitted it — and
 * .dz-form-errors-message (the errors emitter renders title+list only). */

/* ── form sections (live: FormSection primitive) ── */

.dz-form-section-title {
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  color: var(--colour-text);
  margin-block-end: var(--space-md);
}

.dz-form-section-note {
  font-size: var(--text-sm);
  color: var(--colour-text-muted);
  margin-block-end: var(--space-md);
}

.dz-form-section {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  margin-block-end: var(--space-lg);
}

/* ─────────────────────────── form_errors fragment ───────────────
 * Form-level validation error block (HTMX-swapped). Always uses the
 * destructive/danger tone — no need for an attribute selector. */

.dz-form-errors {
  display: flex;
  gap: var(--space-md);
  padding: var(--space-md);
  margin-block-end: var(--space-md);
  border-radius: var(--radius-sm);
  border: 1px solid var(--colour-danger);
  background: color-mix(in oklch, var(--colour-danger) 8%, transparent);
}

.dz-form-errors-icon {
  width: 1.25rem;
  height: 1.25rem;
  flex-shrink: 0;
  color: var(--colour-danger);
}

.dz-form-errors-body {
  min-width: 0;
}

.dz-form-errors-title {
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  color: var(--colour-text);
  margin: 0;
}


.dz-form-errors-list {
  font-size: var(--text-sm);
  color: var(--colour-text);
  list-style: disc inside;
  margin: 0.125rem 0 0;
  padding-inline-start: 0;
  display: flex;
  flex-direction: column;
  gap: 0.125rem;
}

/* ─────────────────────────── form_stepper fragment ──────────────
 * Multi-section form progress list. Each <li> is a (number-circle
 * + label + connector). Active state via .is-active modifier on
 * the whole li. */

.dz-form-stepper {
  display: flex;
  align-items: center;
  width: 100%;
  margin-block-end: var(--space-xl);
  gap: var(--space-sm);
  list-style: none;
  padding-inline-start: 0;
}

.dz-form-stepper-item {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  min-width: 0;
  cursor: pointer;
}

.dz-form-stepper-item.is-not-last {
  flex: 1 1 0;
}

.dz-form-stepper-circle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 1.5rem;
  height: 1.5rem;
  border-radius: var(--radius-full);
  border: 1px solid var(--colour-border);
  background: var(--colour-surface);
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  color: var(--colour-text-muted);
  transition: background var(--duration-base) var(--ease-out),
              color var(--duration-base) var(--ease-out),
              border-color var(--duration-base) var(--ease-out);
}

.dz-form-stepper-circle.is-active {
  background: var(--colour-brand);
  color: var(--colour-brand-contrast);
  border-color: var(--colour-brand);
}

.dz-form-stepper-circle > svg {
  width: 0.75rem;
  height: 0.75rem;
}

.dz-form-stepper-label {
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  color: var(--colour-text-muted);
  transition: color var(--duration-base) var(--ease-out);
}

/* HYPERPART: wizard */
/* The keyboard-operable step trigger — a real <button> inside the li,
 * visually transparent (the circle/label carry the design). */
.dz-form-stepper-button {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  min-width: 0;
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  color: inherit;
  cursor: pointer;
}

.dz-form-stepper-button:focus-visible {
  outline: var(--focus-ring-width) solid var(--focus-ring-color);
  outline-offset: var(--focus-ring-offset);
  border-radius: var(--radius-sm);
}

/* Completed steps swap the number for a pure-CSS checkmark — keyed
 * off data-dz-state (set by dz-wizard.js; the SSR default is
 * current/pending, so no-JS renders numbers throughout). */
[data-dz-state="complete"] .dz-form-stepper-circle span {
  display: none;
}

[data-dz-state="complete"] .dz-form-stepper-circle::after {
  content: "\2713";
}

.dz-form-stepper-label.is-active {
  color: var(--colour-text);
}

.dz-form-stepper-connector {
  flex: 1 1 0;
  height: 1px;
  background: var(--colour-border);
  transition: background var(--duration-base) var(--ease-out);
}

.dz-form-stepper-connector.is-active {
  background: var(--colour-brand);
}

/* ─────────────────────────── steps_indicator fragment ───────────
 * Visual step indicator (cycle 251). Position-based — every step at
 * or before the current is "complete" and rendered with brand fill.
 * Distinct from form_stepper which uses Alpine + per-step state. */

.dz-steps {
  display: flex;
  align-items: center;
  width: 100%;
  list-style: none;
  padding-inline-start: 0;
  margin: 0;
}

.dz-steps-item {
  display: flex;
  align-items: center;
}

.dz-steps-item.is-not-last {
  flex: 1 1 0;
}

.dz-steps-row {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
}

.dz-steps-circle {
  flex-shrink: 0;
  width: 1.5rem;
  height: 1.5rem;
  border-radius: var(--radius-full);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  background: var(--colour-bg);
  color: var(--colour-text-muted);
}

.dz-steps-circle.is-completed {
  background: var(--colour-brand);
  color: var(--colour-brand-contrast);
}

.dz-steps-label {
  font-size: var(--text-sm);
  color: var(--colour-text-muted);
}

.dz-steps-label.is-completed {
  color: var(--colour-text);
  font-weight: var(--weight-medium);
}

.dz-steps-connector {
  flex: 1 1 0;
  margin-inline: var(--space-md);
  height: 1px;
  background: var(--colour-border);
}

.dz-steps-connector.is-completed {
  background: var(--colour-brand);
}
/* --- hatchi-maxchi/components/form.css --- */
/*
 * form.css — form field component family.
 *
 * Used by the render_field macro (templates/macros/form_field.html)
 * which dispatches across ~14 field types: checkbox, ref-entity
 * select, search-select, plain text/number/email/date/datetime
 * (native <input type=date>/datetime-local — the JS calendar `picker`
 * widget was dropped 2026-07-09), textarea, select, money, file, plus
 * widget-driven combobox / tags (Tom Select) / color / rich_text.
 * Native <input type="color"> covers the colour widget.
 *
 * Pre-CSS-refactor every branch composed its own inline Tailwind
 * class string with conditional Jinja for the error/idle border.
 * Post-refactor, the macro emits semantic classes only and styling
 * lives here. Error state derives from the `aria-invalid="true"`
 * attribute the macro already emits for accessibility — single
 * source of truth, no parallel `.is-error` modifier needed.
 *
 * Wrapped in @layer components.
 */

/* ─────────────────────────── outer wrapper ──────────────────────
 * Vertical flow of label / hint / input / error. Tight gap so
 * forms feel cohesive; .stack base utility would be too generous. */

.dz-form-field {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  width: 100%;
}

/* ─────────────────────────── label ──────────────────────────────
 * Block label above the input. Required-mark is a child span. */

.dz-form-label {
  display: block;
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  color: var(--colour-text);
}

.dz-form-required {
  color: var(--colour-danger);
  margin-inline-start: var(--space-xs);
}

/* ─────────────────────────── hint + error ───────────────────────
 * Both small + muted; error is danger-coloured. The macro emits
 * <p id="hint-{name}"> and <p id="error-{name}"> for aria-describedby
 * referencing. */

.dz-form-hint {
  font-size: var(--text-xs);
  color: var(--colour-text-muted);
  line-height: var(--leading-normal);
}

.dz-form-error {
  font-size: var(--text-xs);
  color: var(--colour-danger);
  margin-block-start: var(--space-xs);
}

/* ─────────────────────────── base input ─────────────────────────
 * The canonical row input — used by text, email, number, date,
 * datetime, select. Single class .dz-form-input handles the lot.
 * Error state derives from `aria-invalid="true"` (selector below);
 * focus state via :focus-visible. */

.dz-form-input {
  width: 100%;
  /* #930: previously `height: 2rem` (32px) with `padding: 8px 12px`
     left only 16px of vertical content area for a ~22px line-box,
     clipping descenders (g/p/y) on every input + select placeholder.
     Switch to a min-height + explicit line-height + padding-block so
     the line-box always fits. `height: auto` lets future multi-line
     content (group labels in selects, wrapped placeholders) scale. */
  height: auto;
  min-height: 2.5rem;
  line-height: 1.4;
  padding-block: var(--space-sm);
  padding-inline: var(--space-md);
  font-size: var(--text-sm);
  color: var(--colour-text);
  background: var(--colour-surface);
  border: 1px solid var(--colour-border);
  border-radius: var(--radius-sm);
  transition:
    box-shadow var(--duration-base) var(--ease-out),
    border-color var(--duration-base) var(--ease-out);
}

.dz-form-input::placeholder {
  color: var(--colour-text-muted);
}

.dz-form-input:focus-visible {
  outline: none;
  box-shadow: 0 0 0 1px var(--colour-brand);
  border-color: var(--colour-brand);
}

.dz-form-input[aria-invalid="true"] {
  border-color: var(--colour-danger);
}

.dz-form-input[aria-invalid="true"]:focus-visible {
  box-shadow: 0 0 0 1px var(--colour-danger);
}

/* ─────────────────────────── textarea ───────────────────────────
 * Same chrome as .dz-form-input but multi-line. Override height
 * + padding-block; resize handle vertical only. */

.dz-form-textarea {
  height: auto;
  min-height: 6rem;
  padding-block: var(--space-sm);
  resize: vertical;
}

/* ─────────────────────────── checkbox ───────────────────────────
 * Inline row: checkbox + text label. The macro wraps both in
 * <label class="dz-form-checkbox-label"> for click-target convenience. */

.dz-form-checkbox-label {
  display: inline-flex;
  align-items: center;
  gap: var(--space-sm);
  cursor: pointer;
  font-size: var(--text-sm);
  color: var(--colour-text);
}

.dz-form-checkbox {
  width: 1rem;
  height: 1rem;
  border-radius: var(--radius-sm);
  accent-color: var(--colour-brand);
}

/* HYPERPART: money */
/* ─────────────────────────── money widget ───────────────────────
 * Three sub-elements:
 *   .dz-form-money-group — flex row container
 *   .dz-form-money-prefix — currency symbol box (pinned currency)
 *   .dz-form-money-select — currency dropdown (unpinned currency)
 * The amount input uses .dz-form-input with a .dz-form-input-trailing
 * modifier to round only the right corners. */

.dz-form-money-group {
  display: flex;
  width: 100%;
}

.dz-form-money-prefix {
  display: inline-flex;
  align-items: center;
  /* #930: align with .dz-form-input height to keep prefix flush. */
  min-height: 2.5rem;
  padding-inline: var(--space-md);
  background: var(--colour-bg);
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  color: var(--colour-text-muted);
  border: 1px solid var(--colour-border);
  border-inline-end: 0;
  border-start-start-radius: var(--radius-sm);
  border-end-start-radius: var(--radius-sm);
}

.dz-form-money-select {
  /* #930: matches updated input height; also gets line-height + padding
     so descenders in the currency option list don't clip. */
  height: auto;
  min-height: 2.5rem;
  line-height: 1.4;
  padding-block: var(--space-sm);
  width: 7rem;
  padding-inline: var(--space-sm);
  background: var(--colour-surface);
  color: var(--colour-text);
  font-size: var(--text-sm);
  border: 1px solid var(--colour-border);
  border-inline-end: 0;
  border-start-start-radius: var(--radius-sm);
  border-end-start-radius: var(--radius-sm);
}

.dz-form-money-select:focus-visible {
  outline: none;
  box-shadow: 0 0 0 1px var(--colour-brand);
}

/* Modifier on .dz-form-input that joins it to a leading prefix —
 * rounds only the right corners. Used by the money widget. */
.dz-form-input-trailing {
  border-start-start-radius: 0;
  border-end-start-radius: 0;
}

/* ─────────────────────────── file upload ────────────────────────
 * Two states (dragging via Alpine x-show; preview vs dropzone via
 * x-show). Container uses .dz-file-upload (existing class on the
 * x-data root). */

.dz-file-upload-preview {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  padding: var(--space-md);
  margin-block-end: var(--space-sm);
  border-radius: var(--radius-sm);
  background: var(--colour-bg);
}

.dz-file-upload-preview-icon {
  width: 1.25rem;
  height: 1.25rem;
  flex-shrink: 0;
  color: var(--colour-success);
}

.dz-file-upload-preview-name {
  font-size: var(--text-sm);
  color: var(--colour-text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.dz-file-upload-preview-clear {
  width: 1.5rem;
  height: 1.5rem;
  margin-inline-start: auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-sm);
  color: var(--colour-text-muted);
  cursor: pointer;
  transition: background var(--duration-fast) var(--ease-out);
}

.dz-file-upload-preview-clear:hover {
  background: var(--colour-bg);
  color: var(--colour-text);
}

.dz-file-upload-zone {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 8rem;
  border: 2px dashed var(--colour-border);
  border-radius: var(--radius-md);
  cursor: pointer;
  transition:
    background var(--duration-base) var(--ease-out),
    border-color var(--duration-base) var(--ease-out);
}

.dz-file-upload-zone:hover,
.dz-file-upload-zone.dragging {
  background: var(--colour-bg);
  border-color: var(--colour-brand);
}

.dz-file-upload-zone-icon {
  width: 2rem;
  height: 2rem;
  margin-block-end: var(--space-sm);
  color: var(--colour-text-muted);
  opacity: 0.6;
}

.dz-file-upload-zone-prompt {
  font-size: var(--text-sm);
  color: var(--colour-text-muted);
}

.dz-file-upload-progress {
  width: 100%;
  height: 0.25rem;
  margin-block-start: var(--space-sm);
  border-radius: 2px;
}

/* ─────────────────────────── search-select wrapper ──────────────
 * UX-028 search-select dynamic search; the wrapper is the include
 * `fragments/search_select.html` which has its own internal
 * structure. We don't override that here — only style the label /
 * hint / error around it. The fragment-level CSS would migrate
 * separately when we get to fragments/. */

/* No rules needed — search-select inherits .dz-form-label + hint
 * + error from the surrounding macro structure. */

/* ─────────────────────────── colorpicker (UX-024) ───────────────
 * Native <input type="color"> + hex display sit inline next to each
 * other. #976 dropped the Pickr wrapper; the native input ships ~36 KB
 * lighter and covers every actual usage in the example apps. */

.dz-form-color-group {
  display: flex;
  align-items: center;
  gap: var(--space-md);
}

.dz-form-color-input {
  /* Strip the user-agent native chrome down to a flat 2rem swatch
   * so the button matches the visual rhythm of the other dz-form-*
   * inputs. Cross-browser quirks: WebKit's swatch renders inside
   * `::-webkit-color-swatch-wrapper`; Firefox renders the swatch
   * directly on the input. Both are squared-off here. */
  block-size: 2rem;
  inline-size: 2rem;
  padding: 0;
  border-radius: var(--radius-sm);
  border: 1px solid var(--colour-border);
  background: transparent;
  cursor: pointer;
  transition: filter var(--duration-fast) var(--ease-out);
}

.dz-form-color-input:hover {
  filter: brightness(1.05);
}

.dz-form-color-input::-webkit-color-swatch-wrapper {
  padding: 0;
}

.dz-form-color-input::-webkit-color-swatch {
  border: 0;
  border-radius: calc(var(--radius-sm) - 1px);
}

.dz-form-color-input::-moz-color-swatch {
  border: 0;
  border-radius: calc(var(--radius-sm) - 1px);
}

.dz-form-color-hex {
  font-family: var(--font-mono, monospace);
  font-size: var(--text-xs);
  color: var(--colour-text-muted);
}

/* ─────────────────────────── richtext (UX-025) ──────────────────
 * Quill editor wrapper. Border + radius live on the wrapper; Quill
 * internals manage their own toolbar/editor styling. */

.dz-form-richtext {
  border: 1px solid var(--colour-border);
  border-radius: var(--radius-sm);
  overflow: hidden;
  background: var(--colour-bg);
}

.dz-form-richtext[aria-invalid="true"] {
  border-color: var(--colour-danger);
}

/* ─────────────────────────── slider (UX-023) ────────────────────
 * Native <input type="range"> + dzRangeTooltip controller. */

.dz-form-slider-group {
  display: flex;
  align-items: center;
  gap: var(--space-md);
}

.dz-form-slider {
  inline-size: 100%;
}

.dz-form-slider-value {
  font-family: var(--font-mono, monospace);
  font-size: var(--text-xs);
  color: var(--colour-text);
  min-inline-size: 2rem;
  text-align: end;
}

/* ─────────────────────────── companion regions (#923) ───────────
 * Read-only panels that sit alongside form sections — top, bottom,
 * or below a named section anchor. The panel is a muted card with a
 * tighter typographic hierarchy than the form itself. */

.dz-form-companion {
  margin-block: var(--space-md);
  padding: var(--space-md);
  border-radius: var(--radius-md);
  background: var(--colour-bg);
  border: 1px solid var(--colour-border);
}

.dz-form-companion-eyebrow {
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--colour-text-muted);
  margin-block-end: var(--space-2xs);
}

.dz-form-companion-title {
  font-size: var(--text-md);
  font-weight: var(--weight-semibold);
  color: var(--colour-text);
  margin-block-end: var(--space-sm);
}

/* summary_row — horizontal metric tiles. */
.dz-form-companion-summary-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
  gap: var(--space-sm);
}

.dz-form-companion-metric {
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
  padding: var(--space-sm);
  border-radius: var(--radius-sm);
  background: var(--colour-surface);
  border: 1px solid var(--colour-border);
}

.dz-form-companion-metric-label {
  font-size: var(--text-xs);
  color: var(--colour-text-muted);
  text-transform: capitalize;
}

.dz-form-companion-metric-value {
  font-size: var(--text-lg);
  font-weight: var(--weight-semibold);
  color: var(--colour-text);
  font-variant-numeric: tabular-nums;
}

.dz-form-companion-metric-expr {
  display: none;  /* runtime-evaluated expression — debug only */
}

/* status_list — vertical title + caption rows. */
.dz-form-companion-status-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

.dz-form-companion-entry {
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
  padding-inline-start: var(--space-md);
  border-inline-start: 2px solid var(--colour-border);
}

.dz-form-companion-entry--ok {
  border-inline-start-color: var(--colour-success, hsl(145 55% 45%));
}

.dz-form-companion-entry--warn {
  border-inline-start-color: var(--colour-warn, hsl(40 90% 55%));
}

.dz-form-companion-entry--pending {
  border-inline-start-color: var(--colour-text-muted);
  opacity: 0.85;
}

.dz-form-companion-entry-title {
  font-weight: var(--weight-medium);
  color: var(--colour-text);
}

.dz-form-companion-entry-caption {
  font-size: var(--text-sm);
  color: var(--colour-text-muted);
  line-height: 1.4;
}

/* pipeline_steps — left-to-right ordered stage cards. */
.dz-form-companion-pipeline {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
  gap: var(--space-sm);
  counter-reset: pipeline-step;
}

.dz-form-companion-stage {
  counter-increment: pipeline-step;
  padding: var(--space-sm);
  border-radius: var(--radius-sm);
  background: var(--colour-surface);
  border: 1px solid var(--colour-border);
  position: relative;
}

.dz-form-companion-stage::before {
  content: counter(pipeline-step);
  display: inline-block;
  width: 1.25rem;
  height: 1.25rem;
  text-align: center;
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  color: var(--colour-text-muted);
  margin-block-end: var(--space-2xs);
}

.dz-form-companion-stage-label {
  display: block;
  font-weight: var(--weight-medium);
  color: var(--colour-text);
}

.dz-form-companion-stage-caption {
  display: block;
  font-size: var(--text-sm);
  color: var(--colour-text-muted);
  line-height: 1.4;
}

.dz-form-companion-placeholder {
  font-size: var(--text-sm);
  color: var(--colour-text-muted);
  font-style: italic;
}

/* ── Selection controls (HaTchi-MaXchi tranche 1) ───────────────────────
   Designed checkbox/radio/switch on NATIVE inputs — appearance:none +
   tokens; keyboard/AT semantics stay the browser's. */

input.dz-checkbox,
input.dz-radio {
  appearance: none;
  width: 1rem;
  height: 1rem;
  margin: 0;
  border: 1px solid var(--colour-border);
  background: var(--colour-surface);
  cursor: pointer;
  flex-shrink: 0;
  display: inline-grid;
  place-content: center;
  transition: background var(--duration-fast) var(--ease-out),
              border-color var(--duration-fast) var(--ease-out);
}

input.dz-checkbox { border-radius: var(--radius-sm); }
input.dz-radio { border-radius: var(--radius-full); }

input.dz-checkbox:checked,
input.dz-radio:checked {
  background: var(--colour-brand);
  border-color: var(--colour-brand);
}

input.dz-checkbox:checked::before {
  content: "";
  width: 0.625rem;
  height: 0.625rem;
  background: var(--colour-brand-contrast);
  clip-path: polygon(14% 44%, 0 60%, 40% 100%, 100% 16%, 84% 4%, 38% 71%);
}

input.dz-radio:checked::before {
  content: "";
  width: 0.375rem;
  height: 0.375rem;
  border-radius: var(--radius-full);
  background: var(--colour-brand-contrast);
}

input.dz-switch {
  appearance: none;
  width: 2rem;
  height: 1.125rem;
  margin: 0;
  border: 1px solid transparent;
  border-radius: var(--radius-full);
  background: var(--neutral-300);
  cursor: pointer;
  flex-shrink: 0;
  position: relative;
  transition: background var(--duration-base) var(--ease-out);
}

input.dz-switch::before {
  content: "";
  position: absolute;
  inset-block-start: 50%;
  inset-inline-start: 2px;
  translate: 0 -50%;
  width: 0.875rem;
  height: 0.875rem;
  border-radius: var(--radius-full);
  background: white;
  box-shadow: var(--shadow-sm);
  transition: inset-inline-start var(--duration-base) var(--ease-out);
}

input.dz-switch:checked {
  background: var(--colour-brand);
}

input.dz-switch:checked::before {
  inset-inline-start: calc(100% - 0.875rem - 2px);
}

@media (prefers-color-scheme: dark) {
  input.dz-switch { background: var(--neutral-700); }
}

[data-theme="dark"] input.dz-switch { background: var(--neutral-700); }
[data-theme="dark"] input.dz-switch:checked { background: var(--colour-brand); }

@media (prefers-reduced-motion: reduce) {
  input.dz-switch, input.dz-switch::before { transition: none; }
}
/* --- hatchi-maxchi/components/fragment-primitives.css --- */
/*
 * fragment-primitives.css — visual chrome for typed Fragment renderer output.
 *
 * The Fragment renderer (src/dazzle/render/fragment/renderer.py) emits a
 * deterministic class vocabulary derived from the typed primitive types.
 * Each class needs a matching rule here so flipped surfaces (those with
 * `render: fragment` in DSL) render with the same visual treatment as
 * their Jinja-path equivalents.
 *
 * Rules use the framework's design tokens — never hardcoded colours,
 * spaces, or radii — so theme overrides and token-sheet swaps cascade
 * automatically.
 *
 * Plan 4 scope: simple_task.task_list (Surface + Heading + Region.list +
 * Text + Table). Plan 5+ extends as more surfaces flip.
 */

@layer components {

  /* ───────────────────────── Page ────────────────────────────
   * Top-level body class emitted by the Page primitive. The page
   * primitive wraps `<html>`/`<head>`/`<body>` for typed-Fragment
   * chrome rendering; this rule provides the body-level baseline so
   * Page-rendered pages cohabit cleanly with the existing Jinja
   * shells until the chrome migration completes.
   */

  .dz-page {
    margin: 0;
    min-height: 100vh;
    background: var(--colour-bg, var(--colour-surface));
    color: var(--colour-text);
    font-family: var(--font-sans, "Inter", system-ui, sans-serif);
  }

  /* Bare-Page chrome: when the Page body is a single Stack (the
   * shape used by `build_app_403_view` / `build_site_403_view` and
   * their 404/500 cousins — see #1081) the typed view has no
   * AppShell wrapper to provide padding or maximum width. Without
   * these rules the content sits flush against the viewport edge
   * as raw browser-default text. The `:only-child` selector
   * deliberately narrows the rule so a normal Page → AppShell →
   * Stack layout is unaffected — the AppShell intervenes between
   * Page and Stack, so the selector doesn't match. */
  .dz-page > .dz-stack:only-child {
    max-width: 36rem;
    margin: var(--space-xl) auto;
    padding: var(--space-xl) var(--space-lg);
  }

  /* ───────────────────────── AppShell ────────────────────────
   * Multi-slot application layout — sidebar / header / main / footer.
   * Mirrors the legacy app_shell.html structure so the same component
   * CSS in `components/fragments.css` (`.dz-app-shell`, `.dz-app-content`)
   * applies. The Fragment AppShell adds the missing CSS hooks for
   * the typed slots (`.dz-app-sidebar`, `.dz-app-header`, `.dz-app-main`,
   * `.dz-app-footer`) so a Fragment-rendered shell still gets layout
   * structure even when components/fragments.css isn't loaded.
   */

  .dz-app-sidebar {
    /* Composes with the existing dz-sidebar styling when present;
       on its own, provides a minimal column. Width tracks the
       legacy template's lg:w-64 (16rem). */
    flex: 0 0 auto;
    width: 16rem;
  }

  .dz-app-header {
    /* Top bar; vertical rhythm matches the dz-topbar component. */
    display: flex;
    align-items: center;
    padding: var(--space-sm) var(--space-md);
    border-block-end: 1px solid var(--colour-border);
  }

  .dz-app-main {
    /* The main content slot — flex-grows to fill the column. */
    flex: 1 1 auto;
    padding: var(--space-md);
  }

  .dz-app-footer {
    padding: var(--space-sm) var(--space-md);
    border-block-start: 1px solid var(--colour-border);
  }

  /* ───────────────────────── ErrorPage ───────────────────────
   * Standalone error page (404, 500, auth fallback).
   * Centred column with large status code, message, optional
   * home link. Used inside Page.body for routes without AppShell.
   */

  .dz-error-page {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--space-md);
    min-height: 60vh;
    padding: var(--space-xl) var(--space-md);
    text-align: center;
  }

  .dz-error-page__code {
    margin: 0;
    font-size: var(--text-4xl, 3rem);
    font-weight: var(--weight-bold);
    color: var(--colour-text-muted);
    letter-spacing: -0.02em;
  }

  .dz-error-page__message {
    margin: 0;
    font-size: var(--text-lg);
    color: var(--colour-text);
    max-width: 40rem;
  }

  .dz-error-page__action {
    margin-block-start: var(--space-md);
    padding: var(--space-sm) var(--space-lg);
    color: var(--colour-brand-contrast);
    background: var(--colour-brand);
    border-radius: var(--radius-sm);
    text-decoration: none;
  }

  /* ───────────────────────── Topbar ──────────────────────────
   * Application top bar — leading / title / trailing layout.
   * Class names match the legacy template's `dz-topbar` /
   * `dz-topbar-leading` / `dz-topbar-title` / `dz-topbar-trailing`
   * so existing component CSS applies unchanged.
   */

  .dz-topbar {
    display: flex;
    align-items: center;
    gap: var(--space-md);
    padding: var(--space-sm) var(--space-md);
    border-block-end: 1px solid var(--colour-border);
  }

  .dz-topbar-leading {
    display: flex;
    align-items: center;
    gap: var(--space-xs);
  }

  .dz-topbar-title {
    flex: 1 1 auto;
    min-width: 0;  /* allow truncation if title overflows */
  }

  .dz-topbar-title-text {
    font-weight: var(--weight-semibold);
    color: var(--colour-text);
  }

  .dz-topbar-trailing {
    display: flex;
    align-items: center;
    gap: var(--space-xs);
    margin-inline-start: auto;
  }

  /* ───────────────────────── Sidebar / Nav ───────────────────
   * Navigation primitives — Sidebar, NavGroup, NavItem.
   * Mirrors the legacy template's nav-link conventions; in particular
   * `[aria-current="page"]` styling so active state has one source of
   * truth between the visual and accessibility layers.
   */

  .dz-sidebar {
    display: flex;
    flex-direction: column;
    gap: var(--space-sm);
    padding: var(--space-md);
    height: 100%;
    background: var(--colour-surface);
    border-inline-end: 1px solid var(--colour-border);
  }

  .dz-sidebar__header {
    padding-block-end: var(--space-sm);
    border-block-end: 1px solid var(--colour-border);
  }

  .dz-sidebar__items,
  .dz-nav-group__items {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-2xs, 2px);
  }

  .dz-nav-item {
    display: block;
  }

  .dz-nav-link {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    padding: var(--space-xs) var(--space-sm);
    color: var(--colour-text);
    text-decoration: none;
    border-radius: var(--radius-sm);
  }

  .dz-nav-link:hover {
    background: light-dark(rgba(0, 0, 0, 0.04), rgba(255, 255, 255, 0.04));
  }

  .dz-nav-link[aria-current="page"] {
    /* Active state — visible distinction without overriding the
       hover treatment. */
    background: light-dark(rgba(0, 0, 0, 0.06), rgba(255, 255, 255, 0.08));
    font-weight: var(--weight-medium);
  }

  .dz-nav-group {
    /* `<details>` element — no list-style on summary in modern browsers. */
  }

  .dz-nav-group__header {
    cursor: pointer;
    padding: var(--space-xs) var(--space-sm);
    color: var(--colour-text-muted);
    font-size: var(--text-sm);
    font-weight: var(--weight-medium);
    text-transform: uppercase;
    letter-spacing: 0.05em;
    list-style: none;  /* hide the default <details> marker */
  }

  .dz-nav-group__header::-webkit-details-marker {
    display: none;
  }

  /* ───────────────────────── Surface ─────────────────────────
   * Top-level rendered surface — the typed Fragment equivalent of
   * a workspace's main page or a list/detail page wrapper.
   * Slots: header (titles), body (content), footer (optional).
   */

  .dz-surface {
    display: flex;
    flex-direction: column;
    gap: var(--space-md);
    padding: var(--space-md);
    background: var(--colour-surface);
    color: var(--colour-text);
  }

  .dz-surface__header {
    display: flex;
    flex-direction: column;
    gap: var(--space-xs);
    padding-block-end: var(--space-sm);
    border-block-end: 1px solid var(--colour-border);
  }

  .dz-surface__body {
    display: flex;
    flex-direction: column;
    gap: var(--space-md);
  }

  .dz-surface__footer {
    padding-block-start: var(--space-sm);
    border-block-start: 1px solid var(--colour-border);
  }

  /* ───────────────────────── Heading ─────────────────────────
   * Level-parameterised heading. Level 1 is the page-title weight;
   * 2-3 are section headings; 4-6 are sub-headings used inside
   * regions and panels. Sizing pulls from the same scale as the
   * Jinja-path heading variants.
   */

  .dz-heading {
    margin: 0;
    color: var(--colour-text);
    font-weight: var(--weight-semibold);
    line-height: 1.25;
  }

  .dz-heading--level-1 { font-size: var(--text-xl); }
  .dz-heading--level-2 { font-size: var(--text-lg); }
  .dz-heading--level-3 { font-size: var(--text-base); }
  .dz-heading--level-4 {
    font-size: var(--text-sm);
    font-weight: var(--weight-medium);
  }
  .dz-heading--level-5 {
    font-size: var(--text-sm);
    font-weight: var(--weight-medium);
    color: var(--colour-text-muted);
  }
  .dz-heading--level-6 {
    font-size: var(--text-xs);
    font-weight: var(--weight-medium);
    color: var(--colour-text-muted);
    text-transform: uppercase;
    letter-spacing: 0.05em;
  }

  /* ───────────────── Layout primitives ──────────────────────
   * Stack (vertical), Row (horizontal), Grid (n-column), Split
   * (two-panel). Emitted by src/dazzle/render/fragment/renderer
   * /_render_layout.py as `<div class="dz-{primitive} dz-{primitive}--
   * gap-{none|sm|md|lg}">…</div>`. Without these base rules the
   * children flow inline with zero spacing — the 403/404/500 marketing
   * pages all rendered "Go to DashboardGo Home" run-together until
   * #1079 added these. Split has its own grid in site-sections.css
   * (different naming scheme) and is not styled here.
   */

  /* Layouts L2: the Stack/Row layout rules retired — the Stack primitive
   * emits the HM stack Hyperpart contract (base rule + data-dz-gap scale
   * in layout.css) and Row emits the cluster Hyperpart. .dz-stack's base
   * rule moved to layout.css with the rest of its Hyperpart. */

  .dz-grid {
    display: grid;
    gap: var(--space-md);
  }

  /* Column counts: Grid.columns is an int in [1, 12]. Generated
   * exhaustively rather than via `repeat(auto-fit, ...)` so the
   * column count is exactly what the primitive declares. */
  .dz-grid--columns-1 { grid-template-columns: repeat(1, minmax(0, 1fr)); }
  .dz-grid--columns-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .dz-grid--columns-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .dz-grid--columns-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  .dz-grid--columns-5 { grid-template-columns: repeat(5, minmax(0, 1fr)); }
  .dz-grid--columns-6 { grid-template-columns: repeat(6, minmax(0, 1fr)); }
  .dz-grid--columns-7 { grid-template-columns: repeat(7, minmax(0, 1fr)); }
  .dz-grid--columns-8 { grid-template-columns: repeat(8, minmax(0, 1fr)); }
  .dz-grid--columns-9 { grid-template-columns: repeat(9, minmax(0, 1fr)); }
  .dz-grid--columns-10 { grid-template-columns: repeat(10, minmax(0, 1fr)); }
  .dz-grid--columns-11 { grid-template-columns: repeat(11, minmax(0, 1fr)); }
  .dz-grid--columns-12 { grid-template-columns: repeat(12, minmax(0, 1fr)); }

  /* ───────────────────────── Region ──────────────────────────
   * A semantic content region inside a Surface. The `kind`
   * modifier drives the layout: list (table-shaped), detail
   * (definition-list-shaped), form (FormStack), dashboard
   * (Grid), kanban, calendar, report.
   *
   * Plan 4 scope: list only. Other kinds get base treatment but
   * no kind-specific styling until later plans.
   */

  .dz-region {
    display: flex;
    flex-direction: column;
    gap: var(--space-sm);
  }

  .dz-region--kind-list {
    /* List-mode region wraps a Table primitive. The table itself
       inherits its core styling from table.css; this rule provides
       the list-context spacing and any list-specific cascade
       (e.g. dz-table inside .dz-region--kind-list gets the same
       row-hover treatment as the Jinja .dz-list-table path). */
    overflow-x: auto;
  }

  .dz-region--kind-list > .dz-table {
    width: 100%;
    border-collapse: collapse;
    background: var(--colour-surface);
  }

  .dz-region--kind-list > .dz-table thead > tr {
    border-block-end: 1px solid var(--colour-border);
    text-align: left;
  }

  .dz-region--kind-list > .dz-table th {
    padding: var(--space-sm) var(--space-md);
    font-size: var(--text-sm);
    font-weight: var(--weight-medium);
    color: var(--colour-text-muted);
  }

  .dz-region--kind-list > .dz-table > tbody > tr {
    border-block-end: 1px solid var(--colour-border);
    transition: background-color 0.12s ease;
  }

  .dz-region--kind-list > .dz-table > tbody > tr:last-child {
    border-block-end: none;
  }

  .dz-region--kind-list > .dz-table > tbody > tr:hover {
    /* No --colour-surface-hover token exists yet; light/dark fallback
       picks a subtle tint that works in both modes. Promote to a token
       when more surfaces need it. */
    background: light-dark(rgba(0, 0, 0, 0.04), rgba(255, 255, 255, 0.04));
  }

  .dz-region--kind-list > .dz-table td {
    padding: var(--space-sm) var(--space-md);
    color: var(--colour-text);
  }

  /* Detail kind — definition-list-shaped layout. Each child Row
     renders as label (Heading level 4) + value (Text). The Stack
     wrapper provides the row separation; this rule provides the
     intra-row layout (label-column + value-column). */

  .dz-region--kind-detail {
    overflow-x: auto;
  }

  /* L2: the contextual Row-as-grid rules retired — the detail field
   * layout is the DetailGrid primitive's <dl> (dz-detail-region-grid),
   * not a hijacked Row. */

  /* ───────────────────────── Related ─────────────────────────
   * Related-group region — appended to detail surfaces, one per
   * related entity group (e.g. user_detail showing tasks +
   * comments). Skeleton placeholder lives here today; htmx-loaded
   * rows arrive in a later plan.
   */

  .dz-region--kind-related {
    margin-block-start: var(--space-lg);
    padding-block-start: var(--space-md);
    border-block-start: 1px solid var(--colour-border);
  }

  .dz-region--kind-related .dz-heading--level-2 {
    margin-block-end: var(--space-sm);
  }

  /* ───────────────────────── Form ────────────────────────────
   * Form-mode region — vertical stack of fields with label-above-input
   * layout. Used by CREATE and EDIT surfaces.
   */

  .dz-region--kind-form {
    /* Region container; FormStack inside provides actual layout. */
  }

  .dz-form-stack {
    display: flex;
    flex-direction: column;
    gap: var(--space-md);
    max-width: 40rem;
  }

  .dz-field {
    display: flex;
    flex-direction: column;
    gap: var(--space-xs);
  }

  .dz-field__label {
    font-size: var(--text-sm);
    font-weight: var(--weight-medium);
    color: var(--colour-text);
  }

  .dz-field__input {
    padding: var(--space-sm) var(--space-md);
    font-size: var(--text-base);
    color: var(--colour-text);
    background: var(--colour-surface);
    border: 1px solid var(--colour-border);
    border-radius: var(--radius-sm);
  }

  .dz-field__input:focus {
    outline: 2px solid var(--colour-accent);
    outline-offset: 1px;
  }

  .dz-combobox {
    display: flex;
    flex-direction: column;
    gap: var(--space-xs);
  }

  .dz-combobox__label {
    font-size: var(--text-sm);
    font-weight: var(--weight-medium);
    color: var(--colour-text);
  }

  .dz-combobox__select {
    padding: var(--space-sm) var(--space-md);
    font-size: var(--text-base);
    color: var(--colour-text);
    background: var(--colour-surface);
    border: 1px solid var(--colour-border);
    border-radius: var(--radius-sm);
  }

  /* Plan 14: RefPicker — REF field selector. Parallels .dz-combobox in
     visual treatment; the data-ref-api attribute drives client-side
     option population via dz.filterRefSelect. */
  .dz-ref-picker {
    display: flex;
    flex-direction: column;
    gap: var(--space-xs);
  }

  .dz-ref-picker__label {
    font-size: var(--text-sm);
    font-weight: var(--weight-medium);
    color: var(--colour-text);
  }

  .dz-ref-picker__select {
    padding: var(--space-sm) var(--space-md);
    font-size: var(--text-base);
    color: var(--colour-text);
    background: var(--colour-surface);
    border: 1px solid var(--colour-border);
    border-radius: var(--radius-sm);
  }

  .dz-submit {
    align-self: flex-start;
    padding: var(--space-sm) var(--space-lg);
    font-size: var(--text-base);
    font-weight: var(--weight-medium);
    color: var(--colour-brand-contrast);
    background: var(--colour-brand);
    border: none;
    border-radius: var(--radius-sm);
    cursor: pointer;
  }

  .dz-submit--variant-secondary {
    background: var(--colour-surface);
    color: var(--colour-text);
    border: 1px solid var(--colour-border);
  }

  .dz-submit--variant-danger {
    /* fixed step: fill carries white text; the semantic token flips light in dark */
    background: var(--danger-600);
    color: var(--colour-brand-contrast);
  }

  /* ───────────────────────── Text ────────────────────────────
   * Inline text primitive — used inside cells, in EmptyState
   * descriptions, badges, and as a bare leaf primitive. The tone
   * modifier controls colour role (default/muted/danger/success/
   * warning); other tones map to existing token roles.
   */

  .dz-text {
    color: var(--colour-text);
  }

  .dz-text--tone-default { color: var(--colour-text); }
  .dz-text--tone-muted   { color: var(--colour-text-muted); }
  .dz-text--tone-danger  { color: var(--colour-danger); }
  .dz-text--tone-success { color: var(--colour-success); }
  .dz-text--tone-warning { color: var(--colour-warning); }

}  /* @layer components */

/* ── Icon primitive + inline-SVG icons (HaTchi-MaXchi TASTE-6) ──────────
   Server-rendered Lucide SVGs fill their fixed-size wrapper and inherit
   currentColor; sizes sit on the type scale. */
/* Canonical icon contract — see the Icon Hyperpart page. Sized in `em` so an
   icon scales with its context's font-size; the size scale pins absolute sizes. */
.dz-icon,
.dz-task-inbox-item-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  vertical-align: -0.125em;
  width: 1em;
  height: 1em;
  color: inherit;
  stroke: currentColor;
  /* Lucide stroke defaults must live on the REFERENCING element: `<use>`
     shadow content inherits these from here, NOT from the sprite sheet's
     outer <svg> — without them, sprite icons render at the browser default
     stroke-width:1 / butt caps (thin, squared). */
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
  fill: none;
}
.dz-icon-solid { fill: currentColor; stroke: none; }

.dz-task-inbox-item-icon {
  width: 1.25rem;
  height: 1.25rem;
  color: var(--colour-text-muted);
}

.dz-icon--size-xs { width: 0.75rem; height: 0.75rem; }
.dz-icon--size-sm { width: 1rem; height: 1rem; }
.dz-icon--size-md { width: 1.25rem; height: 1.25rem; }
.dz-icon--size-lg { width: 1.5rem; height: 1.5rem; }
.dz-icon--size-xl { width: 2rem; height: 2rem; }

.dz-icon svg,
.dz-action-card-icon svg,
.dz-status-list-icon svg,
.dz-task-inbox-item-icon svg {
  width: 100%;
  height: 100%;
  display: block;
}

/* Icon gallery demo — the size scale in a row (currentColor from the accent). */
.dz-icon-demo {
  display: flex;
  align-items: center;
  gap: var(--space-lg);
  color: var(--colour-brand);
}

/* ── Nav icons (HaTchi-MaXchi Phase 3, TASTE-6) ─────────────────────────
   Every sidebar item carries a registry SVG — authored or inferred. Muted
   at rest; inherits the link colour on hover/active. */
.dz-nav-link__icon,
.dz-nav-group__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1rem;
  height: 1rem;
  flex-shrink: 0;
  color: var(--colour-text-muted);
}

.dz-nav-link:hover .dz-nav-link__icon,
.dz-nav-link[aria-current="page"] .dz-nav-link__icon {
  color: currentColor;
}

.dz-nav-link__icon svg,
.dz-nav-group__icon svg {
  width: 100%;
  height: 100%;
  display: block;
}

.dz-nav-link {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
}

/* Empty state — the "no data" primitive. Consolidated here (the single home)
 * from the legacy base/design-system.css: the base container + `__` parts now
 * live together. The old broad `.dz-empty-state svg { 3rem }` fossil was
 * dropped — it fought `.dz-empty-state__icon` on specificity (0,1,1 vs 0,1,0),
 * so a bare-svg icon rendered 3rem while a wrapped `__icon svg` rendered 2rem.
 * The `__icon` wrapper (2rem) is now the one icon contract. */
.dz-empty-state {
  text-align: center;
  padding: 3rem 1.5rem;
  color: var(--colour-text-muted);
  /* Column-centred layout folded in from the legacy Dazzle `dazzle-layer.css`
   * `.dz-empty-state` (HMC-003c). Same selector, disjoint properties → the
   * served computed style is the byte-identical union of the two former rules. */
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-height: 12rem;
}

/* Empty-state icon (TASTE-8): quiet registry glyph above the message. */
.dz-empty-state__title {
  color: var(--colour-text); /* tranche-1 nit: was too dim on dark surfaces */
  /* weight + spacing folded from legacy `.dz-empty-state h3` (HMC-003c). Every
   * emitted title is `<h3 class="dz-empty-state__title">`, so the element→BEM
   * move is computed-identical (the old 0,1,1 `h3` rule out-specified this 0,1,0
   * rule only on `color`, which was the same value). */
  font-weight: 600;
  margin-bottom: 0.25rem;
}

/* Description line — folded from legacy `.dz-empty-state p` (HMC-003c). Every
 * emitted description is `<p class="dz-empty-state__description">`. */
.dz-empty-state__description {
  color: var(--colour-text-muted);
  font-size: 0.875rem;
}

.dz-empty-state__icon {
  display: inline-flex;
  width: 2rem;
  height: 2rem;
  color: var(--colour-text-muted);
  margin-block-end: var(--space-sm);
}

.dz-empty-state__icon svg {
  width: 100%;
  height: 100%;
  display: block;
}

/* ── Error pages (403/404/500) — HaTchi-MaXchi #1536 ────────────────────
   The app-shell-lite error views render Page > Stack > Card(EmptyState).
   Centre that lone card in the viewport; everything else on these pages
   inherits the empty-state styling. */
.dz-page > .dz-stack:has(> .dz-card .dz-empty-state) {
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: var(--space-2xl);
  background: var(--colour-bg);
}

.dz-page > .dz-stack:has(> .dz-card .dz-empty-state) > .dz-card {
  width: 100%;
  max-width: 28rem;
  text-align: center;
  padding: var(--space-2xl);
}

.dz-page > .dz-stack:has(> .dz-card .dz-empty-state) .dz-empty-state__action a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 2.25rem;
  padding-inline: var(--space-lg);
  border-radius: var(--radius-md);
  background: var(--colour-brand);
  color: var(--colour-brand-contrast);
  font-weight: var(--weight-medium);
  text-decoration: none;
  box-shadow: var(--shadow-sm);
}

.dz-page > .dz-stack:has(> .dz-card .dz-empty-state) .dz-empty-state__action a:hover {
  background: var(--brand-700);
}
/* --- hatchi-maxchi/components/fragments.css --- */
/*
 * fragments.css — shared HTML-fragment chrome (HaTchi-MaXchi).
 *
 * Assorted fragment/region primitives: sentinel + loading spinners, command
 * palette, toast/bulk/stepper/date-range/context-menu/slide-over/search-flow/
 * 2FA/a11y/related/tooltip/inline-edit/ref-cell/search-input/search-select/
 * app-shell/toast-stack/form-chrome/review-queue/modal/island/error-pages/
 * card-picker/pipeline-steps/experience-shell families. Heavily HM-token-based
 * (293 var(--…) refs). Migrated wholesale from Dazzle into HM (HMC-016,
 * aggressive-mode convergence 2026-07-08); the redundant `.dz-empty-dense`
 * (HM detail.css owns the canonical one) was dropped in the move.
 */

/* ─────────────────────────── empty_state fragment ───────────────
 * The `.dz-empty-state` primitive is now owned by the HM
 * `empty-state` Hyperpart (components/fragment-primitives.css:
 * base container + `__icon`/`__title`/`__description`/`__action`),
 * ingested into the dist. All emitters (the typed substrate + the
 * table/chart read-only empties) emit the `__` form; the bespoke
 * `.dz-empty-state`/`-icon`/`-message`/`-cta` rules were deleted in
 * the Bucket B empty-state convergence (build-to-replace, ADR-0003). */

/* table_pagination — the `.dz-pagination*` family moved to the HM `pagination`
 * Hyperpart (packages/hatchi-maxchi/components/pagination.css) as the canonical
 * definition; Dazzle's `_emit_pagination` consumes it unchanged (byte-identical
 * values + a focus ring). Retired here 2026-07-04. The data-table row / filter
 * bar / bulk-action families (`.dz-tr-*`, `.dz-filter-*`, `.dz-bulk-*`) likewise
 * moved to the HM `table.css` (grid Hyperpart, Slice 0b, 2026-07-04). */

/* ─────────────────────────── toast fragment ─────────────────────
 * Auto-dismissing notification (5s via htmx remove-me). 4 tones
 * keyed off data-dz-toast-level — replaces two parallel dictionary
 * lookups for border-l + icon colour. */

.dz-toast {
  pointer-events: auto;
  max-width: 24rem;
  border-radius: var(--radius-md);
  border: 1px solid var(--colour-border);
  border-inline-start-width: 4px;
  background: var(--colour-surface);
  color: var(--colour-text);
  box-shadow: var(--shadow-md);
  padding-inline: var(--space-md);
  padding-block: var(--space-md);
  display: flex;
  align-items: flex-start;
  gap: var(--space-md);
  transition: all var(--duration-base) var(--ease-out);
}

.dz-toast[data-dz-toast-level="success"] { border-inline-start-color: var(--colour-success); }
.dz-toast[data-dz-toast-level="error"]   { border-inline-start-color: var(--colour-danger); }
.dz-toast[data-dz-toast-level="warning"] { border-inline-start-color: var(--colour-warning); }
.dz-toast[data-dz-toast-level="info"]    { border-inline-start-color: var(--colour-brand); }

.dz-toast-icon {
  width: 0.75rem;
  height: 0.75rem;
  margin-block-start: 3px;
  flex-shrink: 0;
}

.dz-toast[data-dz-toast-level="success"] .dz-toast-icon { color: var(--colour-success); }
.dz-toast[data-dz-toast-level="error"]   .dz-toast-icon { color: var(--colour-danger); }
.dz-toast[data-dz-toast-level="warning"] .dz-toast-icon { color: var(--colour-warning); }
.dz-toast[data-dz-toast-level="info"]    .dz-toast-icon { color: var(--colour-brand); }

.dz-toast-message {
  font-size: var(--text-sm);
  color: var(--colour-text);
}

/* ─────────────────────────── bulk_actions fragment ──────────────
 * Delete-only bulk actions toolbar. Visible via Alpine x-show
 * gated on bulkCount > 0. */

/* ─────────────────────────── form_stepper + steps_indicator ─────
 * MOVED to HaTchi-MaXchi components/form-chrome.css (Tier F3, the
 * fleet-convergence directive). Same selectors, via the HM dist. */

/* ─────────────────────────── date_range_picker fragment ─────────
 * MOVED to HaTchi-MaXchi components/date-range.css (Tier F3). */

/* ─────────────────────────── detail_fields fragment ─────────────
 * Definition-list renderer used by the API read handler for
 * HTMX/browser content negotiation. Lighter version of the
 * detail_view component (no header / actions / related groups). */

.dz-detail-fields-card {
  background: var(--colour-surface);
  border: 1px solid var(--colour-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-sm);
}

.dz-detail-fields-body {
  padding: var(--space-lg);
}

.dz-detail-fields-title {
  font-size: var(--text-lg);
  font-weight: var(--weight-semibold);
  color: var(--colour-text);
  margin: 0;
}

.dz-detail-fields-list {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--space-xs) var(--space-md);
  margin-block-start: var(--space-md);
}

/* #1533: full-length prose on detail pages — the list-cell truncate
 * wrapper never appears here. pre-wrap keeps textarea paragraphs. */
.dz-detail-text {
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.dz-detail-fields-key {
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  color: var(--colour-text-muted);
  text-align: end;
  padding-block: 0.25rem;
}

.dz-detail-fields-value {
  font-size: var(--text-sm);
  padding-block: 0.25rem;
  word-break: break-all;
}

/* ─────────────────────────── context_menu fragment ──────────────
 * Right-click triggered menu. Panel positioned absolutely at cursor
 * coordinates via Alpine `:style`. */

.dz-context-menu {
  position: relative;
}

.dz-context-menu-panel {
  z-index: 50;
  min-width: 11rem;
  padding-block: 0.25rem;
  border-radius: var(--radius-sm);
  background: var(--colour-surface);
  border: 1px solid var(--colour-border);
  box-shadow: var(--shadow-md);
  font-size: var(--text-sm);
  list-style: none;
  margin: 0;
  padding-inline-start: 0;
}

.dz-context-menu-divider {
  margin-block: 0.25rem;
  border-block-start: 1px solid var(--colour-border);
}

.dz-context-menu-item {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  padding-inline: var(--space-md);
  padding-block: 0.375rem;
  color: var(--colour-text);
  text-decoration: none;
  transition: background var(--duration-fast) var(--ease-out);
}

.dz-context-menu-item:hover {
  background: var(--colour-bg);
}

.dz-context-menu-item-icon {
  width: 0.875rem;
  height: 0.875rem;
  color: var(--colour-text-muted);
}

/* ─────────────────────────── slide-over ─────────────────────────
 * DELETED (Tier F2, 2026-07-06): the peek slide_over panel converged
 * onto the HM drawer Hyperpart (native <dialog class="dz-drawer">,
 * packages/hatchi-maxchi/components/drawer.css) — focus trap, inert
 * background, Esc/backdrop dismissal are native. The .dz-slideover-*
 * family had no other producers. */

/* ─────────────────────────── table_sentinel fragment ────────────
 * Infinite-scroll sentinel row. Triggers loading more rows when
 * revealed via htmx hx-trigger="revealed". The visible spinner is
 * just a sized box with animation; sr-only text lives in the
 * fragment template. */

.dz-sentinel-cell {
  text-align: center;
  padding-block: var(--space-md);
}

.dz-sentinel-spinner {
  display: inline-block;
  width: 1rem;
  height: 1rem;
  animation: dz-spin 1s linear infinite;
}

/* ─────────────────────────── form_errors fragment ───────────────
 * MOVED to HaTchi-MaXchi components/form-chrome.css (Tier F3). */

/* ─────────────────────────── search-flow fragments ──────────────
 * MOVED to HaTchi-MaXchi components/search-box.css (Tier F3). */

/* ─────────────────────────── experience_transition macro ────────
 * Single source of truth for the 5 transition-button branches in
 * experience/_content.html (#EX-053). Three style variants — primary,
 * ghost, border (default) — keyed off `data-dz-transition-style`. */

.dz-experience-transition {
  display: inline-flex;
  align-items: center;
  height: 2.25rem;
  padding-inline: var(--space-md);
  font-weight: var(--weight-medium);
  border-radius: var(--radius-sm);
  font-size: var(--text-sm);
  border: 0;
  cursor: pointer;
  transition:
    background var(--duration-fast) var(--ease-out),
    color var(--duration-fast) var(--ease-out),
    border-color var(--duration-fast) var(--ease-out),
    filter var(--duration-fast) var(--ease-out);
}

.dz-experience-transition[data-dz-transition-style="primary"] {
  background: var(--colour-brand);
  color: var(--colour-brand-contrast);
}

.dz-experience-transition[data-dz-transition-style="primary"]:hover {
  filter: brightness(1.1);
}

.dz-experience-transition[data-dz-transition-style="ghost"] {
  background: transparent;
  color: var(--colour-text-muted);
}

.dz-experience-transition[data-dz-transition-style="ghost"]:hover {
  background: var(--colour-bg);
  color: var(--colour-text);
}

.dz-experience-transition[data-dz-transition-style="default"],
.dz-experience-transition:not([data-dz-transition-style]) {
  background: transparent;
  color: var(--colour-text);
  border: 1px solid var(--colour-border);
}

.dz-experience-transition[data-dz-transition-style="default"]:hover,
.dz-experience-transition:not([data-dz-transition-style]):hover {
  background: var(--colour-bg);
}

/* ─────────────────────────── auth chrome (2FA views) ────────────
 * MOVED to HaTchi-MaXchi components/two-factor.css (Tier A2,
 * 2026-07-07 — with three latent-mismatch fixes: card-title/-subtitle
 * rule names, the classless QR img, and honest dz-auth-page
 * centering). The dead subset (product/title/subtitle/link-muted/
 * link-button and pseudo-states /qr/qr-image/qr-secret — zero producers) was deleted. */

/* ─────────────────────────── a11y helpers ───────────────────────
 * Loading spinner used as an HTMX indicator. Sized + animated host
 * for the inline span. (.visually-hidden + sr-only-fallback already
 * defined in utilities.css.) */

.dz-loading-spinner {
  display: flex;
  justify-content: center;
  padding-block: var(--space-md);
}

.dz-loading-spinner-icon {
  display: inline-block;
  width: 1.25rem;
  height: 1.25rem;
  animation: dz-spin 1s linear infinite;
}

/* Skip link — visually hidden by default, focusable via Tab. */
.dz-skip-link {
  position: absolute;
  inset-block-start: -100vh;  /* off-screen by default */
  inset-inline-start: 0;
  padding: var(--space-sm);
  background: var(--colour-brand);
  color: var(--colour-brand-contrast);
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  border-radius: var(--radius-sm);
  z-index: 9999;
  text-decoration: none;
  transition: top var(--duration-fast) var(--ease-out);
}

.dz-skip-link:focus,
.dz-skip-link:focus-visible {
  inset-block-start: 0.5rem;
  inset-inline-start: 0.5rem;
}

/* ─────────────────────────── layouts/single_column ──────────────
 * Top bar + main + footer layout used by simpler app shells. */

.dz-layout-shell {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

.dz-layout-topbar {
  display: flex;
  align-items: center;
  padding-inline: var(--space-md);
  padding-block: var(--space-sm);
  background: var(--colour-surface);
  border-block-end: 1px solid var(--colour-border);
  box-shadow: var(--shadow-sm);
}

.dz-layout-topbar-title {
  flex: 1 1 0;
}

.dz-layout-topbar-title-text {
  font-size: var(--text-lg);
  font-weight: var(--weight-semibold);
}

.dz-layout-topbar-nav {
  display: flex;
  gap: var(--space-sm);
  flex: none;
}

.dz-layout-nav-link {
  display: inline-flex;
  align-items: center;
  height: 2rem;
  padding-inline: var(--space-md);
  border-radius: var(--radius-sm);
  color: var(--colour-text-muted);
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  text-decoration: none;
  transition: background var(--duration-fast) var(--ease-out),
              color var(--duration-fast) var(--ease-out);
}

.dz-layout-nav-link:hover {
  background: var(--colour-bg);
  color: var(--colour-text);
}

.dz-layout-main {
  flex: 1 1 0;
  margin-inline: auto;
  padding: var(--space-md);
  max-width: 64rem;
  width: 100%;
}

@media (min-width: 64rem) {
  .dz-layout-main {
    padding: var(--space-lg);
  }
}

.dz-layout-footer {
  text-align: center;
  padding: var(--space-md);
  background: var(--colour-surface);
  color: var(--colour-text);
  border-block-start: 1px solid var(--colour-border);
}

/* Hide default disclosure marker */
/* ─────────────────────────── related-* fragments ────────────────
 * MOVED to HaTchi-MaXchi components/related-tables.css (W2, 2026-07-06 — the
 * fleet-convergence directive). Same selectors, via the HM dist. */

/* ─────────────────────────── tooltip_rich fragment ──────────────
 * HTML-content tooltip with x-anchor positioning. Trigger wraps
 * caller content; panel sits absolutely positioned + transitions
 * via Alpine x-show. */

/* ─────────────────────────── inline_edit fragment ───────────────
 * The InlineEdit fragment primitive's display span. */

.dz-inline-edit {
  position: relative;
  margin-inline: -0.25rem;
  margin-block: -0.125rem;
}

/* The editor-element rules (dz-inline-edit-input/-select/-checkbox) moved
 * to HaTchi-MaXchi table.css (HM 0.1.26 — the dz-grid-edit.js extension owns
 * them; Dazzle consumes the HM dist). The Alpine-era bool-row/spinner/error
 * chrome died with the template in C2.3. `.dz-inline-edit` above stays: the
 * InlineEdit fragment primitive still emits it. */

/* The `.dz-skeleton*` placeholder family that lived here was dead (zero
 * emitters) and collided with the HM `skeleton` Hyperpart's canonical
 * `.dz-skeleton`. Retired 2026-07-04; the live dashboard loading state is
 * `.dz-card-skeleton` (dashboard.css), which keeps the `dz-pulse` keyframe. */

/* ─────────────────────────── ref_cell macro ─────────────────────
 * Reference link styling — used by detail / grid / list / queue
 * regions. Three modes (link / link_stop / htmx_drawer) all render
 * the same anchor chrome. */

.dz-ref-link {
  color: var(--colour-brand);
  text-decoration: none;
  transition: text-decoration-color var(--duration-fast) var(--ease-out);
}

.dz-ref-link:hover {
  text-decoration: underline;
}

/* ─────────────────────────── search_input fragment ──────────────
 * DELETED (Tier F3, 2026-07-06): the dz-search-input / dz-search-clear
 * family had ZERO producers (py/html/js/examples/tests) — the reusable
 * search-input fragment died in an earlier convergence. The live FTS
 * search box (.dz-search-box-*) is styled by HM search-box.css. */

/* ─────────────────────────── attention_accent macro ─────────────
 * MOVED to HaTchi-MaXchi components/timeline.css (W2 — travels with
 * its consumers: timeline bullets + kanban card-attn). */

/* ─────────────────────────── command_palette fragment ───────────
 * Spotlight-style modal triggered by Cmd+K. Backdrop covers the
 * viewport; palette card sits centred at 20vh from the top. Three
 * regions: search input, results listbox, keyboard-hint footer.
 * Selected/hover row distinction driven by .is-selected modifier
 * Alpine binds. */

.dz-cmdk-overlay {
  position: fixed;
  inset: 0;
  z-index: 50;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding-block-start: 20vh;
}

.dz-cmdk-backdrop {
  position: fixed;
  inset: 0;
  background: oklch(0% 0 0 / 0.4);
}

.dz-cmdk-card {
  position: relative;
  width: 100%;
  max-width: 32rem;
  background: var(--colour-surface);
  border: 1px solid var(--colour-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  overflow: hidden;
}

.dz-cmdk-search-row {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  padding-inline: var(--space-md);
  padding-block: var(--space-sm);
  border-block-end: 1px solid var(--colour-border);
}

.dz-cmdk-search-icon {
  width: 1rem;
  height: 1rem;
  flex-shrink: 0;
  color: var(--colour-text-muted);
}

.dz-cmdk-search-input {
  width: 100%;
  background: transparent;
  font-size: var(--text-base);
  color: var(--colour-text);
  border: 0;
  outline: none;
}

.dz-cmdk-search-input::placeholder {
  color: var(--colour-text-muted);
}

.dz-cmdk-results {
  max-height: 20rem;
  overflow-y: auto;
  padding: 0.375rem;
  list-style: none;
  margin: 0;
}

.dz-cmdk-item {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  padding-inline: var(--space-md);
  padding-block: var(--space-sm);
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: background var(--duration-fast) var(--ease-out),
              color var(--duration-fast) var(--ease-out);
}

.dz-cmdk-item:hover {
  background: var(--colour-bg);
}

.dz-cmdk-item.is-selected {
  background: color-mix(in oklch, var(--colour-brand) 10%, transparent);
  color: var(--colour-brand);
}

.dz-cmdk-item-icon {
  width: 1rem;
  height: 1rem;
  flex-shrink: 0;
}

.dz-cmdk-item-text {
  min-width: 0;
}

.dz-cmdk-item-label {
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  color: var(--colour-text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.dz-cmdk-item-group {
  font-size: var(--text-xs);
  color: var(--colour-text-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.dz-cmdk-empty {
  padding-inline: var(--space-md);
  padding-block: var(--space-lg);
  text-align: center;
  font-size: var(--text-sm);
  font-style: italic;
  color: var(--colour-text-muted);
}

.dz-cmdk-footer {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  padding-inline: var(--space-md);
  padding-block: var(--space-sm);
  border-block-start: 1px solid var(--colour-border);
  font-size: var(--text-xs);
  color: var(--colour-text-muted);
}

.dz-cmdk-hint {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
}

.dz-cmdk-key {
  display: inline-flex;
  align-items: center;
  height: 1.25rem;
  padding-inline: 0.375rem;
  border-radius: var(--radius-sm);
  border: 1px solid var(--colour-border);
  background: var(--colour-bg);
  font-family: var(--font-mono);
  font-size: 0.625rem;
  color: var(--colour-text-muted);
}

/* ─────────────────────────── search_select fragment ─────────────
 * MOVED to HaTchi-MaXchi components/search-select.css (Tier F4b),
 * where the results-panel visibility also converged from Alpine
 * x-show onto aria-expanded (dz-search-select.js). */

/* ─────────────────────────── layouts/app_shell ──────────────────
 * MOVED to HaTchi-MaXchi components/app-shell.css (S1, 2026-07-06 —
 * the app-shell directive): the whole family (shell/content/topbar/
 * sidebar-toggle/icon-button/app-main/page-purpose/backdrop/sidebar)
 * now ships via the HM dist; Dazzle's emitted contract is unchanged. */

/* ─────────────────────────── app body chrome ────────────────────
 * <body> level chrome — full-viewport background + min-height so
 * short pages still fill the screen with the surface colour.
 * Replaces inline `min-h-screen bg-[hsl(var(--background))]`. */

.dz-app-body {
  min-height: 100vh;
  background: var(--colour-bg);
}

/* ─────────────────────────── toast stack ────────────────────────
 * Fixed-position container for stacked toast notifications. Two
 * separate stacks (Alpine dzToast + HTMX OOB) share the same
 * chrome — extracting it kills the duplicated Tailwind string. */

.dz-toast-stack {
  position: fixed;
  top: 1rem;
  right: 1rem;
  z-index: 50;
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  max-width: 24rem;
  pointer-events: none;
}

/* ─────────────────────────── form chrome (UX-016) ───────────────
 * Live subset (.dz-form-section/-title/-note) MOVED to HaTchi-MaXchi
 * components/form-chrome.css (Tier F3). The wrapper family
 * (.dz-form-shell/-header/-title/-body/-actions) was DELETED — its
 * producer was the legacy form_renderer.py removed in ADR-0049 Phase
 * 3b; the substrate form path never emitted it. */

/* ─────────────────────────── review queue (UX-030) ─────────────
 * One-at-a-time review chrome. Reuses .dz-detail-* primitives for
 * the field list; only review-specific shell + progress + nav +
 * action chrome lives here. */

.dz-review-shell {
  max-width: 56rem;  /* ~max-w-4xl */
}

.dz-review-progress {
  display: inline-flex;
  align-items: center;
  gap: var(--space-sm);
}

.dz-review-progress-text {
  font-size: var(--text-xs);
  color: var(--colour-text-muted);
}

.dz-review-progress-bar {
  inline-size: 6rem;
  block-size: 0.25rem;
  border-radius: var(--radius-xs);
}

.dz-review-nav {
  display: flex;
  justify-content: space-between;
  margin-block-end: var(--space-md);
}

.dz-review-actions {
  display: flex;
  gap: var(--space-sm);
  justify-content: flex-end;
}

.dz-review-notes-wrap {
  margin-block-end: var(--space-md);
}

.dz-review-notes-wrap[hidden] {
  display: none;
}

.dz-review-notes-label {
  display: block;
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  color: var(--colour-text);
  margin-block-end: var(--space-xs);
}

.dz-review-notes-required {
  display: inline;
  font-size: var(--text-xs);
  font-weight: var(--weight-normal);
  color: var(--colour-danger);
  margin-inline-start: 0.25rem;
}

.dz-review-notes-textarea {
  inline-size: 100%;
  min-block-size: 6rem;
  resize: vertical;
  padding-inline: var(--space-md);
  padding-block: var(--space-sm);
  border-radius: var(--radius-sm);
  background: var(--colour-bg);
  border: 1px solid var(--colour-border);
  color: var(--colour-text);
  font-size: var(--text-sm);
  transition: box-shadow var(--duration-base) var(--ease-out),
              border-color var(--duration-base) var(--ease-out);
}

.dz-review-notes-textarea::placeholder {
  color: var(--colour-text-muted);
}

.dz-review-notes-textarea:focus-visible {
  outline: none;
  box-shadow: 0 0 0 1px var(--colour-brand);
}

.dz-review-empty {
  text-align: center;
  padding-block: var(--space-2xl);
}

.dz-review-empty-icon {
  font-size: 3rem;
  margin-block-end: var(--space-sm);
}

.dz-review-empty-title {
  font-size: var(--text-md);
  font-weight: var(--weight-semibold);
  color: var(--colour-text);
  margin-block-end: var(--space-xs);
}

.dz-review-empty-body {
  font-size: var(--text-sm);
  color: var(--colour-text-muted);
  margin-block-end: var(--space-md);
}

/* ─────────────────────────── modal ──────────────────────────────
 * DELETED (Tier F2, 2026-07-06): the dormant .dz-modal* family — its
 * components/modal.html primitive died with the Jinja teardown and no
 * producer emits the classes. Centered modal needs are the HM dialog
 * Hyperpart (dialog.css); edge panels the HM drawer. */

/* ─────────────────────────── confirm dialog — DELETED ───────────
 * (#1546, 2026-07-07): the dormant confirm-dialog family (dialog /
 * backdrop / panel / title / message / actions / spinner rules) died
 * with its Alpine template in the Jinja teardown; the LIVE
 * confirm_action_panel wrapper chrome it accidentally provided moved
 * to the HM confirm-panel Hyperpart (components/confirm-panel.css).
 * The dz-spin keyframes below are LIVE (loading spinners in this
 * file + dashboard.css) and deliberately survive the deletion. */

@keyframes dz-spin {
  to { transform: rotate(360deg); }
}

/* ─────────────────────────── island (dormant) ───────────────────
 * Generic dashboard island wrapper — used by components/island.html
 * (UX-059), no production consumer. */

.dz-island {
  background: var(--colour-surface);
  border: 1px solid var(--colour-border);
  border-radius: var(--radius-md);
  padding: var(--space-lg);
}

.dz-island-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-block-end: var(--space-md);
}

.dz-island-title {
  font-size: var(--text-md);
  font-weight: var(--weight-semibold);
  color: var(--colour-text);
  margin: 0;
}

.dz-island-skeleton {
  block-size: 8rem;
  inline-size: 100%;
  border-radius: var(--radius-sm);
}

/* ─────────────────────────── in-app error pages ─────────────────
 * 403 + 404 share centred chrome inside the app shell. Used by
 * app/403.html (#776 / #808) and app/404.html. */

.dz-error-page {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-block-size: 60vh;
  padding-inline: var(--space-md);
}

.dz-error-code {
  font-size: 3rem;
  font-weight: var(--weight-semibold);
  line-height: 1;
  color: var(--colour-text);
  margin-block-end: var(--space-md);
}

.dz-error-message {
  font-size: var(--text-md);
  color: var(--colour-text-muted);
  margin-block-end: var(--space-md);
  text-align: center;
  max-inline-size: 30rem;
}

.dz-error-suggestions-card,
.dz-error-detail-card {
  inline-size: 100%;
  max-inline-size: 30rem;
  margin-block-end: var(--space-lg);
  padding: var(--space-md);
  border-radius: var(--radius-md);
  border: 1px solid var(--colour-border);
}

.dz-error-suggestions-card {
  background: var(--colour-surface);
}

.dz-error-detail-card {
  background: color-mix(in oklch, var(--colour-bg) 70%, transparent);
  font-size: var(--text-sm);
  color: var(--colour-text);
}

.dz-error-suggestions-title {
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  color: var(--colour-text);
  margin-block-end: var(--space-sm);
}

.dz-error-suggestions-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  list-style: none;
  margin: 0;
  padding: 0;
}

.dz-error-suggestion-link {
  display: inline-flex;
  align-items: center;
  gap: var(--space-sm);
  font-size: var(--text-sm);
  color: var(--colour-brand);
  text-decoration: none;
}

.dz-error-suggestion-link:hover {
  text-decoration: underline;
}

.dz-error-suggestion-url {
  font-family: var(--font-mono, monospace);
  font-size: var(--text-xs);
  color: var(--colour-text-muted);
}

.dz-error-detail-grid {
  display: grid;
  grid-template-columns: auto 1fr;
  column-gap: var(--space-md);
  row-gap: 0.375rem;
  margin: 0;
}

.dz-error-detail-label {
  color: var(--colour-text-muted);
}

.dz-error-detail-chip {
  display: inline-block;
  margin-inline-end: 0.25rem;
  padding-inline: 0.25rem;
  padding-block: 0.125rem;
  border-radius: var(--radius-xs);
  background: var(--colour-bg);
  border: 1px solid var(--colour-border);
  font-size: var(--text-xs);
  font-family: var(--font-mono, monospace);
}

.dz-error-actions {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
}

/* ─────────────────────────── workspace card picker (UX-038) ─────
 * Popover above the "Add a card" button, listing widget catalog
 * entries. Used by workspace/_card_picker.html. */

/* #982: visibility driven by `[data-show-picker]` on the ancestor
 * `.dz-workspace[x-data="dzDashboardBuilder()"]` rather than Alpine
 * `x-show` on this element. dzDashboardBuilder mirrors `showPicker`
 * to `data-show-picker="1"` via $watch; CSS reveals the picker only
 * when the flag is set. Default state hidden so there's no flash on
 * initial paint (replaces the prior `x-cloak`-style guard). */

.dz-card-picker {
  position: absolute;
  bottom: 100%;
  left: 50%;
  transform: translateX(-50%);
  margin-block-end: var(--space-sm);
  inline-size: 20rem;
  background: var(--colour-surface);
  color: var(--colour-text);
  box-shadow: var(--shadow-md);
  border-radius: var(--radius-md);
  border: 1px solid var(--colour-border);
  padding: var(--space-md);
  z-index: 50;
  max-block-size: 16rem;
  overflow-y: auto;
  display: none;
}

.dz-workspace[data-show-picker="1"] .dz-card-picker {
  display: block;
}

.dz-card-picker-title {
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--colour-text-muted);
  margin-block-end: var(--space-sm);
}

.dz-card-picker-entry {
  inline-size: 100%;
  text-align: start;
  padding-inline: var(--space-md);
  padding-block: var(--space-sm);
  border-radius: var(--radius-sm);
  background: transparent;
  border: 0;
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  font-size: var(--text-sm);
  cursor: pointer;
  transition: background var(--duration-fast) var(--ease-out);
}

.dz-card-picker-entry:hover {
  background: var(--colour-bg);
}

.dz-card-picker-display-tag {
  padding-inline: 0.375rem;
  padding-block: 0.125rem;
  border-radius: var(--radius-sm);
  background: var(--colour-bg);
  font-size: 0.6875rem;
  font-weight: var(--weight-medium);
  color: var(--colour-text-muted);
}

.dz-card-picker-title-text {
  color: var(--colour-text);
}

.dz-card-picker-entity {
  font-size: 0.6875rem;
  color: var(--colour-text-muted);
  margin-inline-start: auto;
}

.dz-card-picker-empty {
  font-size: var(--text-sm);
  color: var(--colour-text-muted);
  padding-block: var(--space-sm);
}

/* ─────────────────────────── pipeline_steps region (#890/#911) ──
 * Sequential-stage workflow visualisation. Stack vertically on
 * narrow viewports, row of cards on >=md (40rem) with chevron
 * connectors between cards. Used by workspace/regions/pipeline_steps.html. */

.dz-pipeline-stages {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  margin-block-start: var(--space-sm);
  list-style: none;
  padding: 0;
}

@media (min-width: 48rem) {
  .dz-pipeline-stages {
    flex-direction: row;
  }
}

.dz-pipeline-stage {
  position: relative;
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  padding: var(--space-md);
  border-radius: var(--radius-md);
  border: 1px solid var(--colour-border);
  background: var(--colour-surface);
}

.dz-pipeline-stage-label {
  font-size: 0.6875rem;
  font-weight: var(--weight-medium);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--colour-text-muted);
}

.dz-pipeline-stage-value {
  font-size: 1.5rem;
  font-weight: var(--weight-semibold);
  line-height: 1;
  color: var(--colour-text);
}

.dz-pipeline-stage-caption {
  font-size: var(--text-xs);
  line-height: 1.3;
  color: var(--colour-text-muted);
}

.dz-pipeline-stage-progress {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  margin-block-start: var(--space-xs);
}

.dz-pipeline-stage-progress-track {
  position: relative;
  flex: 1;
  block-size: 0.25rem;
  border-radius: 9999px;
  background: var(--colour-bg);
  overflow: hidden;
}

.dz-pipeline-stage-progress-fill {
  position: absolute;
  inset-block: 0;
  inset-inline-start: 0;
  background: var(--colour-brand);
  border-radius: 9999px;
  transition: width 0.2s var(--ease-out);
}

.dz-pipeline-stage-progress-label {
  font-size: 0.6875rem;
  font-weight: var(--weight-medium);
  color: var(--colour-text-muted);
  font-variant-numeric: tabular-nums;
}

.dz-pipeline-connector {
  position: absolute;
  display: none;
  align-items: center;
  justify-content: center;
  inset-inline-end: -0.875rem;
  inset-block-start: 50%;
  transform: translateY(-50%);
  inline-size: 1.75rem;
  block-size: 1.75rem;
  z-index: 5;
  color: var(--colour-text-muted);
}

@media (min-width: 48rem) {
  .dz-pipeline-connector {
    display: flex;
  }
}

.dz-pipeline-connector-mobile {
  display: flex;
  align-items: center;
  justify-content: center;
  margin-block-start: var(--space-xs);
  color: var(--colour-text-muted);
}

@media (min-width: 48rem) {
  .dz-pipeline-connector-mobile {
    display: none;
  }
}

/* ─────────────────────────── experience shell (UX-072) ──────────
 * Step-progress + inner-surface chrome for experience flows. Used
 * by experience/_content.html. */

.dz-experience {
  max-inline-size: 56rem;
  margin-inline: auto;
}

.dz-experience-title {
  font-size: var(--text-xl);
  font-weight: var(--weight-bold);
  color: var(--colour-text);
  margin-block-end: var(--space-xl);
}

/* Experience stepper reuses the existing .dz-steps* family from
 * the steps_indicator fragment block above (line ~448). The pattern
 * uses .is-completed on the .dz-steps-circle / .dz-steps-label /
 * .dz-steps-connector children rather than a modifier on the item. */

.dz-experience-form-shell {
  max-inline-size: 42rem;
}

.dz-experience-form-body {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
}

.dz-experience-stage-title {
  font-size: var(--text-md);
  font-weight: var(--weight-semibold);
  margin-block-end: var(--space-md);
}

.dz-experience-actions {
  display: flex;
  gap: var(--space-md);
  padding-block-start: var(--space-md);
}

.dz-experience-actions-centered {
  justify-content: center;
}

.dz-experience-ready {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  padding-inline: var(--space-md);
  padding-block: var(--space-sm);
  border-radius: var(--radius-sm);
  background: color-mix(in oklch, var(--colour-brand) 8%, transparent);
  border: 1px solid color-mix(in oklch, var(--colour-brand) 20%, transparent);
  font-size: var(--text-sm);
  color: var(--colour-text);
}

.dz-experience-placeholder {
  border-radius: var(--radius-md);
  background: var(--colour-bg);
  padding: var(--space-2xl);
  text-align: center;
}

.dz-experience-placeholder-title {
  font-size: var(--text-md);
  font-weight: var(--weight-medium);
  margin-block-end: var(--space-sm);
}

.dz-experience-placeholder-body {
  color: var(--colour-text-muted);
}
/* --- hatchi-maxchi/components/funnel.css --- */
/* HYPERPART: funnel */
/* Promoted VERBATIM from Dazzle regions.css (Tier C-a, fleet
 * convergence). The Dazzle emitter contract is unchanged. */

/* ─────────────────────────── funnel_chart region ────────────────
 * Stacked proportional bars rendering ordered stages. Each stage
 * gets a width derived from `count / first_count` and a tone tint
 * via data-dz-funnel-step (0..N) so the depth-by-position fade can
 * happen in CSS. Replaces the dynamic
 * `bg-[hsl(var(--primary)/{{ pct }})]` Tailwind arbitrary value
 * (which was JIT-invisible). */

.dz-funnel-stages {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  margin-block-start: var(--space-sm);
}

.dz-funnel-stage-row {
  display: flex;
  flex-direction: column;
  align-items: center;
}

.dz-funnel-stage {
  border-radius: var(--radius-sm);
  padding-inline: var(--space-md);
  padding-block: 0.375rem;
  text-align: center;
  /* The step fade lives on the BAR SURFACE via --dz-funnel-fade, never on
   * element opacity — the promoted rules faded whole stages to 20%,
   * taking the TEXT with them (unreadable + a WCAG contrast failure the
   * axe gate caught on first gallery exposure). Text keeps full-contrast
   * scheme-aware ink on the mixed surface. */
  background: color-mix(
    in oklab,
    var(--colour-brand) calc(var(--dz-funnel-fade, 100) * 1%),
    var(--colour-surface)
  );
  color: var(--colour-text);
  min-width: 7.5rem;
}

/* Position-driven surface fade (steps 0..7). Beyond step 7 the surface
 * stays at the step-7 mix. */
.dz-funnel-stage[data-dz-funnel-step="0"] { --dz-funnel-fade: 60; }
.dz-funnel-stage[data-dz-funnel-step="1"] { --dz-funnel-fade: 50; }
.dz-funnel-stage[data-dz-funnel-step="2"] { --dz-funnel-fade: 41; }
.dz-funnel-stage[data-dz-funnel-step="3"] { --dz-funnel-fade: 33; }
.dz-funnel-stage[data-dz-funnel-step="4"] { --dz-funnel-fade: 26; }
.dz-funnel-stage[data-dz-funnel-step="5"] { --dz-funnel-fade: 20; }
.dz-funnel-stage[data-dz-funnel-step="6"] { --dz-funnel-fade: 15; }
.dz-funnel-stage[data-dz-funnel-step="7"] { --dz-funnel-fade: 11; }

.dz-funnel-stage-label {
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
}

.dz-funnel-stage-count {
  font-size: var(--text-xs);
  /* inherits the stage's full-contrast ink — muted text fails WCAG on
   * the brand-tinted surfaces; size alone differentiates it. */
  margin-inline-start: 0.25rem;
}

.dz-funnel-summary {
  font-size: var(--text-xs);
  color: var(--colour-text-muted);
  margin-block-start: var(--space-sm);
}
/* --- hatchi-maxchi/components/grid-list.css --- */
/* HYPERPART: grid-list */
/* Promoted VERBATIM from Dazzle regions.css (W-leftovers, fleet
 * convergence). The Dazzle emitter contract is unchanged. */

/* ─────────────────────────── grid region ────────────────────────
 * Responsive cell grid (1 → 2 → 3 columns at sm/lg breakpoints).
 * Cells are intentionally plain — the dashboard slot owns the card
 * chrome. Each cell gets an attention-accent border via the
 * existing attention_classes macro (untouched), and .is-clickable
 * when wired to action_url. */

.dz-grid-list {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-md);
}

@media (min-width: 40rem) {
  .dz-grid-list {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (min-width: 64rem) {
  .dz-grid-list {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

.dz-grid-cell {
  border-radius: var(--radius-sm);
  padding: var(--space-md);
  transition: background var(--duration-fast) var(--ease-out);
}

.dz-grid-cell.is-clickable {
  cursor: pointer;
}

.dz-grid-cell.is-clickable:hover {
  background: var(--colour-bg);
}

.dz-grid-cell-title {
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  color: var(--colour-text);
  margin-block-end: 0.375rem;
}

.dz-grid-cell-field {
  font-size: var(--text-xs);
  color: var(--colour-text-muted);
  margin: 0;
}

.dz-grid-cell-field-label {
  font-size: var(--text-xs);
}
/* --- hatchi-maxchi/components/heatmap.css --- */
/* HYPERPART: heatmap */
/* Promoted from Dazzle regions.css (Tier C-b, fleet convergence).
 * The Dazzle emitter contract is unchanged. One deliberate delta from
 * the source: the three tone-cell backgrounds moved from
 * `color-mix(... 15%, transparent)` recipes to the engineered
 * `--colour-*-soft` tokens (the HM contrast recipe — same change the
 * status-list/alert promotions made). */

/* ─────────────────────────── heatmap region ─────────────────────
 * Matrix table with row labels and column labels, value cells
 * tinted by threshold band (success / warning / destructive).
 * Tints route through data-dz-heatmap-tone so the threshold
 * branching can stay in CSS (template emits the data-attribute,
 * not a class string). */

.dz-heatmap-scroll {
  overflow-x: auto;
}

.dz-heatmap-grid {
  width: 100%;
  font-size: var(--text-sm);
  border-collapse: collapse;
}

.dz-heatmap-grid > thead > tr {
  border-block-end: 1px solid var(--colour-border);
}

.dz-heatmap-grid th {
  padding-inline: var(--space-sm);
  padding-block: var(--space-xs);
  text-align: center;
  font-weight: var(--weight-medium);
  color: var(--colour-text-muted);
}

.dz-heatmap-grid > tbody > tr {
  border-block-end: 1px solid var(--colour-border);
}

.dz-heatmap-grid > tbody > tr:last-child {
  border-block-end: 0;
}

.dz-heatmap-grid > tbody > tr.is-clickable {
  cursor: pointer;
  transition: background var(--duration-fast) var(--ease-out);
}

.dz-heatmap-grid > tbody > tr.is-clickable:hover {
  background: var(--colour-bg);
}

.dz-heatmap-row-label {
  padding-inline: var(--space-sm);
  padding-block: var(--space-xs);
  font-weight: var(--weight-medium);
  color: var(--colour-text);
}

.dz-heatmap-cell {
  padding-inline: var(--space-sm);
  padding-block: var(--space-xs);
  text-align: center;
  transition: opacity var(--duration-fast) var(--ease-out);
}

.dz-heatmap-cell[data-dz-heatmap-tone="bad"] {
  background: var(--colour-danger-soft);
  color: var(--colour-danger);
}

.dz-heatmap-cell[data-dz-heatmap-tone="warn"] {
  background: var(--colour-warning-soft);
  color: var(--colour-warning);
}

.dz-heatmap-cell[data-dz-heatmap-tone="good"] {
  background: var(--colour-success-soft);
  color: var(--colour-success);
}

.dz-heatmap-overflow {
  font-size: var(--text-xs);
  color: var(--colour-text-muted);
  margin-block-start: var(--space-sm);
}
/* --- hatchi-maxchi/components/histogram.css --- */
/* HYPERPART: histogram */
/* Promoted VERBATIM from Dazzle regions.css (Tier C-b, fleet
 * convergence). The Dazzle emitter contract is unchanged. */

/* ─────────────────────────── histogram region ───────────────────
 * Server-rendered SVG histogram. Bar fill, axis line, and reference
 * lines all use design tokens via their own SVG attributes; this
 * file owns only the host shape + summary footer. */

.dz-histogram-svg {
  width: 100%;
  height: auto;
}

.dz-histogram-summary {
  font-size: var(--text-xs);
  color: var(--colour-text-muted);
  margin-block-start: var(--space-sm);
  font-family: var(--font-mono);
}
/* --- hatchi-maxchi/components/hm-core.css --- */
/*
 * hm-core — HaTchi-MaXchi tranche 2A components (spec:
 * hatchi-maxchi-standalone). Accumulates new standalone-system
 * components until the extraction restructures per-component.
 *
 * Components: dz-progress, dz-avatar, dz-breadcrumb, dz-toggle-group,
 * dz-popover, dz-kbd.
 */

/* ── dz-progress ─────────────────────────────────────────────────────
 * <div class="dz-progress" role="progressbar" aria-valuenow="60"
 *      aria-valuemin="0" aria-valuemax="100">
 *   <div class="dz-progress__bar" style="width:60%"></div>
 * </div>
 */

.dz-progress {
  width: 100%;
  height: 0.5rem;
  border-radius: var(--radius-full);
  background: color-mix(in oklab, var(--colour-brand) 12%, var(--colour-bg));
  overflow: hidden;
}

.dz-progress__bar {
  /* Dynamic value via a custom property, not an inline width: the markup
     sets `--dz-progress-value: 62%` and CSS owns the property mapping. */
  width: var(--dz-progress-value, 0%);
  height: 100%;
  border-radius: var(--radius-full);
  background: var(--colour-brand);
  transition: width var(--duration-slow) var(--ease-out);
}

.dz-progress[data-dz-tone="success"] .dz-progress__bar { background: var(--colour-success); }
.dz-progress[data-dz-tone="warning"] .dz-progress__bar { background: var(--colour-warning); }
.dz-progress[data-dz-tone="destructive"] .dz-progress__bar { background: var(--colour-danger); }

@media (prefers-reduced-motion: reduce) {
  .dz-progress__bar { transition: none; }
}

/* ── dz-avatar ───────────────────────────────────────────────────────
 * <span class="dz-avatar" data-dz-size="md">AB</span>
 * <span class="dz-avatar"><img src="…" alt=""></span>
 * Initials inherit a deterministic tone via data-dz-hue (0-359) set by
 * the emitter (hash of the name), falling back to brand.
 */

.dz-avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  border-radius: var(--radius-full);
  /* The semantic wash, not a hand-rolled mix — the 15% mix put initials
     at 4.29:1 (axe); the wash token is calibrated for 4.5:1 tone text. */
  background: var(--colour-brand-soft);
  color: var(--colour-brand-text);
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  letter-spacing: 0.02em;
  overflow: hidden;
  flex-shrink: 0;
  user-select: none;
}

.dz-avatar[data-dz-size="sm"] { width: 1.5rem; height: 1.5rem; font-size: 0.625rem; }
.dz-avatar[data-dz-size="lg"] { width: 2.75rem; height: 2.75rem; font-size: var(--text-sm); }

.dz-avatar img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.dz-avatar-group {
  display: inline-flex;
}

.dz-avatar-group .dz-avatar {
  border: 2px solid var(--colour-surface);
}

.dz-avatar-group .dz-avatar + .dz-avatar {
  margin-inline-start: -0.5rem;
}

/* ── dz-breadcrumb ───────────────────────────────────────────────────
 * <nav class="dz-breadcrumb" aria-label="Breadcrumb"><ol>
 *   <li><a href="/app">Home</a></li>
 *   <li><a href="/app/invoices">Invoices</a></li>
 *   <li aria-current="page">INV-0042</li>
 * </ol></nav>
 * Separator is CSS-generated (chevron) — markup stays list-clean.
 */

.dz-breadcrumb ol {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  list-style: none;
  margin: 0;
  padding: 0;
  font-size: var(--text-sm);
}

.dz-breadcrumb li {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  color: var(--colour-text-muted);
}

.dz-breadcrumb li + li::before {
  content: "";
  width: 0.875rem;
  height: 0.875rem;
  background: currentColor;
  opacity: 0.5;
  mask: center / contain no-repeat
    url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m9 18 6-6-6-6"/></svg>');
}

.dz-breadcrumb a {
  color: var(--colour-text-muted);
  text-decoration: none;
}

.dz-breadcrumb a:hover {
  color: var(--colour-text);
}

.dz-breadcrumb [aria-current="page"] {
  color: var(--colour-text);
  font-weight: var(--weight-medium);
}

/* ── dz-toggle-group ─────────────────────────────────────────────────
 * The hypermedia answer: native radios, styled as a segmented control.
 * <fieldset class="dz-toggle-group" role="radiogroup">
 *   <label><input type="radio" name="view" checked><span>List</span></label>
 *   <label><input type="radio" name="view"><span>Board</span></label>
 * </fieldset>
 */

.dz-toggle-group {
  display: inline-flex;
  padding: 2px;
  gap: 2px;
  border: 1px solid var(--colour-border);
  border-radius: var(--radius-md);
  background: color-mix(in oklab, var(--colour-text) 4%, var(--colour-bg));
  margin: 0;
}

.dz-toggle-group label {
  position: relative;
  cursor: pointer;
}

.dz-toggle-group input {
  position: absolute;
  inset: 0;
  opacity: 0;
  cursor: pointer;
}

.dz-toggle-group span {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
  padding: 0.25rem var(--space-md);
  border-radius: calc(var(--radius-md) - 2px);
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  color: var(--colour-text-muted);
  transition: background var(--duration-fast) var(--ease-out),
              color var(--duration-fast) var(--ease-out);
}

.dz-toggle-group input:checked + span {
  background: var(--colour-surface);
  color: var(--colour-text);
  box-shadow: var(--shadow-sm);
}

.dz-toggle-group input:focus-visible + span {
  outline: var(--focus-ring-width) solid var(--focus-ring-color);
  outline-offset: var(--focus-ring-offset);
}

/* ── dz-popover — details-based generic panel ────────────────────────
 * <details class="dz-popover"><summary>…trigger…</summary>
 *   <div class="dz-popover__panel">…free content…</div></details>
 * data-dz-align="start|end" on the root positions the panel.
 */

.dz-popover {
  position: relative;
  display: inline-block;
}

.dz-popover > summary {
  list-style: none;
  cursor: pointer;
}

.dz-popover > summary::-webkit-details-marker { display: none; }

.dz-popover__panel {
  position: absolute;
  inset-block-start: calc(100% + 0.375rem);
  inset-inline-start: 0;
  min-width: 14rem;
  padding: var(--space-lg);
  background: var(--colour-surface);
  border: 1px solid var(--colour-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
  z-index: 40;
}

.dz-popover[data-dz-align="end"] .dz-popover__panel {
  inset-inline-start: auto;
  inset-inline-end: 0;
}

/* ── dz-kbd ──────────────────────────────────────────────────────────
 * <kbd class="dz-kbd">⌘K</kbd> — shortcut chips (command palette etc.)
 */

.dz-kbd {
  display: inline-flex;
  align-items: center;
  padding: 0.125rem 0.375rem;
  border: 1px solid var(--colour-border);
  border-block-end-width: 2px;
  border-radius: var(--radius-sm);
  background: var(--colour-surface);
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  color: var(--colour-text-muted);
}

/* ── dz-command — the hx-get command palette (htmx4 flagship) ─────────
 * <dialog class="dz-command">
 *   <input class="dz-command__input" placeholder="Search…" name="q"
 *          hx-get="/app/command" hx-trigger="input changed delay:150ms"
 *          hx-target="next .dz-command__results">
 *   <div class="dz-command__results">…server-rendered items…</div>
 * </dialog>
 * Server renders .dz-command__item entries (icon + label + dz-kbd hint).
 * dz-command.js: ⌘K/Ctrl-K opens, Esc closes, arrows move [aria-selected].
 */

/* HYPERPART: command */
dialog.dz-command {
  width: min(34rem, calc(100vw - 2rem));
  padding: 0;
  border: 1px solid var(--colour-border);
  border-radius: var(--radius-lg);
  background: var(--colour-surface);
  box-shadow: var(--shadow-lg);
  /* top-anchored, horizontally centered (native dialog centers by
     default; override only the block axis) */
  margin-block: 12vh auto;
  margin-inline: auto;
}

dialog.dz-command::backdrop {
  background: oklch(13% 0.028 261.69 / 0.4);
  backdrop-filter: blur(2px);
}

/* input + close share a flex row (the bar); results follow. Flex, not
   absolute positioning — abspos against a modal <dialog> is fragile on
   Safari/iPadOS (the button landed at its static position there, so taps
   on the expected top-right corner missed it). */
.dz-command__bar {
  display: flex;
  align-items: center;
  border-block-end: 1px solid var(--colour-border);
}

.dz-command__input {
  flex: 1;
  min-width: 0;
  padding: var(--space-lg);
  border: none;
  background: transparent;
  color: var(--colour-text);
  font-family: var(--font-sans);
  font-size: var(--text-base);
  outline: none;
  box-sizing: border-box;
}

/* Always-visible dismiss affordance — essential on touch (no Esc key).
   A native <button> so its tap fires reliably, incl. on iOS Safari. */
.dz-command__close {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.25rem;
  height: 2.25rem;
  margin-inline-end: var(--space-sm);
  padding: 0;
  border: none;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--colour-text-muted);
  cursor: pointer;
}
.dz-command__close:hover {
  background: var(--colour-surface-muted);
  color: var(--colour-text);
}
.dz-command__close:focus-visible {
  outline: var(--focus-ring-width) solid var(--focus-ring-color);
  outline-offset: var(--focus-ring-offset);
}
.dz-command__close svg {
  width: 1rem;
  height: 1rem;
}

.dz-command__results {
  max-height: 20rem;
  overflow-y: auto;
  padding: var(--space-xs);
}

.dz-command__group {
  padding: var(--space-sm) var(--space-md) var(--space-xs);
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--colour-text-muted);
}

.dz-command__item {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  width: 100%;
  padding: var(--space-sm) var(--space-md);
  border: none;
  border-radius: var(--radius-sm);
  background: none;
  color: var(--colour-text);
  font-size: var(--text-sm);
  text-align: start;
  text-decoration: none;
  cursor: pointer;
}

.dz-command__item:hover,
.dz-command__item[aria-selected="true"] {
  background: color-mix(in oklab, var(--colour-brand) 8%, var(--colour-surface));
}

.dz-command__item .dz-kbd { margin-inline-start: auto; }

.dz-command__empty {
  padding: var(--space-xl);
  text-align: center;
  color: var(--colour-text-muted);
  font-size: var(--text-sm);
}

/* ── dz-hover-card ────────────────────────────────────────────────────
 * <span class="dz-hover-card">trigger<span class="dz-hover-card__panel">
 * …rich preview…</span></span> — pure CSS reveal, htmx can lazy-load the
 * panel body with hx-trigger="intersect once".
 */

.dz-hover-card {
  position: relative;
  display: inline-block;
}

.dz-hover-card__panel {
  position: absolute;
  inset-block-start: calc(100% + 0.375rem);
  inset-inline-start: 0;
  width: 18rem;
  padding: var(--space-lg);
  background: var(--colour-surface);
  border: 1px solid var(--colour-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--duration-fast) var(--ease-out);
  z-index: 45;
}

.dz-hover-card:hover .dz-hover-card__panel,
.dz-hover-card:focus-within .dz-hover-card__panel {
  opacity: 1;
  visibility: visible;
  transition-delay: 400ms;
}

@media (prefers-reduced-motion: reduce) {
  .dz-hover-card__panel { transition: none; }
}

/* ── dz-scroll-area ──────────────────────────────────────────────────
 * Thin, token-coloured scrollbars for constrained panels.
 */

.dz-scroll-area {
  overflow: auto;
  scrollbar-width: thin;
  scrollbar-color: color-mix(in oklab, var(--colour-text) 25%, transparent) transparent;
}

.dz-scroll-area::-webkit-scrollbar { width: 8px; height: 8px; }
.dz-scroll-area::-webkit-scrollbar-thumb {
  background: color-mix(in oklab, var(--colour-text) 25%, transparent);
  border-radius: var(--radius-full);
}
.dz-scroll-area::-webkit-scrollbar-track { background: transparent; }

/* HYPERPART: toolbar — inline composite (button + toggle-group + menu). */
.dz-toolbar {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  flex-wrap: wrap;
  padding: var(--space-sm);
  border: 1px solid var(--colour-border);
  border-radius: var(--radius-md);
  background: var(--colour-surface);
}

/* HYPERPART: master-detail — exchange composite (list → hx-get detail card). */
.dz-master-detail {
  display: grid;
  grid-template-columns: 14rem 1fr;
  gap: var(--space-md);
  border: 1px solid var(--colour-border);
  border-radius: var(--radius-md);
  overflow: hidden;
  min-height: 12rem;
}
.dz-master-detail__list {
  list-style: none;
  margin: 0;
  padding: var(--space-xs);
  border-inline-end: 1px solid var(--colour-border);
  background: var(--colour-surface);
}
.dz-master-detail__item {
  display: block;
  padding: var(--space-sm) var(--space-md);
  border-radius: var(--radius-sm);
  color: var(--colour-text);
  text-decoration: none;
  font-size: var(--text-sm);
}
.dz-master-detail__item:hover {
  background: var(--colour-bg);
}
.dz-master-detail__item[aria-current="true"] {
  background: var(--colour-brand-soft);
  color: var(--colour-brand-text);
  font-weight: var(--weight-medium);
}
.dz-master-detail__detail {
  padding: var(--space-md);
}
/* --- hatchi-maxchi/components/htmx-states.css --- */
/*
 * htmx-states.css — HTMX runtime-class styling.
 *
 * HTMX applies these classes to elements during request lifecycles:
 *
 *   .htmx-request          — element has an in-flight request
 *   .htmx-indicator        — visibility-toggled spinner / loader
 *   .htmx-swapping         — element is about to be swapped out (300ms)
 *   .htmx-settling         — element was just swapped in (settle phase)
 *   .htmx-added            — newly-inserted element (highlight transition)
 *
 * Migrated from dz.css during the v0.62 CSS refactor. All values
 * route through design tokens. Wrapped in @layer components.
 */

/* ── Row swap animations ─────────────────────────────────────────── */
/* Row fades out during delete swap (swap:300ms on hx-swap). */

tr.htmx-swapping {
  opacity: 0;
  transform: translateX(var(--space-lg));
  transition:
    opacity var(--duration-slow) var(--ease-out),
    transform var(--duration-slow) var(--ease-out);
}

/* Row highlights briefly after creation or update. */

tr.htmx-added,
tr.htmx-settling {
  animation: dz-row-highlight 600ms var(--ease-out);
}

@keyframes dz-row-highlight {
  from {
    background-color: var(--colour-success-soft);
  }
  to {
    background-color: transparent;
  }
}

/* ── Submit button feedback ─────────────────────────────────────── */
/* Form button shows an in-flight state on submit. */

button.htmx-request,
.dz-button.htmx-request {
  pointer-events: none;
  opacity: 0.7;
}

/* ── Inline loading-spinner two-tone ────────────────────────────── */
/* The inline SVG spinners the Dazzle emitters render (search-select,
 * table-loading, tab-loading, workspace-save-busy) are the classic
 * two-tone loader: a dim background track ring + a brighter rotating
 * head arc. These carry the canonical opacities HM owns, replacing the
 * Tailwind `opacity-25` / `opacity-75` utilities the emitters used
 * before Tailwind was unbundled (markup-reservoir drain, HMC-002). */

.dz-spinner-track {
  opacity: 0.25;
}

.dz-spinner-head {
  opacity: 0.75;
}

/* ── Indicator visibility ───────────────────────────────────────── */
/* Override HTMX's built-in opacity-based hiding with display:none —
 * hidden indicators must not occupy layout space (opacity:0 still
 * reserves vertical space for the element). */

.htmx-indicator {
  display: none;
}

/* Descendant case: indicator is a child of the requesting element. */
.htmx-request .htmx-indicator {
  display: inline-block;
}

/* Same-element case: hx-indicator="#id" adds htmx-request to the
 * indicator itself rather than its parent. */
.htmx-request.htmx-indicator {
  display: inline-block;
}

/* ── Reduced-motion accommodation ───────────────────────────────── */
/* Skip the row-highlight + slide-out animations when the user has
 * set prefers-reduced-motion. The reset already does the global
 * disable; this override is here so the comment explains *why* —
 * row highlights are pure decoration for status feedback. */

@media (prefers-reduced-motion: reduce) {
  tr.htmx-swapping {
    transform: none;
  }

  tr.htmx-added,
  tr.htmx-settling {
    animation: none;
  }
}
/* --- hatchi-maxchi/components/kanban.css --- */
/* HYPERPART: kanban */
/* Promoted VERBATIM from Dazzle regions.css (W2, fleet convergence).
 * The Dazzle emitter contract predates the move and is unchanged. */

/* ─────────────────────────── kanban region ──────────────────────
 * Horizontal column board. Each column is a fixed-width tinted
 * surface with a header (status badge + count) and a vertically-
 * scrolling card stack. Cards are tinted surfaces; .is-clickable
 * gives them shadow/border affordance when wired to action_url. */

.dz-kanban-board {
  display: flex;
  gap: var(--space-md);
  overflow-x: auto;
  padding-block-end: var(--space-sm);
  min-height: 12.5rem;
}

.dz-kanban-column {
  flex-shrink: 0;
  width: 16.25rem;
  min-width: 16.25rem;
  background: color-mix(in oklch, var(--colour-bg) 60%, transparent);
  border-radius: var(--radius-md);
  padding: var(--space-sm);
}

.dz-kanban-column-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-block-end: var(--space-sm);
  padding-inline: 0.25rem;
}

.dz-kanban-column-count {
  font-size: var(--text-xs);
  color: var(--colour-text-muted);
}

.dz-kanban-stack {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  max-height: 60vh;
  overflow-y: auto;
}

.dz-kanban-card {
  background: var(--colour-surface);
  border: 1px solid var(--colour-border);
  border-radius: var(--radius-sm);
  box-shadow: 0 1px 3px oklch(0% 0 0 / 0.04);
}

.dz-kanban-card.is-clickable {
  cursor: pointer;
  transition: box-shadow var(--duration-fast) var(--ease-out),
              border-color var(--duration-fast) var(--ease-out);
}

.dz-kanban-card.is-clickable:hover {
  box-shadow: 0 2px 8px oklch(0% 0 0 / 0.06);
  border-color: color-mix(in oklch, var(--colour-brand) 50%, var(--colour-border));
}

.dz-kanban-card-body {
  padding: var(--space-sm);
}

.dz-kanban-card-title {
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  color: var(--colour-text);
  margin-block-end: 0.25rem;
}

.dz-kanban-card-field {
  font-size: var(--text-xs);
  color: var(--colour-text-muted);
}

.dz-kanban-card-attn {
  font-size: var(--text-xs);
  margin-block-start: 0.25rem;
}

.dz-kanban-card-attn[data-dz-attn="critical"] { color: var(--colour-danger); }
.dz-kanban-card-attn[data-dz-attn="warning"]  { color: var(--colour-warning); }
.dz-kanban-card-attn[data-dz-attn="notice"]   { color: var(--colour-brand); }

.dz-kanban-empty {
  font-size: var(--text-xs);
  /* full muted token — the 60%-transparent mix failed WCAG contrast
   * (axe gate on first gallery exposure; shipped that way in Dazzle). */
  color: var(--colour-text-muted);
  text-align: center;
  padding-block: var(--space-md);
}

.dz-kanban-overflow {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  margin-block-start: var(--space-sm);
}

.dz-kanban-overflow-text {
  font-size: var(--text-xs);
  color: var(--colour-text-muted);
}

.dz-kanban-load-all {
  height: 1.5rem;
  padding-inline: var(--space-sm);
  border-radius: var(--radius-sm);
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  color: var(--colour-text-muted);
  background: transparent;
  border: 0;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  transition: background var(--duration-fast) var(--ease-out),
              color var(--duration-fast) var(--ease-out);
}

.dz-kanban-load-all:hover {
  background: var(--colour-bg);
  color: var(--colour-text);
}
/* --- hatchi-maxchi/components/layout.css --- */
/* Layout — the Every-Layout vocabulary as CSS-only Hyperparts (L1).
 *
 * Intrinsic, media-query-free responsiveness: the sidebar wraps when its
 * CONTENT would get too narrow, the auto-grid packs columns to fit — the
 * same markup behaves in a page, a card, or a drawer. Variants ride
 * data-dz-* attributes; the consumer-set widths are PUBLIC_CSS_PROPS
 * knobs (they follow the namespace prefix — see build.py).
 *
 * The gap scale is shared by stack / cluster / auto-grid via one
 * attribute contract (data-dz-gap: xs|sm|md|lg|xl; unset = md). */

/* HYPERPART: stack */
/* L2: with the legacy --gap-* modifier scale retired (the emitters now
 * speak data-dz-gap), the base rule lives here with the rest of the
 * Hyperpart — the sublayer-precedence trap it once had to dodge has no
 * competing rules left. */
.dz-stack {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
}

/* HYPERPART: cluster */
.dz-cluster {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-md);
}

.dz-cluster[data-dz-align="start"] {
  align-items: flex-start;
}

.dz-cluster[data-dz-align="end"] {
  align-items: flex-end;
}

.dz-cluster[data-dz-align="baseline"] {
  align-items: baseline;
}

.dz-cluster[data-dz-align="stretch"] {
  align-items: stretch;
}

.dz-cluster[data-dz-justify="end"] {
  justify-content: flex-end;
}

.dz-cluster[data-dz-justify="between"] {
  justify-content: space-between;
}

.dz-cluster[data-dz-justify="center"] {
  justify-content: center;
}

/* The shared gap scale — one contract for the three gapped layouts. */
.dz-stack[data-dz-gap="none"],
.dz-cluster[data-dz-gap="none"],
.dz-auto-grid[data-dz-gap="none"] {
  gap: 0;
}

.dz-stack[data-dz-gap="xs"],
.dz-cluster[data-dz-gap="xs"],
.dz-auto-grid[data-dz-gap="xs"] {
  gap: var(--space-xs);
}

.dz-stack[data-dz-gap="sm"],
.dz-cluster[data-dz-gap="sm"],
.dz-auto-grid[data-dz-gap="sm"] {
  gap: var(--space-sm);
}

.dz-stack[data-dz-gap="md"],
.dz-cluster[data-dz-gap="md"],
.dz-auto-grid[data-dz-gap="md"] {
  gap: var(--space-md);
}

.dz-stack[data-dz-gap="lg"],
.dz-cluster[data-dz-gap="lg"],
.dz-auto-grid[data-dz-gap="lg"] {
  gap: var(--space-lg);
}

.dz-stack[data-dz-gap="xl"],
.dz-cluster[data-dz-gap="xl"],
.dz-auto-grid[data-dz-gap="xl"] {
  gap: var(--space-xl);
}

/* HYPERPART: sidebar-layout */
/* The Every-Layout sidebar: the side takes its preferred width, the
 * content grows aggressively but never below its comfortable minimum —
 * when the line can't hold that, the content wraps to a full row. The
 * breakpoint is the CONTENT'S minimum, not a viewport width. */
.dz-sidebar-layout {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-lg);
}

.dz-sidebar-layout > :first-child {
  flex-basis: var(--dz-sidebar-width, 16rem);
  flex-grow: 1;
}

.dz-sidebar-layout > :last-child {
  flex-basis: 0;
  flex-grow: 999;
  min-inline-size: var(--dz-sidebar-content-min, 50%);
}

/* data-dz-side="end": the side pane comes AFTER the content in source
 * order too (accessibility: keep DOM order = reading order), so the
 * grow/basis roles simply swap ends. */
.dz-sidebar-layout[data-dz-side="end"] > :first-child {
  flex-basis: 0;
  flex-grow: 999;
  min-inline-size: var(--dz-sidebar-content-min, 50%);
}

.dz-sidebar-layout[data-dz-side="end"] > :last-child {
  flex-basis: var(--dz-sidebar-width, 16rem);
  flex-grow: 1;
  min-inline-size: 0;
}

/* HYPERPART: auto-grid */
/* The inner min() stops overflow when the container is narrower than the
 * column minimum — the classic auto-fit footgun. */
.dz-auto-grid {
  display: grid;
  grid-template-columns: repeat(
    auto-fit,
    minmax(min(var(--dz-grid-min, 14rem), 100%), 1fr)
  );
  gap: var(--space-md);
}

/* HYPERPART: center */
.dz-center {
  box-sizing: content-box;
  margin-inline: auto;
  max-inline-size: 65ch;
}

.dz-center[data-dz-measure="wide"] {
  max-inline-size: 90ch;
}

.dz-center[data-dz-measure="full"] {
  max-inline-size: none;
}
/* --- hatchi-maxchi/components/list-region.css --- */
/* HYPERPART: list-region */
/* Promoted from Dazzle regions.css (W-leftovers, fleet convergence).
 * The Dazzle emitter contract is unchanged. Two DEAD rule families
 * from the source were deleted rather than promoted (proven
 * un-emitted across py/html/js/examples): the filter bar
 * (.dz-list-filters/.dz-list-filter-select — the substrate list
 * region never grew filter chrome) and .dz-list-action-button (the
 * actions row only ever renders the CSV export button). */

/* ─────────────────────────── list region ────────────────────────
 * Data table inside a region card. Has its own actions row (region
 * actions + CSV export) and table. Row attention tint via
 * .is-clickable + attention_classes macro (unchanged). */

.dz-list-actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  margin-block-end: var(--space-md);
}

.dz-list-action-group {
  display: flex;
  align-items: center;
  gap: 0.25rem;
}

.dz-list-csv-button {
  height: 1.75rem;
  width: 1.75rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-sm);
  color: var(--colour-text-muted);
  background: transparent;
  border: 0;
  cursor: pointer;
  transition: background var(--duration-fast) var(--ease-out),
              color var(--duration-fast) var(--ease-out);
}

.dz-list-csv-button:hover {
  background: var(--colour-bg);
  color: var(--colour-text);
}

.dz-list-csv-button > svg {
  width: 1rem;
  height: 1rem;
}

.dz-list-scroll {
  overflow-x: auto;
}

.dz-list-table {
  width: 100%;
  border-collapse: collapse;
}

.dz-list-table > thead > tr {
  background: color-mix(in oklch, var(--colour-bg) 70%, transparent);
  border-block-end: 1px solid var(--colour-border);
}

.dz-list-table th {
  text-align: start;
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  color: var(--colour-text-muted);
  padding-inline: var(--space-md);
  padding-block: var(--space-sm);
  letter-spacing: -0.01em;
}

.dz-list-table > tbody > tr {
  border-block-end: 1px solid var(--colour-border);
  transition: background var(--duration-fast) var(--ease-out);
}

.dz-list-table > tbody > tr:last-child {
  border-block-end: 0;
}

.dz-list-row.is-clickable {
  cursor: pointer;
}

.dz-list-row.is-clickable:hover {
  background: var(--colour-bg);
}

.dz-list-table td {
  font-size: var(--text-sm);
  color: var(--colour-text);
  padding-inline: var(--space-md);
  padding-block: var(--space-sm);
}

.dz-list-sort-link {
  cursor: pointer;
  user-select: none;
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
  text-decoration: none;
  color: inherit;
  transition: color var(--duration-fast) var(--ease-out);
}

.dz-list-sort-link:hover {
  color: var(--colour-text);
}

.dz-list-overflow {
  font-size: var(--text-xs);
  color: var(--colour-text-muted);
  margin-block-start: var(--space-sm);
}
/* --- hatchi-maxchi/components/metrics.css --- */
/* HYPERPART: metrics */
/* Promoted VERBATIM from Dazzle regions.css (Tier C-a, fleet
 * convergence). The Dazzle emitter contract is unchanged. */

/* ─────────────────────────── metrics region ─────────────────────
 * Responsive 1/2/4-col grid of KPI tiles. Each tile is plain (no
 * border — the dashboard slot owns card chrome) with a soft
 * background tint, label / value, and optional delta row.
 * Per-tile tone tinting travels with the component (moved verbatim
 * from Dazzle dz-tones.css, keyed off data-dz-tone). */

.dz-metrics-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-md);
}

@media (min-width: 40rem) {
  .dz-metrics-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (min-width: 64rem) {
  .dz-metrics-grid {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}

.dz-metric-tile {
  padding: var(--space-md);
  border-radius: var(--radius-sm);
  background: color-mix(in oklch, var(--colour-bg) 60%, transparent);
}

/* Tone tints (moved from Dazzle dz-tones.css with the component, #894).
 * Keyed off data-dz-tone; the [attr] selector out-specifies the base
 * rule above. neutral falls back to the base background. */
.dz-metric-tile[data-dz-tone="positive"] {
  background: color-mix(in oklab, var(--colour-success) 10%, transparent);
}
.dz-metric-tile[data-dz-tone="warning"] {
  background: color-mix(in oklab, var(--colour-warning) 10%, transparent);
}
.dz-metric-tile[data-dz-tone="destructive"] {
  background: color-mix(in oklab, var(--colour-danger) 10%, transparent);
}
.dz-metric-tile[data-dz-tone="accent"] {
  background: color-mix(in oklab, var(--colour-brand) 10%, transparent);
}

.dz-metric-label {
  font-size: var(--text-xs);
  color: var(--colour-text-muted);
  letter-spacing: -0.01em;
}

.dz-metric-value {
  font-size: var(--text-lg);
  font-weight: var(--weight-semibold);
  color: var(--colour-text);
  margin-block-start: 0.125rem;
  font-variant-numeric: tabular-nums;
}

.dz-metric-delta {
  margin-block-start: var(--space-xs);
  font-size: var(--text-xs);
  font-variant-numeric: tabular-nums;
}
/* --- hatchi-maxchi/components/mobile-scroll.css --- */
/*
 * mobile-scroll.css — touch-friendly scroll containment (HaTchi-MaXchi).
 *
 * Two properties make scrollable regions feel native on touch:
 * `-webkit-overflow-scrolling: touch` (iOS momentum) and
 * `overscroll-behavior: contain` (no scroll-chain / browser-back swipe).
 * Applied to the scroll viewports (card/detail/drawer/app/form bodies,
 * pdf-viewer panel) + the `.dz-scroll`/`.dz-scroll-x`/`.dz-table-wrap`
 * utilities. Migrated from Dazzle into HM (HMC-022, aggressive-mode
 * convergence 2026-07-09). NOTE: no `@layer components {` wrapper — HM build adds it.
 */
  .dz-card-body,
  .dz-drawer-content,
  .dz-detail-card-body,
  .dz-app-content,
  .dz-app-body,
  .dz-drawer__body,
  .dz-pdf-viewer-panel,
  .dz-scroll {
    -webkit-overflow-scrolling: touch;
    overscroll-behavior: contain;
  }

  /* Horizontal-scroll containers (tables, charts, region grids).
   * `overscroll-behavior-x: contain` prevents the browser-back
   * "swipe-from-edge" from firing when the user pans inside a
   * wide table — common iOS frustration where horizontal scroll
   * navigates history instead. */
  .dz-table-wrap,
  .dz-scroll-x {
    -webkit-overflow-scrolling: touch;
    overscroll-behavior-x: contain;
  }
/* --- hatchi-maxchi/components/onboarding.css --- */
/*
 * onboarding.css — guided-onboarding overlay primitives (HaTchi-MaXchi).
 *
 * Styles for the 8 step kinds (popover, spotlight, inline_card, empty_state,
 * banner, checklist_item, blocking_task, nudge) emitted by
 * `dazzle.render.onboarding.renderer`. Every overlay shares the outer
 * `<dz-onboarding-step>` custom element; per-kind appearance is a
 * `.dz-onboarding-<kind>` block + BEM child classes. The bulk routes through HM
 * tokens; the spotlight-ring / scrim geometry (6rem ring, 999px cutout shadow,
 * scrim opacity) is intentionally bespoke, not design-system-tokenizable.
 * Migrated from Dazzle into HM (HMC-014, aggressive-mode convergence 2026-07-08).
 * NOTE: no `@layer components {` wrapper here — HM build.py adds it.
 */

/* ─────────────────────────────────────────────────────────────────
 * Shared outer element. Custom elements default to display: inline
 * — that's almost always wrong for an overlay. Make every kind
 * `block` by default; per-kind overrides take it from there.
 * ───────────────────────────────────────────────────────────────── */
dz-onboarding-step {
  display: block;
}

/* Hide overlay visibly during dismiss-fade so the htmx outer-swap
 * isn't jarring. Reduced motion respects the user preference. */
@media (prefers-reduced-motion: no-preference) {
  dz-onboarding-step {
    transition: opacity 0.15s ease;
  }
}

/* ─────────────────────────────────────────────────────────────────
 * popover — floating callout, anchored or bottom-fixed
 * ───────────────────────────────────────────────────────────────── */
.dz-onboarding-popover {
  position: fixed;
  bottom: var(--space-xl);
  left: 50%;
  transform: translateX(-50%);
  z-index: 9000;
  max-width: min(28rem, calc(100vw - var(--space-xl) * 2));
  padding: var(--space-lg);
  background: var(--colour-surface);
  color: var(--colour-text);
  border: 1px solid var(--colour-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  font-size: var(--text-sm);
}

.dz-onboarding-popover__chrome {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-md);
  margin-bottom: var(--space-sm);
}

.dz-onboarding-popover__title {
  margin: 0;
  font-size: var(--text-base);
  font-weight: var(--weight-medium);
  color: var(--colour-text);
}

.dz-onboarding-popover__body {
  margin: 0 0 var(--space-md);
  color: var(--colour-text-muted);
  line-height: 1.5;
}

.dz-onboarding-popover__actions {
  display: flex;
  justify-content: flex-end;
}

/* ─────────────────────────────────────────────────────────────────
 * spotlight — dimmed page + halo ring + callout
 * ───────────────────────────────────────────────────────────────── */
.dz-onboarding-spotlight {
  position: fixed;
  inset: 0;
  z-index: 9100;
}

.dz-onboarding-spotlight__backdrop {
  position: absolute;
  inset: 0;
  background: rgb(0 0 0 / 0.55);
}

.dz-onboarding-spotlight__ring {
  /* JS positions this against the target in v0.71.6+; the default
   * shape is a centered translucent ring as a fallback so the dim
   * is non-blocking. */
  position: absolute;
  top: 50%;
  left: 50%;
  width: 6rem;
  height: 6rem;
  margin: -3rem 0 0 -3rem;
  border-radius: var(--radius-full);
  box-shadow:
    0 0 0 999px rgb(0 0 0 / 0.0),
    0 0 0 4px var(--colour-brand);
  pointer-events: none;
}

.dz-onboarding-spotlight__card {
  position: absolute;
  top: 50%;
  left: 50%;
  /* Offset by 8rem so the ring (3rem half-height + 4px border-shadow)
   * has visible breathing room before the card starts. Tighter than
   * this and the ring and card visually fuse. */
  transform: translate(-50%, calc(-50% + 8rem));
  max-width: min(26rem, calc(100vw - var(--space-xl) * 2));
  padding: var(--space-lg);
  background: var(--colour-surface);
  color: var(--colour-text);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
}

.dz-onboarding-spotlight__title {
  margin: 0 0 var(--space-sm);
  font-size: var(--text-lg);
  font-weight: var(--weight-medium);
}

.dz-onboarding-spotlight__body {
  margin: 0 0 var(--space-md);
  color: var(--colour-text-muted);
  line-height: 1.5;
}

.dz-onboarding-spotlight__dismiss {
  position: absolute;
  top: var(--space-sm);
  right: var(--space-sm);
}

/* ─────────────────────────────────────────────────────────────────
 * inline_card — solid card in page flow
 * ───────────────────────────────────────────────────────────────── */
.dz-onboarding-inline-card {
  margin: var(--space-lg) 0;
  padding: var(--space-lg);
  background: var(--colour-surface);
  color: var(--colour-text);
  border: 1px solid var(--colour-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-sm);
}

.dz-onboarding-inline-card__chrome {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-md);
  margin-bottom: var(--space-sm);
}

.dz-onboarding-inline-card__title {
  margin: 0;
  font-size: var(--text-base);
  font-weight: var(--weight-medium);
}

.dz-onboarding-inline-card__body {
  margin: 0 0 var(--space-md);
  color: var(--colour-text-muted);
  line-height: 1.5;
}

.dz-onboarding-inline-card__actions {
  display: flex;
  gap: var(--space-sm);
}

/* ─────────────────────────────────────────────────────────────────
 * empty_state — large-format replace-the-list prompt
 * ───────────────────────────────────────────────────────────────── */
.dz-onboarding-empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: var(--space-md);
  padding: var(--space-3xl) var(--space-xl);
  background: var(--colour-surface);
  color: var(--colour-text);
  border: 1px dashed var(--colour-border);
  border-radius: var(--radius-lg);
  margin: var(--space-xl) 0;
}

.dz-onboarding-empty-state__icon {
  width: 3rem;
  height: 3rem;
  border-radius: var(--radius-full);
  background: var(--colour-brand);
  opacity: 0.15;
}

.dz-onboarding-empty-state__title {
  margin: 0;
  font-size: var(--text-xl);
  font-weight: var(--weight-medium);
}

.dz-onboarding-empty-state__body {
  margin: 0;
  max-width: 36rem;
  color: var(--colour-text-muted);
  line-height: 1.5;
}

.dz-onboarding-empty-state__actions {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  margin-top: var(--space-sm);
}

/* ─────────────────────────────────────────────────────────────────
 * banner — full-width strip across the top
 * ───────────────────────────────────────────────────────────────── */
.dz-onboarding-banner {
  position: sticky;
  top: 0;
  z-index: 8000;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-md);
  padding: var(--space-sm) var(--space-lg);
  background: var(--colour-brand);
  color: var(--colour-brand-contrast);
  font-size: var(--text-sm);
}

.dz-onboarding-banner__message {
  flex: 1 1 auto;
  min-width: 0;
}

.dz-onboarding-banner__title {
  font-weight: var(--weight-medium);
}

.dz-onboarding-banner__separator {
  opacity: 0.7;
}

.dz-onboarding-banner__body {
  opacity: 0.9;
}

.dz-onboarding-banner__actions {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  flex: 0 0 auto;
}

/* ─────────────────────────────────────────────────────────────────
 * checklist_item — list row with checkbox indicator
 * ───────────────────────────────────────────────────────────────── */
.dz-onboarding-checklist-item {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: var(--space-md);
  padding: var(--space-md) var(--space-lg);
  background: var(--colour-surface);
  color: var(--colour-text);
  border: 1px solid var(--colour-border);
  border-radius: var(--radius-md);
  margin: var(--space-sm) 0;
}

.dz-onboarding-checklist-item__indicator {
  width: 1.25rem;
  height: 1.25rem;
  border-radius: var(--radius-sm);
  border: 2px solid var(--colour-border);
  background: transparent;
}

.dz-onboarding-checklist-item__content {
  min-width: 0;
}

.dz-onboarding-checklist-item__title {
  margin: 0 0 var(--space-xs);
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
}

.dz-onboarding-checklist-item__body {
  margin: 0;
  font-size: var(--text-xs);
  color: var(--colour-text-muted);
  line-height: 1.4;
}

.dz-onboarding-checklist-item__actions {
  display: flex;
  gap: var(--space-sm);
}

.dz-onboarding-checklist-item__dismiss--hidden {
  display: none;
}

/* ─────────────────────────────────────────────────────────────────
 * blocking_task — native <dialog> modal with scrim
 * ───────────────────────────────────────────────────────────────── */
.dz-onboarding-blocking-task {
  /* Outer custom element is just a container; the <dialog> handles
   * positioning + the modal stacking context. */
  display: contents;
}

.dz-onboarding-blocking-task__backdrop {
  position: fixed;
  inset: 0;
  background: rgb(0 0 0 / 0.55);
  z-index: 9199;
}

.dz-onboarding-blocking-task__dialog {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  z-index: 9200;
  max-width: min(32rem, calc(100vw - var(--space-xl) * 2));
  padding: var(--space-xl);
  background: var(--colour-surface);
  color: var(--colour-text);
  border: none;
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
}

.dz-onboarding-blocking-task__title {
  margin: 0 0 var(--space-md);
  font-size: var(--text-xl);
  font-weight: var(--weight-medium);
}

.dz-onboarding-blocking-task__body {
  margin: 0 0 var(--space-lg);
  color: var(--colour-text-muted);
  line-height: 1.5;
}

.dz-onboarding-blocking-task__actions {
  display: flex;
  justify-content: flex-end;
}

/* ─────────────────────────────────────────────────────────────────
 * nudge — small toast, bottom-right
 * ───────────────────────────────────────────────────────────────── */
.dz-onboarding-nudge {
  position: fixed;
  bottom: var(--space-xl);
  right: var(--space-xl);
  z-index: 8500;
  display: flex;
  align-items: center;
  gap: var(--space-md);
  max-width: min(24rem, calc(100vw - var(--space-xl) * 2));
  padding: var(--space-md) var(--space-lg);
  background: var(--colour-surface);
  color: var(--colour-text);
  border: 1px solid var(--colour-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
  font-size: var(--text-sm);
}

.dz-onboarding-nudge__content {
  flex: 1 1 auto;
  min-width: 0;
}

.dz-onboarding-nudge__title {
  display: block;
  margin-bottom: var(--space-xs);
  font-weight: var(--weight-medium);
}

.dz-onboarding-nudge__body {
  display: block;
  color: var(--colour-text-muted);
}

.dz-onboarding-nudge__actions {
  display: flex;
  gap: var(--space-xs);
  flex: 0 0 auto;
}

/* ─────────────────────────────────────────────────────────────────
 * Shared dismiss + CTA styling for every kind. The renderer emits
 * per-kind classes (`__dismiss`, `__cta`) but every kind uses the
 * same visual baseline; per-kind blocks above override positioning
 * only where they need to.
 * ───────────────────────────────────────────────────────────────── */
/* Wrapped in :where() so the shared baseline has specificity (0,0,0).
 * That lets per-kind overrides (e.g. `.dz-onboarding-banner__cta`) win
 * on a single class selector — otherwise the attribute-pair (0,2,0)
 * would beat the class (0,1,0) and per-kind chrome inversions would
 * silently lose the cascade. */
:where([class*="dz-onboarding-"][class$="__dismiss"]) {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.75rem;
  height: 1.75rem;
  padding: 0;
  background: transparent;
  border: none;
  border-radius: var(--radius-sm);
  color: inherit;
  cursor: pointer;
  font-size: var(--text-base);
  line-height: 1;
  opacity: 0.6;
  transition: opacity 0.1s ease;
}

:where([class*="dz-onboarding-"][class$="__dismiss"]):hover {
  opacity: 1;
}

:where([class*="dz-onboarding-"][class$="__cta"]) {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-xs) var(--space-md);
  background: var(--colour-brand);
  color: var(--colour-brand-contrast);
  text-decoration: none;
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  border-radius: var(--radius-sm);
  cursor: pointer;
  border: none;
  transition: filter 0.1s ease;
}

:where([class*="dz-onboarding-"][class$="__cta"]):hover {
  filter: brightness(0.92);
}

/* Empty-state dismiss is a labelled "Skip" — give it ghost-button
 * shape rather than the bare ✕ chrome. */
.dz-onboarding-empty-state__dismiss {
  width: auto;
  padding: var(--space-xs) var(--space-md);
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  opacity: 1;
  color: var(--colour-text-muted);
}

/* Banner CTA + dismiss sit on a coloured ground — invert chrome
 * colours to stay legible. */
.dz-onboarding-banner__cta {
  background: var(--colour-brand-contrast);
  color: var(--colour-brand);
}

.dz-onboarding-banner__cta:hover {
  filter: brightness(0.96);
}

.dz-onboarding-banner__dismiss {
  color: var(--colour-brand-contrast);
}
/* --- hatchi-maxchi/components/pagination.css --- */
/* HYPERPART: pagination */
/*
 * dz-pagination — the footer beneath a data table: a summary on one side,
 * page-number buttons on the other. Each button is an htmx affordance
 * (hx-get a page → swap the list body), so this is an Exchange component,
 * not a client widget. Numbered buttons with an ellipsis for long ranges;
 * the current page is marked `.is-current` + `aria-current="page"`.
 */

.dz-pagination {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-inline: 0.25rem;
  padding-block: var(--space-sm);
  font-size: var(--text-sm);
}

.dz-pagination-summary {
  color: var(--colour-text-muted);
}

.dz-pagination-pages {
  display: flex;
  align-items: center;
  gap: 0.25rem;
}

.dz-pagination-page {
  height: 2rem;
  padding-inline: var(--space-md);
  border-radius: var(--radius-sm);
  font-size: var(--text-sm);
  color: var(--colour-text-muted);
  background: transparent;
  border: 0;
  cursor: pointer;
  transition:
    background var(--duration-fast) var(--ease-out),
    color var(--duration-fast) var(--ease-out);
}

.dz-pagination-page:hover {
  background: var(--colour-bg);
}

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

.dz-pagination-page.is-current {
  background: var(--colour-brand);
  color: var(--colour-brand-contrast);
}

/* prev / next arrows are just page buttons; disabled at the range ends */
.dz-pagination-page:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

.dz-pagination-page:disabled:hover {
  background: transparent;
}

/* #984: ellipsis marker between non-contiguous page-button ranges. Sized to
 * match the page-button height so the row stays flat, but non-interactive
 * (no hover, no cursor). aria-hidden in markup — screen readers skip it; the
 * surrounding buttons carry the page numbers. */
.dz-pagination-ellipsis {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 2rem;
  padding-inline: var(--space-xs);
  font-size: var(--text-sm);
  color: var(--colour-text-muted);
  user-select: none;
}
/* --- hatchi-maxchi/components/pdf-viewer.css --- */
/*
 * pdf-viewer.css — PDF detail-view chrome (HaTchi-MaXchi).
 *
 * The full PDF viewer surface: header (back / title / prev-next nav), body +
 * embed, optional footer slot, right-side panels (checkbox-toggled), and the
 * keyboard cheat-sheet <dialog>. Sits on top of the HM `pdf` embed primitive
 * (pdf.css: .dz-pdf / .dz-pdf-stage / toolbar), which it references as
 * descendants. Bespoke a11y/geometry values (1.75rem controls, 360px panel,
 * visually-hidden clip, focus outlines) are intentionally non-token.
 * Migrated from Dazzle into HM (HMC-015, aggressive-mode convergence 2026-07-08).
 */
.dz-pdf-viewer {
  display: grid;
  grid-template-rows: auto 1fr auto;
  height: 100vh;
  background: var(--colour-bg);
  color: var(--colour-text);
}

/* ── Header ──────────────────────────────────────────────────── */

.dz-pdf-viewer-header {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: var(--space-md);
  padding-block: var(--space-sm);
  padding-inline: var(--space-lg);
  background: var(--colour-surface);
  border-block-end: 1px solid var(--colour-border);
}

.dz-pdf-viewer-back {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
  padding-block: var(--space-xs);
  padding-inline: var(--space-sm);
  font-size: var(--text-sm);
  color: var(--colour-text-muted);
  text-decoration: none;
  border-radius: var(--radius-sm);
  transition: background var(--duration-base) var(--ease-out);
}

.dz-pdf-viewer-back:hover {
  background: var(--colour-bg);
  color: var(--colour-text);
}

.dz-pdf-viewer-back:focus-visible {
  /* Framework focus-ring convention — same shape as
   * `.dz-workspace-context-select`, `.dz-toggle-item`, etc. The
   * brand-tinted box-shadow stays visible in both light and dark
   * themes; a bare `outline: none` left keyboard users guessing
   * where focus lived (#942 cycle 1f). */
  background: var(--colour-bg);
  color: var(--colour-text);
  outline: none;
  box-shadow: 0 0 0 2px var(--colour-brand);
}

.dz-pdf-viewer-title {
  margin: 0;
  font-size: var(--text-base);
  font-weight: var(--weight-medium);
  text-align: center;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.dz-pdf-viewer-nav {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
}

.dz-pdf-viewer-nav-link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  inline-size: 1.75rem;
  block-size: 1.75rem;
  color: var(--colour-text-muted);
  text-decoration: none;
  border-radius: var(--radius-sm);
  transition: background var(--duration-base) var(--ease-out);
}

.dz-pdf-viewer-nav-link:hover {
  background: var(--colour-bg);
  color: var(--colour-text);
}

.dz-pdf-viewer-nav-link:focus-visible {
  /* Same framework focus-ring convention as the back button. */
  background: var(--colour-bg);
  color: var(--colour-text);
  outline: none;
  box-shadow: 0 0 0 2px var(--colour-brand);
}

/* Forced-colors / Windows High Contrast mode: the box-shadow rings
 * above are suppressed by the user agent, leaving keyboard users
 * with no focus indicator. Re-introduce a real outline using the
 * system ``Highlight`` colour so the chrome stays accessible.
 * Same fallback applies to both interactive elements. */
@media (forced-colors: active) {
  .dz-pdf-viewer-back:focus-visible,
  .dz-pdf-viewer-nav-link:focus-visible {
    outline: 2px solid Highlight;
    outline-offset: 2px;
    box-shadow: none;
  }
}

.dz-pdf-viewer-nav-link[aria-disabled="true"] {
  opacity: 0.4;
  pointer-events: none;
}

/* ── Body ────────────────────────────────────────────────────── */

.dz-pdf-viewer-body {
  position: relative;
  /* Semantic token, not a raw ramp value — flips correctly under
   * `[data-theme="dark"]` (binding from #938). Earlier draft used
   * `var(--neutral-100)` which is light-only and left a jarring
   * pale panel inside an otherwise-dark viewer; #942 cycle 1d's
   * dark-mode parity gates caught it. The body sits a step darker
   * than the chrome surfaces in both themes so the content area
   * reads as distinct from the header/footer. */
  background: var(--colour-bg);
  overflow: hidden;
}

.dz-pdf-viewer-embed {
  display: block;
  inline-size: 100%;
  block-size: 100%;
  border: 0;
}

/* hx-pdf P3: the embed slot now hosts the HM pdf Hyperpart. Inside the
 * full-page grid the SECTION becomes a grid (toolbar/status/stage) so
 * the stage takes the remainder — a bare block-size:100% stage would
 * overflow by the toolbar height and the section's overflow:hidden
 * would clip the document's bottom band. Corners squared: the region
 * is full-bleed here. */
.dz-pdf-viewer .dz-pdf.dz-pdf-viewer-embed {
  display: grid;
  grid-template-rows: auto auto 1fr;
  border-radius: 0;
}

.dz-pdf-viewer .dz-pdf-stage {
  max-height: none;
  block-size: auto;
  min-block-size: 0;
}

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

.dz-pdf-viewer-footer {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: var(--space-xs);
  padding-block: var(--space-xs);
  padding-inline: var(--space-lg);
  font-size: var(--text-xs);
  color: var(--colour-text-muted);
  background: var(--colour-surface);
  border-block-start: 1px solid var(--colour-border);
}

.dz-pdf-viewer-kbd {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  padding-block: 0;
  padding-inline: var(--space-xs);
  background: var(--colour-bg);
  border: 1px solid var(--colour-border);
  border-radius: var(--radius-sm);
}

.dz-pdf-viewer-kbd-label {
  margin-inline-end: var(--space-md);
}

.dz-pdf-viewer-kbd-sep {
  color: var(--colour-border);
}

/* #944 cycle 6a — optional project-supplied footer slot.
 * Renders before the keyboard legend in the same flex row. The
 * slot's children are project-shaped (page counter, zoom buttons,
 * pill toggles, etc); chrome stays minimal so projects don't have
 * to compete with framework layout. */
.dz-pdf-viewer-footer-slot {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
}

/* Hide the back-link label on narrow viewports to keep the header
 * tidy — the icon alone communicates the action. */
@media (max-width: 640px) {
  .dz-pdf-viewer-back-label {
    display: none;
  }
}

/* ── #942 cycle 2a — right-side panel ─────────────────────────
 *
 * Optional slide-in panel for related-entity content. Toggle is a
 * hidden checkbox; CSS-only show/hide via the parent's :has(:checked).
 * Keeps the wrapper free of x-data (preserves the widget contract
 * from #940 — the bridge mounts the keyboard handler; Alpine doesn't
 * touch this node).
 *
 * Drawer behaviour: panel `position: absolute`, slides in from the
 * trailing edge; default `transform: translateX(100%)` hides it
 * off-screen, `:has(:checked)` swaps to `translateX(0)`. CSS
 * transition respects the global `prefers-reduced-motion` rule
 * already in reset.css. Mobile (≤768px): panel goes full-width
 * over the content (drawer pattern). */

.dz-pdf-viewer-panel-toggle {
  /* Visually hidden but keyboard-accessible (the keyboard handler
   * .click()s it). Don't use display:none — that removes the
   * input from the DOM, breaking :has(:checked). */
  position: absolute;
  inline-size: 1px;
  block-size: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.dz-pdf-viewer-panel {
  position: absolute;
  inset-block: 0;
  inset-inline-end: 0;
  inline-size: 360px;
  display: grid;
  grid-template-rows: auto 1fr;
  background: var(--colour-surface);
  border-inline-start: 1px solid var(--colour-border);
  box-shadow: -4px 0 12px hsl(0 0% 0% / 0.06);
  transform: translateX(100%);
  transition: transform var(--duration-base) var(--ease-out);
  z-index: 1;
}

/* #943 cycle 5a — multi-panel exclusivity uses the adjacent-sibling
 * combinator so each toggle's checked state only affects its
 * immediately-following <aside>. The cycle 2a `:has(:checked)`
 * pattern can't disambiguate which checkbox is checked when N
 * exist, so it would slide every panel in. Adjacent-sibling does
 * the right thing for any N — including the cycle 2a single-panel
 * case (the toggle is still right before its panel). */
.dz-pdf-viewer-panel-toggle:checked + .dz-pdf-viewer-panel {
  transform: translateX(0);
}

.dz-pdf-viewer-panel-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-md);
  padding-block: var(--space-sm);
  padding-inline: var(--space-lg);
  background: var(--colour-bg);
  border-block-end: 1px solid var(--colour-border);
}

.dz-pdf-viewer-panel-title {
  margin: 0;
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
}

.dz-pdf-viewer-panel-close {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  inline-size: 1.75rem;
  block-size: 1.75rem;
  color: var(--colour-text-muted);
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: background var(--duration-base) var(--ease-out);
}

.dz-pdf-viewer-panel-close {
  /* `<button>`-styled like a borderless icon affordance. */
  background: transparent;
  border: 0;
  cursor: pointer;
}

.dz-pdf-viewer-panel-close:hover {
  background: var(--colour-surface);
  color: var(--colour-text);
}

.dz-pdf-viewer-panel-close:focus-visible {
  /* Same framework focus-ring convention as the back/nav buttons
   * (cycle 1f). The button is the actual focus target now (not a
   * visually-hidden checkbox), so the ring lands directly on the
   * affordance. */
  outline: none;
  box-shadow: 0 0 0 2px var(--colour-brand);
}

@media (forced-colors: active) {
  .dz-pdf-viewer-panel-close:focus-visible {
    outline: 2px solid Highlight;
    outline-offset: 2px;
    box-shadow: none;
  }
}

.dz-pdf-viewer-panel-body {
  overflow-y: auto;
  padding: var(--space-lg);
}

@media (max-width: 768px) {
  .dz-pdf-viewer-panel {
    inline-size: 100%;
  }
}

/* #943 cycle 5c — keyboard cheat-sheet overlay. Renders via the
 * native <dialog> element so backdrop + focus trap come for free. */
.dz-pdf-viewer-help {
  inline-size: min(420px, calc(100vw - var(--space-2xl)));
  max-block-size: 80vh;
  padding: 0;
  border: 1px solid var(--colour-border);
  border-radius: var(--radius-md);
  background: var(--colour-surface);
  color: var(--colour-text);
  box-shadow: 0 16px 48px hsl(0 0% 0% / 0.16);
}

.dz-pdf-viewer-help::backdrop {
  background: hsl(0 0% 0% / 0.32);
}

.dz-pdf-viewer-help-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-md);
  padding-block: var(--space-sm);
  padding-inline: var(--space-lg);
  border-block-end: 1px solid var(--colour-border);
}

.dz-pdf-viewer-help-title {
  margin: 0;
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
}

.dz-pdf-viewer-help-close {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  inline-size: 1.75rem;
  block-size: 1.75rem;
  background: transparent;
  border: 0;
  color: var(--colour-text-muted);
  border-radius: var(--radius-sm);
  cursor: pointer;
}

.dz-pdf-viewer-help-close:hover {
  background: var(--colour-bg);
  color: var(--colour-text);
}

.dz-pdf-viewer-help-close:focus-visible {
  outline: none;
  box-shadow: 0 0 0 2px var(--colour-brand);
}

@media (forced-colors: active) {
  .dz-pdf-viewer-help-close:focus-visible {
    outline: 2px solid Highlight;
    outline-offset: 2px;
    box-shadow: none;
  }
}

.dz-pdf-viewer-help-list {
  margin: 0;
  padding-block: var(--space-sm);
  padding-inline: 0;
  font-size: var(--text-sm);
}

.dz-pdf-viewer-help-row {
  display: grid;
  grid-template-columns: 8rem 1fr;
  align-items: baseline;
  gap: var(--space-md);
  padding-block: var(--space-xs);
  padding-inline: var(--space-lg);
}

.dz-pdf-viewer-help-row dt {
  margin: 0;
  display: flex;
  align-items: center;
  gap: var(--space-xs);
}

.dz-pdf-viewer-help-row dd {
  margin: 0;
  color: var(--colour-text-muted);
}

.dz-pdf-viewer-help-row kbd {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  padding-block: 0;
  padding-inline: var(--space-xs);
  background: var(--colour-bg);
  border: 1px solid var(--colour-border);
  border-radius: var(--radius-sm);
}
/* --- hatchi-maxchi/components/pdf.css --- */
/* HYPERPART: pdf */
/*
 * dz-pdf — the hx-pdf viewing shell. The application owns the chrome;
 * PDF.js paints into the viewer region's canvas. Authored for the HM
 * promotion of the ratified hx-pdf spec (§7 markup contract); the
 * legacy #946 dz-pdf-viewer chrome retires onto this in P3.
 */

.dz-pdf {
  border: 1px solid var(--colour-border);
  border-radius: var(--radius-md);
  background: var(--colour-surface);
  overflow: hidden;
}

.dz-pdf-toolbar {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-sm);
  padding: var(--space-sm) var(--space-md);
  border-block-end: 1px solid var(--colour-border);
}

.dz-pdf-toolbar label {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
  font-size: var(--text-sm);
  color: var(--colour-text-muted);
}

.dz-pdf-page-input {
  width: 3.5rem;
  height: 1.75rem;
  text-align: center;
  border: 1px solid var(--colour-border);
  border-radius: var(--radius-sm);
  background: var(--colour-surface);
  color: var(--colour-text);
  font-size: var(--text-sm);
}

.dz-pdf-page-count {
  font-size: var(--text-sm);
  color: var(--colour-text-muted);
}

.dz-pdf-toolbar-spacer {
  flex: 1 1 0;
}

.dz-pdf-status {
  font-size: var(--text-sm);
  color: var(--colour-text-muted);
  padding-inline: var(--space-md);
}

.dz-pdf-status:empty {
  display: none;
}

.dz-pdf-stage {
  overflow: auto;
  background: var(--colour-bg);
  padding: var(--space-md);
  min-height: 12rem;
  max-height: 40rem;
}

.dz-pdf-stage canvas {
  display: block;
  margin-inline: auto;
  box-shadow: var(--shadow-md, 0 2px 8px oklch(0% 0 0 / 0.15));
  background: white;
}

[data-dz-pdf-error="true"] .dz-pdf-stage {
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--colour-text-muted);
  font-size: var(--text-sm);
}
/* --- hatchi-maxchi/components/pivot.css --- */
/* HYPERPART: pivot */
/* Promoted VERBATIM from Dazzle regions.css (Tier C-b, fleet
 * convergence). The Dazzle emitter contract is unchanged. */

/* ─────────────────────────── pivot_table region ─────────────────
 * Multi-dimension cross-tab. Same shape as the heatmap grid but
 * with per-row dimension labels on the left and measure values
 * (right-aligned, mono-numeric) on the right. */

.dz-pivot-scroll {
  overflow-x: auto;
}

.dz-pivot-grid {
  width: 100%;
  font-size: var(--text-sm);
  border-collapse: collapse;
}

.dz-pivot-grid > thead > tr {
  border-block-end: 1px solid var(--colour-border);
}

.dz-pivot-grid th {
  padding-inline: var(--space-sm);
  padding-block: var(--space-xs);
  font-weight: var(--weight-medium);
  color: var(--colour-text-muted);
  text-align: start;
}

.dz-pivot-grid th.is-measure {
  text-align: end;
  font-family: var(--font-mono);
}

.dz-pivot-grid > tbody > tr {
  border-block-end: 1px solid var(--colour-border);
}

.dz-pivot-grid > tbody > tr:last-child {
  border-block-end: 0;
}

.dz-pivot-grid td {
  padding-inline: var(--space-sm);
  padding-block: var(--space-xs);
  color: var(--colour-text);
}

.dz-pivot-grid td.is-measure {
  text-align: end;
  font-family: var(--font-mono);
}

.dz-pivot-null {
  color: var(--colour-text-muted);
}

.dz-pivot-summary {
  font-size: var(--text-xs);
  color: var(--colour-text-muted);
  margin-block-start: var(--space-sm);
}
/* --- hatchi-maxchi/components/profile-card.css --- */
/* HYPERPART: profile-card */
/* Promoted VERBATIM from Dazzle regions.css (Tier A1, fleet
 * convergence — the LAST regions.css family). The Dazzle emitter
 * contract is unchanged. */

/* ─────────────────────────── profile_card region ────────────────
 * Identity panel: avatar/initials + primary/secondary text,
 * optional 3-up stats grid, optional bulleted facts list. */

.dz-profile-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
}

.dz-profile-identity {
  display: flex;
  align-items: flex-start;
  gap: var(--space-md);
}

.dz-profile-avatar,
.dz-profile-initials {
  width: 3rem;
  height: 3rem;
  border-radius: var(--radius-full);
  background: var(--colour-bg);
  flex-shrink: 0;
}

.dz-profile-avatar {
  object-fit: cover;
}

.dz-profile-initials {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  color: var(--colour-text-muted);
}

.dz-profile-text {
  flex: 1 1 0;
  min-width: 0;
}

.dz-profile-primary {
  font-size: var(--text-base);
  font-weight: var(--weight-semibold);
  color: var(--colour-text);
  margin: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.dz-profile-secondary {
  font-size: var(--text-xs);
  color: var(--colour-text-muted);
  margin-block-start: 0.125rem;
}

.dz-profile-stats {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-sm);
  border-block-start: 1px solid var(--colour-border);
  padding-block-start: var(--space-md);
  margin: 0;
}

@media (min-width: 40rem) {
  .dz-profile-stats {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

.dz-profile-stat-label {
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--colour-text-muted);
  margin: 0;
}

.dz-profile-stat-value {
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  color: var(--colour-text);
  margin-block-start: 0.125rem;
}

.dz-profile-facts {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  border-block-start: 1px solid var(--colour-border);
  padding-block-start: var(--space-md);
  font-size: var(--text-xs);
  color: var(--colour-text);
  list-style: none;
  margin: 0;
  padding-inline-start: 0;
}

.dz-profile-fact {
  display: flex;
  gap: var(--space-sm);
  line-height: var(--leading-tight);
}

.dz-profile-fact-bullet {
  color: var(--colour-text-muted);
  flex-shrink: 0;
}

.dz-profile-fact-text {
  min-width: 0;
}
/* --- hatchi-maxchi/components/progress-region.css --- */
/* HYPERPART: progress-region */
/* Promoted VERBATIM from Dazzle regions.css (Tier C-b, fleet
 * convergence). The Dazzle emitter contract is unchanged. */

/* ─────────────────────────── progress region ────────────────────
 * Header row: <progress> bar + percentage label. Stages row: chips
 * coloured by status (complete=success, in-progress=warning,
 * empty=neutral). Optional summary line beneath. */

.dz-progress-header {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  margin-block-end: var(--space-sm);
}

.dz-progress-header > progress {
  flex: 1 1 0;
  height: 0.5rem;
  width: 100%;
}

.dz-progress-header > span {
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  white-space: nowrap;
  color: var(--colour-text);
}

.dz-progress-stages {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xs);
}

.dz-progress-chip {
  display: inline-flex;
  align-items: center;
  padding-inline: var(--space-sm);
  padding-block: 0.125rem;
  border-radius: var(--radius-sm);
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  border: 1px solid var(--colour-border);
  background: var(--colour-bg);
  color: var(--colour-text-muted);
}

/* Per-stage tone — driven by data-dz-stage-tone (template emits
 * "complete" / "active" / "empty"). Mirror of the badge tone system. */
.dz-progress-chip[data-dz-stage-tone="complete"] {
  background: var(--colour-success-soft);
  color: var(--colour-success);
  border-color: color-mix(in oklch, var(--colour-success) 30%, transparent);
}

.dz-progress-chip[data-dz-stage-tone="active"] {
  background: var(--colour-warning-soft);
  color: var(--colour-warning);
  border-color: color-mix(in oklch, var(--colour-warning) 30%, transparent);
}

.dz-progress-summary {
  font-size: var(--text-xs);
  color: var(--colour-text-muted);
  margin-block-start: var(--space-sm);
}
/* --- hatchi-maxchi/components/queue.css --- */
/* HYPERPART: queue */
/* Promoted VERBATIM from Dazzle regions.css (W1, fleet convergence).
 * The Dazzle emitter contract predates the move and is unchanged. */

/* ─────────────────────────── queue region ───────────────────────
 * Review queue: count chip + metrics row + filter bar + rows with
 * inline transition buttons. Each row has plain padding (no full
 * border — dashboard slot owns chrome) and a hover tint when no
 * attention accent is present. */

.dz-queue-count-row {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  margin-block-end: var(--space-sm);
}

.dz-queue-count {
  display: inline-flex;
  align-items: center;
  padding-inline: var(--space-sm);
  height: 1.25rem;
  border-radius: var(--radius-xs, 0.1875rem);
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  background: var(--colour-brand);
  color: var(--colour-brand-contrast);
}

.dz-queue-metrics {
  display: flex;
  gap: var(--space-md);
  margin-block-end: var(--space-md);
}

.dz-queue-metric {
  flex: 1 1 0;
  text-align: center;
  padding: var(--space-sm);
  border-radius: var(--radius-sm);
  background: color-mix(in oklch, var(--colour-bg) 60%, transparent);
  border: 1px solid var(--colour-border);
}

.dz-queue-metric-value {
  font-size: var(--text-base);
  font-weight: var(--weight-semibold);
  color: var(--colour-text);
}

.dz-queue-metric-label {
  font-size: var(--text-xs);
  color: var(--colour-text-muted);
}

.dz-queue-filters {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-sm);
  margin-block-end: var(--space-sm);
}

.dz-queue-filter-select {
  height: 1.75rem;
  padding-inline: var(--space-sm);
  border-radius: var(--radius-sm);
  background: var(--colour-surface);
  border: 1px solid var(--colour-border);
  font-size: var(--text-xs);
  color: var(--colour-text);
}

.dz-queue-filter-select:focus-visible {
  outline: none;
  box-shadow: 0 0 0 1px var(--colour-brand);
  border-color: var(--colour-brand);
}

.dz-queue-rows {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

.dz-queue-row {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  padding: var(--space-md);
  border-radius: var(--radius-sm);
  transition: background var(--duration-fast) var(--ease-out);
}

/* Hover tint only when no attention accent — attention accents
 * carry their own tint via attention_classes macro. */
.dz-queue-row:not([data-dz-attn]):hover {
  background: var(--colour-bg);
}

.dz-queue-row-main {
  flex: 1 1 0;
  min-width: 0;
}

.dz-queue-row-main.is-clickable {
  cursor: pointer;
}

.dz-queue-row-headline {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
}

.dz-queue-row-title {
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  color: var(--colour-text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.dz-queue-row-attn {
  font-size: var(--text-xs);
  color: var(--colour-text-muted);
  margin-block-start: 0.125rem;
}

.dz-queue-row-date {
  font-size: var(--text-xs);
  color: var(--colour-text-muted);
}

.dz-queue-row-actions {
  display: flex;
  gap: 0.25rem;
  flex-shrink: 0;
}

.dz-queue-action {
  height: 1.75rem;
  padding-inline: 0.625rem;
  border-radius: var(--radius-sm);
  border: 1px solid var(--colour-border);
  background: transparent;
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  color: var(--colour-text);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  transition: background var(--duration-fast) var(--ease-out);
}

.dz-queue-action:hover {
  background: var(--colour-bg);
}

.dz-queue-overflow {
  font-size: var(--text-xs);
  color: var(--colour-text-muted);
  margin-block-start: var(--space-sm);
}

.dz-queue-empty {
  text-align: center;
  padding-block: var(--space-md);
}
/* --- hatchi-maxchi/components/related-tables.css --- */
/* HYPERPART: related-tables */
/* Promoted VERBATIM from Dazzle fragments.css (W2, fleet convergence).
 * The Dazzle emitter contract predates the move and is unchanged. */

/* ─────────────────────────── related-* fragments ────────────────
 * Shared chrome across related_status_cards, related_table_group,
 * related_file_list — each renders one or more "tabs" of related
 * entities. Group label, create button, and card/row hover all
 * share the same shape. */

.dz-related-group {
  margin-block-end: var(--space-md);
}

.dz-related-tab-label {
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  color: var(--colour-text-muted);
  margin-block-end: var(--space-sm);
}

.dz-related-create-row {
  display: flex;
  justify-content: flex-end;
  margin-block-end: var(--space-sm);
}

.dz-related-create-button {
  display: inline-flex;
  align-items: center;
  height: 1.75rem;
  padding-inline: 0.625rem;
  border-radius: var(--radius-sm);
  background: var(--colour-brand);
  color: var(--colour-brand-contrast);
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  text-decoration: none;
  transition: filter var(--duration-fast) var(--ease-out);
}

.dz-related-create-button:hover {
  filter: brightness(1.1);
}

.dz-related-empty {
  text-align: center;
  padding-block: var(--space-lg);
  font-size: var(--text-sm);
  color: var(--colour-text-muted);
}

/* status_cards — responsive 1/2/3-col card grid */
.dz-related-status-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-md);
}

@media (min-width: 40rem) {
  .dz-related-status-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (min-width: 64rem) {
  .dz-related-status-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

.dz-related-status-card {
  background: var(--colour-surface);
  border: 1px solid var(--colour-border);
  border-radius: var(--radius-md);
  padding: var(--space-md);
  cursor: pointer;
  transition: box-shadow var(--duration-base) var(--ease-out);
}

.dz-related-status-card:hover {
  box-shadow: 0 2px 8px oklch(0% 0 0 / 0.06);
}

.dz-related-status-card-row {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-sm);
}

.dz-related-status-card-text {
  min-width: 0;
}

.dz-related-status-card-primary {
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  color: var(--colour-text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  margin: 0;
}

.dz-related-status-card-secondary {
  font-size: var(--text-xs);
  color: var(--colour-text-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  margin: 0;
}

.dz-related-status-card-badge {
  flex-shrink: 0;
}

/* table_group — tab strip rides the HM tabs Hyperpart (Tier F4):
 * .dz-tabs__list/.dz-tabs__tab from tabs.css, driven by dz-tabs.js.
 * The bespoke .dz-related-tabs/.dz-related-tab pill family (visually
 * the same underline idiom) was retired with the Alpine activeTab
 * island. Only the count chip stays related-specific. */
.dz-related-tab-count {
  margin-inline-start: 0.25rem;
  display: inline-flex;
  align-items: center;
  padding-inline: 0.375rem;
  height: 1rem;
  border-radius: var(--radius-sm);
  background: var(--colour-bg);
  font-size: var(--text-xs);
  color: var(--colour-text-muted);
}

.dz-related-table-card {
  background: var(--colour-surface);
  border: 1px solid var(--colour-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-sm);
  overflow: hidden;
  margin-block-start: var(--space-sm);
}

.dz-related-table-create-row {
  display: flex;
  justify-content: flex-end;
  padding: var(--space-md);
  padding-block-end: 0;
}

.dz-related-table-scroll {
  overflow-x: auto;
}

.dz-related-table {
  width: 100%;
  border-collapse: collapse;
}

.dz-related-table > thead > tr {
  background: color-mix(in oklch, var(--colour-bg) 70%, transparent);
  border-block-end: 1px solid var(--colour-border);
}

.dz-related-table th {
  text-align: start;
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  color: var(--colour-text-muted);
  padding-inline: var(--space-md);
  padding-block: var(--space-sm);
  letter-spacing: -0.01em;
}

.dz-related-table > tbody > tr {
  border-block-end: 1px solid var(--colour-border);
  cursor: pointer;
  transition: background var(--duration-fast) var(--ease-out);
}

.dz-related-table > tbody > tr:last-child {
  border-block-end: 0;
}

.dz-related-table > tbody > tr:hover {
  background: var(--colour-bg);
}

.dz-related-table td {
  font-size: var(--text-sm);
  color: var(--colour-text);
  padding-inline: var(--space-md);
  padding-block: 0.625rem;
}

.dz-related-table-empty-cell {
  text-align: center;
  padding-block: var(--space-2xl);
  font-size: var(--text-sm);
  color: var(--colour-text-muted);
}

/* file_list — compact rows for documents/evidence */
.dz-related-file-list {
  background: var(--colour-surface);
  border: 1px solid var(--colour-border);
  border-radius: var(--radius-md);
  overflow: hidden;
}

.dz-related-file-list > * + * {
  border-block-start: 1px solid var(--colour-border);
}

.dz-related-file-row {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  padding-inline: var(--space-md);
  padding-block: var(--space-md);
  cursor: pointer;
  transition: background var(--duration-fast) var(--ease-out);
}

.dz-related-file-row:hover {
  background: var(--colour-bg);
}

.dz-related-file-icon {
  width: 1.25rem;
  height: 1.25rem;
  flex-shrink: 0;
  color: color-mix(in oklch, var(--colour-text-muted) 60%, transparent);
}

.dz-related-file-text {
  flex: 1 1 0;
  min-width: 0;
}

.dz-related-file-primary {
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  color: var(--colour-text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  margin: 0;
}

.dz-related-file-secondary {
  font-size: var(--text-xs);
  color: var(--colour-text-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  margin: 0;
}

.dz-related-file-date {
  font-size: var(--text-xs);
  color: var(--colour-text-muted);
  flex-shrink: 0;
}
/* --- hatchi-maxchi/components/richtext.css --- */
/*
 * richtext.css — rich-text editor internals (HaTchi-MaXchi).
 *
 * The `.dz-form-richtext` editor surface: toolbar buttons, the
 * `[data-dz-editor]` contenteditable area, and its typographic content
 * (headings, lists, marks). Complements the `.dz-form-richtext` field
 * wrapper base defined in form.css. Migrated from Dazzle into HM
 * (HMC-021, aggressive-mode convergence 2026-07-09).
 */

.dz-form-richtext .dz-richtext-toolbar {
  display: flex;
  flex-wrap: wrap;
  gap: var(--dz-space-1, 0.25rem);
  padding: var(--dz-space-1, 0.25rem);
  border: 1px solid var(--dz-color-border, #e5e7eb);
  border-bottom: none;
  border-radius: var(--dz-radius-sm, 0.25rem) var(--dz-radius-sm, 0.25rem) 0 0;
  background: var(--dz-color-surface-subtle, #f9fafb);
}

.dz-form-richtext .dz-richtext-toolbar button[data-cmd] {
  appearance: none;
  border: 1px solid transparent;
  background: transparent;
  color: var(--dz-color-text, #111827);
  min-width: 2rem;
  min-height: 2rem;
  padding: 0 var(--dz-space-2, 0.5rem);
  border-radius: var(--dz-radius-sm, 0.25rem);
  font-family: var(--dz-font-sans);
  font-size: var(--text-sm, 0.875rem);
  cursor: pointer;
  line-height: 1;
}

.dz-form-richtext .dz-richtext-toolbar button[data-cmd]:hover,
.dz-form-richtext .dz-richtext-toolbar button[data-cmd]:focus-visible {
  background: var(--dz-color-surface-hover, #f3f4f6);
  border-color: var(--dz-color-border, #e5e7eb);
  outline: none;
}

.dz-form-richtext .dz-richtext-toolbar button[data-cmd][aria-pressed="true"],
.dz-form-richtext .dz-richtext-toolbar button[data-cmd].is-active {
  background: var(--dz-color-surface-selected, #dbeafe);
  border-color: var(--dz-color-primary, #3b82f6);
  color: var(--dz-color-primary, #3b82f6);
}

.dz-form-richtext [data-dz-editor] {
  min-height: 8rem;
  padding: var(--dz-space-3, 0.75rem);
  border: 1px solid var(--dz-color-border, #e5e7eb);
  border-radius: 0 0 var(--dz-radius-sm, 0.25rem) var(--dz-radius-sm, 0.25rem);
  background: var(--dz-color-surface, #ffffff);
  color: var(--dz-color-text, #111827);
  font-family: var(--dz-font-sans);
  font-size: var(--text-base, 1rem);
  line-height: 1.5;
  outline: none;
}

.dz-form-richtext [data-dz-editor]:focus-visible,
.dz-form-richtext [data-dz-editor]:focus-within {
  border-color: var(--dz-color-primary, #3b82f6);
  box-shadow: 0 0 0 2px var(--dz-color-primary-soft, rgba(59, 130, 246, 0.2));
}

.dz-form-richtext [data-dz-editor] p {
  margin: 0 0 var(--dz-space-2, 0.5rem) 0;
}

.dz-form-richtext [data-dz-editor] p:last-child {
  margin-bottom: 0;
}

.dz-form-richtext [data-dz-editor] strong { font-weight: 700; }
.dz-form-richtext [data-dz-editor] em { font-style: italic; }
.dz-form-richtext [data-dz-editor] u { text-decoration: underline; }
.dz-form-richtext [data-dz-editor] s { text-decoration: line-through; }

/* Cycle 2: blocks + lists + links + inline code. */
.dz-form-richtext [data-dz-editor] h2 {
  font-size: var(--dz-heading-app-section-title, var(--text-base));
  font-weight: 700;
  margin: var(--dz-space-3, 0.75rem) 0 var(--dz-space-2, 0.5rem) 0;
  line-height: 1.25;
}
.dz-form-richtext [data-dz-editor] h3 {
  font-size: var(--dz-heading-app-subsection-title, var(--text-sm));
  font-weight: 600;
  margin: var(--dz-space-2, 0.5rem) 0;
  line-height: 1.3;
}
.dz-form-richtext [data-dz-editor] ul,
.dz-form-richtext [data-dz-editor] ol {
  margin: 0 0 var(--dz-space-2, 0.5rem) 0;
  padding-left: 1.5rem;
}
.dz-form-richtext [data-dz-editor] li {
  margin: 0;
}
.dz-form-richtext [data-dz-editor] blockquote {
  margin: var(--dz-space-2, 0.5rem) 0;
  padding-left: var(--dz-space-3, 0.75rem);
  border-left: 3px solid var(--dz-color-border, #e5e7eb);
  color: var(--dz-color-text-subtle, #6b7280);
}
.dz-form-richtext [data-dz-editor] code {
  font-family: var(--dz-font-mono, ui-monospace, SFMono-Regular, monospace);
  font-size: 0.9em;
  background: var(--dz-color-surface-subtle, #f3f4f6);
  padding: 0.1em 0.3em;
  border-radius: var(--dz-radius-xs, 0.125rem);
}
.dz-form-richtext [data-dz-editor] a {
  color: var(--dz-color-primary, #3b82f6);
  text-decoration: underline;
}

.dz-form-richtext .dz-richtext-toolbar-sep {
  display: inline-block;
  width: 1px;
  align-self: stretch;
  background: var(--dz-color-border, #e5e7eb);
  margin: 0 var(--dz-space-1, 0.25rem);
}
/* --- hatchi-maxchi/components/search-box.css --- */
/* HYPERPART: search-box */
/* Tier F3, fleet convergence. Two lineages in one file:
 *
 * 1. The `.dz-search-box-*` family is AUTHORED here — the Dazzle FTS
 *    search_box region emitted this markup UNSTYLED (no rules existed
 *    anywhere), so these are new HM styles for the live contract, not
 *    a move. The coaching-line toggle is pure CSS (`:has` +
 *    `:placeholder-shown`) — it replaced the Alpine `q` island.
 *
 * 2. The `.dz-select-result-*` / `.dz-search-result-*` families are
 *    promoted VERBATIM from Dazzle fragments.css (the search-flow
 *    autofill pattern; also consumed by the search-select widget). */

.dz-search-box-region {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

.dz-search-box-input-row {
  display: flex;
  align-items: center;
}

.dz-search-box-input {
  width: 100%;
  height: 2.25rem;
  padding-inline: var(--space-md);
  border-radius: var(--radius-sm);
  background: var(--colour-surface);
  border: 1px solid var(--colour-border);
  font-size: var(--text-sm);
  color: var(--colour-text);
}

.dz-search-box-input:focus-visible {
  outline: none;
  border-color: var(--colour-brand);
  box-shadow: 0 0 0 1px var(--colour-brand);
}

/* Coaching line — visible until the user types. Pure CSS: while the
 * input shows its placeholder the query is empty; the first keystroke
 * hides the line (and the first result swap replaces it entirely). */
.dz-search-box-region:has(.dz-search-box-input:not(:placeholder-shown))
  .dz-search-box-empty:not(.dz-search-box-empty--no-results) {
  display: none;
}

.dz-search-box-empty {
  font-size: var(--text-sm);
  color: var(--colour-text-muted);
  padding-block: var(--space-sm);
}

.dz-search-box-result-count {
  font-size: var(--text-xs);
  color: var(--colour-text-muted);
  margin-block-end: var(--space-xs);
}

.dz-search-box-result-list {
  list-style: none;
  margin: 0;
  padding-inline-start: 0;
  display: flex;
  flex-direction: column;
}

.dz-search-box-result {
  border-block-end: 1px solid var(--colour-border);
}

.dz-search-box-result:last-child {
  border-block-end: 0;
}

.dz-search-box-result-link {
  display: block;
  padding-block: var(--space-sm);
  padding-inline: var(--space-xs);
  text-decoration: none;
  border-radius: var(--radius-sm);
  transition: background var(--duration-fast) var(--ease-out);
}

.dz-search-box-result-link:hover {
  background: var(--colour-bg);
}

.dz-search-box-result-title {
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  color: var(--colour-text);
}

.dz-search-box-result-snippets {
  list-style: none;
  margin: 0.125rem 0 0;
  padding-inline-start: 0;
}

.dz-search-box-result-snippet {
  font-size: var(--text-xs);
  color: var(--colour-text-muted);
}

.dz-search-box-result-snippet-field {
  font-weight: var(--weight-medium);
  margin-inline-end: 0.25rem;
}

/* mark = the FTS highlight the server emits inside snippets/titles */
.dz-search-box-result-link mark {
  background: var(--colour-warning-soft);
  color: inherit;
  border-radius: 2px;
}

/* ── search-flow fragments (VERBATIM from Dazzle fragments.css) ──
 * select_result + search_results fragments — used by the
 * search-flow autofill pattern (cycle 288). select_result uses a
 * success-tinted "Selected:" line; search_results renders a list
 * of clickable rows + an empty/no-results state. */

.dz-select-result-confirm {
  padding: var(--space-md);
  font-size: var(--text-sm);
  color: var(--colour-success);
}

.dz-select-result-input {
  width: 100%;
  height: 2rem;
  padding-inline: var(--space-md);
  border-radius: var(--radius-sm);
  background: var(--colour-surface);
  border: 1px solid var(--colour-border);
  font-size: var(--text-sm);
  color: var(--colour-text);
}

.dz-search-result-row {
  padding-inline: var(--space-md);
  padding-block: var(--space-sm);
  cursor: pointer;
  transition: background var(--duration-fast) var(--ease-out);
}

.dz-search-result-row:hover {
  background: var(--colour-bg);
}

.dz-search-result-name {
  font-weight: var(--weight-medium);
  font-size: var(--text-sm);
}

.dz-search-result-secondary {
  font-size: var(--text-xs);
  color: var(--colour-text-muted);
}

.dz-search-result-empty {
  padding: var(--space-md);
  font-size: var(--text-sm);
  color: var(--colour-text-muted);
}
/* --- hatchi-maxchi/components/search-select.css --- */
/* HYPERPART: search-select */
/* Promoted from Dazzle fragments.css (Tier F4b, fleet convergence).
 * One deliberate delta: the results panel's visibility moved from
 * Alpine x-show/x-cloak onto state-in-DOM — the input SSRs
 * aria-expanded="false", controllers/dz-search-select.js flips it on
 * focusin/focusout, and the sibling rule below hides the panel off
 * the attribute. */

/* ─────────────────────────── search_select fragment ─────────────
 * Debounced search with dropdown results. Standard form input
 * chrome + spinner + ARIA combobox listbox panel below. Error
 * state (border colour) keyed off `aria-invalid="true"` — same
 * convention as form.css. */

.dz-search-select {
  position: relative;
  width: 100%;
}

.dz-search-select-input {
  width: 100%;
  height: 2rem;
  padding-inline-start: var(--space-md);
  padding-inline-end: 2rem;
  border-radius: var(--radius-sm);
  background: var(--colour-surface);
  border: 1px solid var(--colour-border);
  font-size: var(--text-sm);
  color: var(--colour-text);
  outline: none;
  transition:
    box-shadow var(--duration-base) var(--ease-out),
    border-color var(--duration-base) var(--ease-out);
}

.dz-search-select-input::placeholder {
  color: var(--colour-text-muted);
}

.dz-search-select-input:focus-visible {
  box-shadow: 0 0 0 1px var(--colour-brand);
  border-color: var(--colour-brand);
}

.dz-search-select-input[aria-invalid="true"] {
  border-color: var(--colour-danger);
}

.dz-search-select-input[aria-invalid="true"]:focus-visible {
  box-shadow: 0 0 0 1px var(--colour-danger);
}

.dz-search-select-spinner {
  position: absolute;
  inset-inline-end: var(--space-md);
  inset-block-start: 50%;
  transform: translateY(-50%);
  width: 1rem;
  height: 1rem;
  color: var(--colour-text-muted);
}

.dz-search-select-spinner-icon {
  width: 1rem;
  height: 1rem;
  animation: dz-spin 1s linear infinite;
}

.dz-search-select-results {
  position: absolute;
  z-index: 50;
  width: 100%;
  margin-block-start: 0.25rem;
  max-height: 15rem;
  overflow-y: auto;
  background: var(--colour-surface);
  border: 1px solid var(--colour-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
}

.dz-search-select-prompt {
  padding-inline: var(--space-md);
  padding-block: var(--space-md);
  font-size: var(--text-sm);
  color: var(--colour-text-muted);
}

/* Results visibility rides `data-dz-open` on the ROOT (set by
 * dz-search-select.js) — closed at SSR (attribute absent), open on
 * focus, closed 200ms after focus leaves the widget (the grace lets a
 * result-row click land before the panel hides). Root-keyed, NOT
 * input-keyed: the select exchange OOB-replaces the input element, so
 * an input-anchored sibling rule would wedge the panel open after a
 * successful selection. aria-expanded on the input is the a11y mirror. */
.dz-search-select:not([data-dz-open="true"]) .dz-search-select-results {
  display: none;
}
/* --- hatchi-maxchi/components/sitespec.css --- */
/* HaTchi-MaXchi — Sitespec / marketing page sections (hero, features, pricing, FAQ, testimonials, stats, CTA, steps, logos, comparison, cards, trust-bar, team, backgrounds). Ported from Dazzle site-sections.css (HM-sitespec 1B, 2026-07-09) — theme-agnostic structure; aesthetic families reflavor via tokens. Authored with dz- prefix (build keeps it for Dazzle, strips for standalone). */

/* =============================================================================
   Hero Section
   ============================================================================= */

.dz-section-hero {
  background: linear-gradient(135deg, var(--dz-hero-bg-from) 0%, var(--dz-hero-bg-to) 100%);
  color: white;
  padding: var(--dz-spacing-hero-y, 6rem) 1.5rem;
  text-align: center;
  position: relative;
  overflow: hidden;
}

.dz-section-hero::before {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: radial-gradient(circle at 30% 70%, oklch(1 0 0 / 0.1) 0%, transparent 50%);
  pointer-events: none;
}

/* Hero with Media Image — flex on the section to create two columns */
.dz-hero-with-media {
  display: flex;
  align-items: center;
  gap: 3rem;
}

.dz-hero-with-media .dz-section-content {
  flex: 1;
  text-align: left;
}

.dz-hero-with-media .dz-cta-group {
  justify-content: flex-start;
}

.dz-hero-media {
  flex: 1;
  max-width: 50%;
}

.dz-hero-image {
  width: 100%;
  height: auto;
  border-radius: var(--dz-radius-lg, 1rem);
  box-shadow: var(--dz-shadow-xl, 0 20px 50px -12px oklch(0 0 0 / 0.3));
}

@media (max-width: 768px) {
  .dz-hero-with-media {
    flex-direction: column;
    text-align: center;
  }

  .dz-hero-with-media .dz-section-content {
    text-align: center;
  }

  .dz-hero-with-media .dz-cta-group {
    justify-content: center;
  }

  .dz-hero-media {
    max-width: 100%;
    order: -1;
  }
}

/* =============================================================================
   Features Section
   ============================================================================= */

.dz-section-features {
  padding: var(--dz-spacing-section-y, 5rem) 1.5rem;
  background: var(--p, oklch(0.99 0 0));
}

/* =============================================================================
   Pricing Section
   ============================================================================= */

.dz-section-pricing {
  padding: var(--dz-spacing-section-y, 5rem) 1.5rem;
  background: var(--dz-section-alt-bg, oklch(0.97 0.01 260));
}

/* =============================================================================
   FAQ Section
   ============================================================================= */

.dz-section-faq {
  padding: var(--dz-spacing-section-y, 5rem) 1.5rem;
}

/* =============================================================================
   Testimonials Section
   ============================================================================= */

.dz-section-testimonials {
  padding: var(--dz-spacing-section-y, 5rem) 1.5rem;
  background: var(--dz-section-alt-bg, oklch(0.97 0.01 260));
}

/* =============================================================================
   Stats Section
   ============================================================================= */

.dz-section-stats {
  padding: var(--dz-spacing-section-y, 5rem) 1.5rem;
  background: linear-gradient(135deg, var(--dz-hero-bg-from) 0%, var(--dz-hero-bg-to) 100%);
  color: white;
}

/* =============================================================================
   CTA Section
   ============================================================================= */

.dz-section-cta {
  padding: var(--dz-spacing-section-y, 5rem) 1.5rem;
  background: linear-gradient(135deg, var(--dz-hero-bg-from) 0%, var(--dz-hero-bg-to) 100%);
  color: white;
  text-align: center;
}

.dz-section-cta h2 {
  font-size: var(--dz-font-size-cta-headline, 3rem);
  font-weight: 800;
  letter-spacing: -0.02em;
  color: white;
}

.dz-section-cta .dz-button[data-dz-variant="primary"] {
  background: var(--dz-cta-fill);
  color: var(--dz-cta-text);
  border: none;
  font-weight: 700;
  font-size: 1.0625rem;
  padding: 0.875rem 1.75rem;
  border-radius: var(--dz-radius-button, 0.5rem);
  box-shadow: var(--dz-cta-shadow);
  transition: transform var(--dz-transition-fast, 150ms), box-shadow var(--dz-transition-fast, 150ms);
}

.dz-section-cta .dz-button[data-dz-variant="primary"]:hover {
  background: var(--dz-cta-fill-hover);
  box-shadow: var(--dz-cta-shadow-hover);
  transform: translateY(-1px);
}

/* =============================================================================
   Steps Section
   ============================================================================= */

.dz-section-steps {
  padding: var(--dz-spacing-section-y, 5rem) 1.5rem;
}

/* =============================================================================
   Logo Cloud Section
   ============================================================================= */

.dz-section-logo-cloud {
  padding: var(--dz-spacing-section-y, 5rem) 1.5rem;
  background: var(--dz-section-alt-bg, oklch(0.97 0.01 260));
  text-align: center;
}

/* =============================================================================
   Comparison Section
   ============================================================================= */

.dz-section-comparison {
  padding: var(--dz-spacing-section-y, 5rem) 1.5rem;
}

.dz-comparison-wrapper {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}

.dz-comparison-table {
  width: 100%;
  border-collapse: collapse;
  text-align: center;
}

.dz-comparison-table th,
.dz-comparison-table td {
  padding: 0.75rem 1rem;
  border-bottom: 1px solid var(--dz-border-subtle);
}

.dz-comparison-table thead th {
  font-weight: 600;
  font-size: var(--text-base);
  padding-bottom: 1rem;
}

.dz-comparison-feature {
  text-align: left;
  font-weight: 500;
}

.dz-comparison-highlighted {
  background: var(--dz-feature-icon-bg, oklch(0.92 0.03 260));
}

/* =============================================================================
   Value Highlight Section
   ============================================================================= */

.dz-section-value-highlight {
  padding: var(--dz-spacing-hero-y, 6rem) 1.5rem;
  text-align: center;
  background: var(--dz-section-alt-bg, oklch(0.97 0.01 260));
}

.dz-value-headline {
  font-size: var(--dz-font-size-hero-headline, 3.5rem);
  font-weight: var(--dz-font-weight-hero-headline, 800);
  line-height: var(--dz-line-height-hero-headline, 1.1);
  letter-spacing: var(--dz-letter-spacing-hero-headline, -0.02em);
}

.dz-value-body {
  max-width: 40rem;
  margin: 0 auto 2rem;
  font-size: var(--text-base);
  line-height: 1.6;
  color: var(--dz-text-body);
}

/* =============================================================================
   Split Content Section
   ============================================================================= */

.dz-section-split-content {
  padding: var(--dz-spacing-section-y, 5rem) 1.5rem;
}

.dz-split-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 3rem;
  align-items: center;
}

.dz-split--reversed .dz-split-grid {
  direction: rtl;
}

.dz-split--reversed .dz-split-grid > * {
  direction: ltr;
}

.dz-split-text h2 {
  text-align: left;
}

.dz-split-text p {
  color: var(--dz-text-body);
}

.dz-cta-group--left {
  justify-content: flex-start;
}

.dz-split-media img {
  width: 100%;
  height: auto;
  border-radius: var(--dz-radius-lg, 1rem);
  box-shadow: var(--dz-shadow-card, 0 1px 3px oklch(0 0 0 / 0.1));
}

@media (max-width: 768px) {
  .dz-split-grid {
    grid-template-columns: 1fr;
  }

  .dz-split--reversed .dz-split-grid {
    direction: ltr;
  }

  .dz-split-text h2 {
    text-align: center;
  }

  .dz-cta-group--left {
    justify-content: center;
  }
}

/* =============================================================================
   Card Grid Section
   ============================================================================= */

.dz-section-card-grid {
  padding: var(--dz-spacing-section-y, 5rem) 1.5rem;
  background: var(--dz-section-alt-bg, oklch(0.97 0.01 260));
}

.dz-card-grid {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 1.5rem;
}

.dz-card-grid > * {
  flex: 0 1 320px;   /* balanced: ~3-up, incomplete rows centre (#1565) */
}

.dz-card-grid:has(> :nth-child(4):last-child) > * {
  flex-basis: calc(50% - 0.75rem);
  max-width: 26rem;
}

.dz-card-item {
  background: var(--dz-surface-card);
  padding: var(--dz-spacing-card-padding, 1.5rem);
  border: var(--dz-card-border);
  border-radius: var(--dz-radius-card, 0.75rem);
  box-shadow: var(--dz-shadow-card);
  display: flex;
  flex-direction: column;
  transition: transform var(--dz-transition-fast, 150ms),
              box-shadow var(--dz-transition-fast, 150ms),
              border-color var(--dz-transition-fast, 150ms);
}

.dz-card-item:hover {
  transform: translateY(-2px);
  box-shadow: var(--dz-card-shadow-hover);
  border-color: color-mix(in oklab, var(--dz-hero-bg-from) 35%, transparent);
}

.dz-card-item h3 {
  font-size: var(--text-base);
  font-weight: 600;
  margin: 0.5rem 0;
}

.dz-card-item p {
  color: var(--dz-text-body);
  margin: 0 0 1rem;
  line-height: var(--dz-line-height-body, 1.6);
  flex: 1;
}

.dz-card-icon {
  width: 3rem;
  height: 3rem;
  background: var(--dz-feature-icon-bg, oklch(0.92 0.03 260));
  border-radius: 0.625rem;
  box-shadow: var(--dz-card-icon-ring);
  display: flex;
  align-items: center;
  justify-content: center;
  margin-bottom: 1rem;
  color: var(--dz-hero-bg-from);
}

.dz-card-icon svg {
  width: 1.5rem;
  height: 1.5rem;
}

/* =============================================================================
   Trust Bar Section
   ============================================================================= */

.dz-section-trust-bar {
  padding: 2rem 1.5rem;
  background: var(--dz-section-alt-bg, oklch(0.97 0.01 260));
  border-top: 1px solid var(--dz-border-subtle);
  border-bottom: 1px solid var(--dz-border-subtle);
}

.dz-trust-strip {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 2rem;
  align-items: center;
}

.dz-trust-item {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: var(--text-sm);
  font-weight: 500;
  color: var(--dz-text-body);
}

.dz-trust-item svg {
  width: 1.25rem;
  height: 1.25rem;
  color: var(--dz-hero-bg-from);
}

/* =============================================================================
   Team Section
   ============================================================================= */

.dz-section-team {
  padding: var(--dz-spacing-section-y, 5rem) 1.5rem;
}

.dz-team-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 2rem;
  max-width: 64rem;
  margin: 0 auto;
}

.dz-team-card {
  text-align: center;
  padding: 2rem 1.5rem;
}

.dz-team-avatar {
  width: 6rem;
  height: 6rem;
  border-radius: 50%;
  margin: 0 auto 1rem;
  overflow: hidden;
  background: linear-gradient(135deg, var(--dz-hero-bg-from) 0%, var(--dz-hero-bg-to) 100%);
  display: flex;
  align-items: center;
  justify-content: center;
}

.dz-team-avatar img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.dz-team-initials {
  font-size: var(--text-xl);
  font-weight: 600;
  color: white;
  letter-spacing: 0.05em;
}

.dz-team-name {
  font-size: var(--text-base);
  font-weight: 600;
  margin: 0 0 0.25rem;
}

.dz-team-role {
  font-size: var(--text-sm);
  font-weight: 500;
  color: var(--dz-hero-bg-from);
  margin: 0 0 0.75rem;
}

.dz-team-bio {
  font-size: var(--text-sm);
  line-height: 1.6;
  color: var(--dz-text-body);
  margin: 0 0 1rem;
}

.dz-team-links {
  display: flex;
  gap: 0.75rem;
  justify-content: center;
}

.dz-team-link {
  color: var(--dz-text-body);
  transition: color var(--dz-transition-fast, 150ms);
}

.dz-team-link:hover {
  color: var(--dz-hero-bg-from);
}

.dz-team-link svg {
  width: 1.25rem;
  height: 1.25rem;
}

/* =============================================================================
   Dark Mode Adjustments
   ============================================================================= */

/* Site Header — dark mode handled by token overrides in design-system.css */

/* Theme Toggle Button */
.dz-theme-toggle {
  background: transparent;
  border: none;
  cursor: pointer;
  padding: 0.5rem;
  border-radius: 0.375rem;
  color: var(--dz-text-body);
  transition: color var(--dz-transition-fast, 150ms), background var(--dz-transition-fast, 150ms);
  display: flex;
  align-items: center;
  justify-content: center;
}

.dz-theme-toggle:hover {
  color: var(--dz-text-foreground);
  background: var(--dz-border-subtle);
}

/* Theme toggle dark mode handled by token overrides in design-system.css */

.dz-theme-toggle__icon {
  width: 1.25rem;
  height: 1.25rem;
}

/* Show sun icon by default (for dark mode), hide moon */
.dz-theme-toggle__sun { display: none; }
.dz-theme-toggle__moon { display: block; }

/* In dark mode, show sun, hide moon */
[data-theme="dark"] .dz-theme-toggle__sun { display: block; }
[data-theme="dark"] .dz-theme-toggle__moon { display: none; }

/* =============================================================================
   Section Background Utilities
   Applied via sitespec background: alt | primary | dark
   ============================================================================= */

.dz-bg-alt {
  background: var(--dz-section-alt-bg, oklch(0.97 0.01 260));
}

.dz-bg-primary {
  background: linear-gradient(135deg, var(--dz-hero-bg-from) 0%, var(--dz-hero-bg-to) 100%);
  color: white;
}

.dz-bg-primary .dz-section-header h2,
.dz-bg-primary .dz-section-header p {
  color: white;
}

.dz-bg-dark {
  background: oklch(0.15 0.01 260);
  color: oklch(0.9 0 0);
}

.dz-bg-dark .dz-section-header h2,
.dz-bg-dark .dz-section-header p {
  color: oklch(0.9 0 0);
}

[data-theme="dark"] .dz-bg-alt {
  background: oklch(0.12 0.01 260);
}

/* Section backgrounds */
[data-theme="dark"] .dz-section-features,
[data-theme="dark"] .dz-section-faq,
[data-theme="dark"] .dz-section-steps {
  background: oklch(0.12 0.01 260);
}

[data-theme="dark"] .dz-section-pricing,
[data-theme="dark"] .dz-section-testimonials,
[data-theme="dark"] .dz-section-logo-cloud,
[data-theme="dark"] .dz-section-cta,
[data-theme="dark"] .dz-section-value-highlight,
[data-theme="dark"] .dz-section-card-grid,
[data-theme="dark"] .dz-section-trust-bar {
  background: oklch(0.08 0.01 260);
}

[data-theme="dark"] .dz-section-comparison,
[data-theme="dark"] .dz-section-split-content {
  background: oklch(0.12 0.01 260);
}

/* Cards and items */
[data-theme="dark"] .dz-feature-item,
[data-theme="dark"] .dz-testimonial-item,
[data-theme="dark"] .dz-card-item,
[data-theme="dark"] .dz-pricing-tier:not(.dz-pricing-highlighted) {
  background: oklch(0.18 0.01 260);
}

/* Comparison table */
[data-theme="dark"] .dz-comparison-table th,
[data-theme="dark"] .dz-comparison-table td {
  border-color: oklch(1 0 0 / 0.1);
}

[data-theme="dark"] .dz-comparison-highlighted {
  background: oklch(0.18 0.03 260);
}

/* Headings */
[data-theme="dark"] .dz-section h2,
[data-theme="dark"] .dz-feature-item h3,
[data-theme="dark"] .dz-step-item h3,
[data-theme="dark"] .dz-faq-item summary,
[data-theme="dark"] .dz-pricing-tier h3 {
  color: oklch(0.95 0 0);
}

/* Body text */
[data-theme="dark"] .dz-feature-item p,
[data-theme="dark"] .dz-step-item p,
[data-theme="dark"] .dz-testimonial-item blockquote,
[data-theme="dark"] .dz-faq-item p {
  color: oklch(0.65 0 0);
}

/* FAQ borders */
[data-theme="dark"] .dz-faq-item {
  border-color: oklch(1 0 0 / 0.1);
}

/* Prose/markdown content */
[data-theme="dark"] .dz-prose {
  color: oklch(0.85 0 0);
}

[data-theme="dark"] .dz-prose h1,
[data-theme="dark"] .dz-prose h2,
[data-theme="dark"] .dz-prose h3 {
  color: oklch(0.95 0 0);
}

/* Logo cloud */
[data-theme="dark"] .dz-logo-item img {
  filter: grayscale(100%) invert(1);
  opacity: 0.5;
}

[data-theme="dark"] .dz-logo-item:hover img {
  filter: grayscale(0%) invert(1);
  opacity: 0.8;
}

/* Buttons - works with both DaisyUI btn and legacy dz-btn classes */
[data-theme="dark"] .dz-btn-secondary,
[data-theme="dark"] .btn-outline {
  color: oklch(0.85 0 0);
  border-color: oklch(0.85 0 0);
}

[data-theme="dark"] .dz-btn-secondary:hover,
[data-theme="dark"] .btn-outline:hover {
  background: oklch(1 0 0 / 0.1);
}

/* =============================================================================
   Responsive Adjustments
   ============================================================================= */

@media (max-width: 768px) {
  .dz-section h1 {
    font-size: var(--text-3xl);
  }

  .dz-section h2 {
    font-size: var(--text-2xl);
  }

  .dz-section-markdown h2,
  .dz-section .prose h2 {
    font-size: var(--text-lg);
  }

  .dz-section-markdown h1,
  .dz-section .prose h1 {
    font-size: var(--text-2xl);
  }

  .dz-section-cta h2 {
    font-size: var(--text-2xl);
  }

  .dz-cta-group {
    flex-direction: column;
    align-items: center;
  }

  .dz-pricing-highlighted {
    transform: none;
  }

  .dz-stat-value {
    font-size: var(--text-2xl);
  }
}

/* =============================================================================
   Site Layout - Header, Footer, Nav
   ============================================================================= */

.dz-site {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}

/* Default body background — overridden per page via {% block body_class %}.
 * Was inline `bg-[hsl(var(--card))]` Tailwind on <body> in site_base.html;
 * v0.62 moves to a semantic class rule. */
.dz-site-bg {
  background: var(--dz-surface-card, var(--colour-surface));
}

.dz-site-header {
  position: sticky;
  top: 0;
  z-index: 100;
  background: var(--dz-header-bg);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--dz-border-subtle);
}

.dz-site-nav {
  max-width: 72rem;
  margin: 0 auto;
  padding: 1rem 1.5rem;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 2rem;
}

.dz-site-logo {
  font-size: var(--text-lg);
  font-weight: 700;
  color: var(--dz-logo-color);
  text-decoration: none;
}

.dz-nav-items {
  display: flex;
  align-items: center;
  gap: 1.5rem;
}

.dz-nav-link {
  font-size: var(--text-sm);
  font-weight: 500;
  color: var(--dz-text-body);
  text-decoration: none;
  transition: color var(--dz-transition-fast, 150ms);
}

.dz-nav-link:hover {
  color: var(--dz-text-foreground);
}

.dz-nav-cta {
  font-size: var(--text-sm);
  font-weight: 600;
  color: white;
  background: var(--dz-hero-bg-from, oklch(0.50 0.18 270));
  padding: 0.5rem 1rem;
  border-radius: var(--dz-radius-button, 0.375rem);
  text-decoration: none;
  transition: background var(--dz-transition-fast, 150ms);
}

.dz-nav-cta:hover {
  background: var(--dz-hero-bg-to, oklch(0.45 0.18 270));
}

.dz-site-footer {
  margin-top: auto;
  background: var(--dz-footer-bg);
  color: var(--dz-footer-text);
  padding: 3rem 1.5rem 1.5rem;
}

.dz-footer-content {
  max-width: 72rem;
  margin: 0 auto;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 2rem;
  margin-bottom: 2rem;
}

.dz-footer-col h4 {
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--dz-footer-heading);
  margin: 0 0 1rem;
}

.dz-footer-col ul {
  list-style: none;
  padding: 0;
  margin: 0;
}

.dz-footer-col li {
  margin-bottom: 0.5rem;
}

.dz-footer-col a {
  font-size: var(--text-sm);
  color: var(--dz-footer-link);
  text-decoration: none;
  transition: color var(--dz-transition-fast, 150ms);
}

.dz-footer-col a:hover {
  color: var(--dz-footer-heading);
}

.dz-footer-bottom {
  max-width: 72rem;
  margin: 0 auto;
  padding-top: 1.5rem;
  border-top: 1px solid var(--dz-footer-border);
  text-align: center;
}

.dz-footer-bottom p {
  font-size: var(--text-xs);
  margin: 0;
}

/* =============================================================================
   Button Styles for Site Pages

   NOTE: Most button styling is now handled by DaisyUI btn classes (btn, btn-primary, etc.)
   These are kept for backwards compatibility and site-specific overrides.
   ============================================================================= */

/* Hero section CTA buttons need special styling to stand out on the gradient.
   Buttons render as `.dz-button[data-dz-variant]` (HM button component); these
   hero-context rules win on specificity to make the primary pop and the
   secondary a legible ghost. */
.dz-section-hero .dz-button[data-dz-variant="primary"] {
  background: var(--dz-cta-fill);
  color: var(--dz-cta-text);
  border: none;
  font-weight: 700;
  font-size: 1.0625rem;
  padding: 0.875rem 1.75rem;
  border-radius: var(--dz-radius-button, 0.5rem);
  box-shadow: var(--dz-cta-shadow);
  transition: transform var(--dz-transition-fast, 150ms), box-shadow var(--dz-transition-fast, 150ms),
              background var(--dz-transition-fast, 150ms);
}

.dz-section-hero .dz-button[data-dz-variant="primary"]:hover {
  background: var(--dz-cta-fill-hover);
  box-shadow: var(--dz-cta-shadow-hover);
  transform: translateY(-1px);
}

.dz-section-hero .dz-button[data-dz-variant="outline"],
.dz-section-hero .dz-button[data-dz-variant="secondary"] {
  background: transparent;
  color: white;
  border: 1.5px solid var(--dz-hero-border, oklch(1 0 0 / 0.5));
  font-weight: 600;
  font-size: 1.0625rem;
  padding: 0.875rem 1.75rem;
  border-radius: var(--dz-radius-button, 0.5rem);
  transition: background var(--dz-transition-fast, 150ms), border-color var(--dz-transition-fast, 150ms);
}

.dz-section-hero .dz-button[data-dz-variant="outline"]:hover,
.dz-section-hero .dz-button[data-dz-variant="secondary"]:hover {
  background: var(--dz-hero-overlay);
  border-color: var(--dz-hero-border-hover);
  color: white;
}

/* =============================================================================
   Site Page Sections (JS-rendered)

   These rules target the HTML structure generated by get_site_js().
   They use design-system tokens so themes propagate correctly.
   ============================================================================= */

.dz-section {
  width: 100%;
}

.dz-section-content {
  max-width: 72rem;
  margin: 0 auto;
  padding: 0 1.5rem;
}

/* --- Section header (renderSectionHeader helper) --- */
.dz-section-header {
  text-align: center;
  max-width: 40rem;
  margin: 0 auto 3rem;
}

.dz-section-media {
  text-align: center;
  margin: 0 auto 2rem;
  max-width: 48rem;
}

.dz-section-media img {
  max-width: 100%;
  height: auto;
  border-radius: 0.5rem;
}

/* --- Generic section typography --- */
.dz-section h1 {
  font-size: var(--dz-font-size-hero-headline, 3.5rem);
  font-weight: var(--dz-font-weight-hero-headline, 800);
  line-height: var(--dz-line-height-hero-headline, 1.1);
  letter-spacing: var(--dz-letter-spacing-hero-headline, -0.02em);
  margin: 0 0 1.5rem;
}

.dz-section h2 {
  font-size: var(--dz-font-size-section-headline, 2.25rem);
  font-weight: var(--dz-font-weight-section-headline, 700);
  line-height: var(--dz-line-height-section-headline, 1.2);
  letter-spacing: var(--dz-letter-spacing-section-headline, -0.01em);
  margin: 0 0 2rem;
  text-align: center;
}

.dz-section h3 {
  font-size: var(--text-base);
  font-weight: 600;
  margin: 0.5rem 0;
}

.dz-section p {
  font-size: var(--dz-font-size-body, 1rem);
  line-height: var(--dz-line-height-body, 1.6);
}

/* --- Article body / markdown typography ---
   .dz-section h1/h2/h3/p above target marketing *section headlines*
   (centred, large). Markdown bodies and any .prose content inside a
   section are article copy and must opt out of the headline treatment
   — otherwise blog-post H2s render centred and oversized (issue #921). */
.dz-section-markdown h1,
.dz-section-markdown h2,
.dz-section-markdown h3,
.dz-section-markdown h4,
.dz-section-markdown p,
.dz-section .prose h1,
.dz-section .prose h2,
.dz-section .prose h3,
.dz-section .prose h4,
.dz-section .prose p {
  text-align: start;
  letter-spacing: normal;
}

.dz-section-markdown h1,
.dz-section .prose h1 {
  font-size: var(--text-2xl);
  font-weight: 700;
  line-height: 1.25;
  margin: 2.5rem 0 1rem;
}

.dz-section-markdown h2,
.dz-section .prose h2 {
  font-size: var(--text-xl);
  font-weight: 600;
  line-height: 1.3;
  margin: 2rem 0 0.75rem;
}

.dz-section-markdown h3,
.dz-section .prose h3 {
  font-size: var(--text-lg);
  font-weight: 600;
  line-height: 1.4;
  margin: 1.5rem 0 0.5rem;
}

.dz-section-markdown h4,
.dz-section .prose h4 {
  font-size: var(--text-base);
  font-weight: 600;
  line-height: 1.4;
  margin: 1.25rem 0 0.5rem;
}

.dz-section-markdown p,
.dz-section .prose p {
  font-size: var(--dz-font-size-body, 1rem);
  line-height: 1.7;
  margin: 0 0 1em;
}

/* --- 404 page --- */
.dz-404-section {
  text-align: center;
  padding: var(--dz-spacing-hero-y, 6rem) 1.5rem;
}

.dz-404-headline {
  font-size: var(--dz-font-size-hero-headline, 3.5rem);
  font-weight: var(--dz-font-weight-hero-headline, 800);
  line-height: var(--dz-line-height-hero-headline, 1.1);
  margin-bottom: 1rem;
}

.dz-404-cta {
  margin-top: 2rem;
}

/* --- Pricing tier description --- */
.dz-pricing-description {
  color: var(--dz-text-muted);
  margin: 0 0 1.5rem;
  font-size: var(--dz-font-size-body, 1rem);
  line-height: var(--dz-line-height-body, 1.6);
}

/* --- Markdown page section --- */
.dz-section-markdown {
  padding: var(--dz-spacing-section-y, 5rem) 1.5rem;
}

.dz-section-markdown .prose hr {
  border: none;
  height: 1px;
  background: linear-gradient(to right, transparent, var(--dz-border-subtle), transparent);
  margin: 3rem auto;
  max-width: 200px;
}

.dz-subhead {
  font-size: var(--text-lg);
  line-height: 1.6;
  opacity: 0.9;
  margin: 0 0 2rem;
}

.dz-cta-group {
  display: flex;
  gap: 1rem;
  justify-content: center;
  flex-wrap: wrap;
}

/* Balanced feature/testimonial grid — flex-wrap + center so any card count reads
   intentional: max ~3 per row (cards don't grow), and incomplete trailing rows
   CENTER instead of left-orphaning (#1565 — a 5th card no longer hangs alone). */
.dz-features-grid,
.dz-testimonials-grid {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 1.5rem;
}

.dz-features-grid > *,
.dz-testimonials-grid > * {
  flex: 0 1 320px;   /* fixed basis, no grow → wraps at ~3-up, orphans centre */
}

/* Exactly 4 → a balanced 2×2 rather than a lone centred card. */
.dz-features-grid:has(> :nth-child(4):last-child) > *,
.dz-testimonials-grid:has(> :nth-child(4):last-child) > * {
  flex-basis: calc(50% - 0.75rem);
  max-width: 26rem;
}

.dz-pricing-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 1.5rem;
}

.dz-feature-item,
.dz-testimonial-item {
  background: var(--dz-surface-card);
  padding: var(--dz-spacing-card-padding, 1.5rem);
  border: var(--dz-card-border);
  border-radius: var(--dz-radius-card, 0.75rem);
  box-shadow: var(--dz-shadow-card);
  transition: transform var(--dz-transition-fast, 150ms), box-shadow var(--dz-transition-fast, 150ms),
              border-color var(--dz-transition-fast, 150ms);
}

.dz-feature-item:hover {
  transform: translateY(-2px);
  box-shadow: var(--dz-card-shadow-hover);
  border-color: color-mix(in oklab, var(--dz-hero-bg-from) 35%, transparent);
}

.dz-feature-item h3 {
  font-size: var(--text-base);
  font-weight: 600;
  margin: 0.5rem 0;
}

.dz-feature-item p {
  color: var(--dz-text-body);
  margin: 0;
  line-height: var(--dz-line-height-body, 1.6);
}

.dz-feature-icon {
  width: 3rem;
  height: 3rem;
  background: var(--dz-feature-icon-bg, oklch(0.92 0.03 260));
  border-radius: 0.625rem;
  box-shadow: var(--dz-card-icon-ring);
  display: flex;
  align-items: center;
  justify-content: center;
  margin-bottom: 1rem;
  color: var(--dz-hero-bg-from);
}

.dz-feature-icon svg {
  width: 1.5rem;
  height: 1.5rem;
}

.dz-faq-list {
  max-width: 48rem;
  margin: 0 auto;
}

.dz-faq-item {
  border-bottom: 1px solid var(--dz-border-default);
  padding: 1rem 0;
}

.dz-faq-item summary {
  font-weight: 600;
  cursor: pointer;
  list-style: none;
}

.dz-faq-item summary::-webkit-details-marker {
  display: none;
}

.dz-faq-item p {
  margin: 0.75rem 0 0;
  color: var(--dz-text-body);
  line-height: var(--dz-line-height-body, 1.6);
}

/* #1113 — replaces DaisyUI's `stats stats-vertical lg:stats-horizontal
 * shadow` grid emitted by `site_section_builder._build_stats_section`.
 * Vertical stack on narrow viewports, horizontal row from --breakpoint-lg
 * upward. Tokens-only, no hardcoded values. */
.dz-stats-grid {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  padding: var(--space-md);
  background: var(--colour-surface);
  color: var(--colour-text);
  border: 1px solid var(--colour-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-sm);
  text-align: left;
}

@media (min-width: 64rem) {
  .dz-stats-grid {
    flex-direction: row;
    align-items: stretch;
    justify-content: space-around;
    gap: var(--space-lg);
  }
}

.dz-stat-item {
  padding: var(--space-md);
}

.dz-stat-value {
  font-size: var(--text-2xl);
  font-weight: var(--weight-semibold);
  line-height: var(--leading-tight);
  color: var(--colour-text);
  display: block;
  font-variant-numeric: tabular-nums;
}

.dz-stat-label {
  font-size: var(--text-sm);
  color: var(--colour-text-muted);
}

/* List wrapper for the marketing steps section. Mobile stacks
 * vertically; desktop becomes a horizontal flex row. Distinct from
 * the framework's `.dz-steps` rule (in components/fragments.css)
 * which is used by the in-app step indicator. */
.dz-section-steps-list {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 1rem;
  list-style: none;
  padding: 0;
  margin: 0;
  width: 100%;
}

@media (min-width: 1024px) {
  .dz-section-steps-list {
    flex-direction: row;
    align-items: center;
    gap: 0;
  }
}

/* Connector line between marketing-steps cards on desktop. */
.dz-step-connector {
  display: none;
}

@media (min-width: 1024px) {
  .dz-step-connector {
    display: block;
    flex: 1;
    margin: 0 0.75rem;
    height: 1px;
    background: var(--dz-border-default, var(--colour-border));
  }
}

/* Mobile: vertical steps — number beside text in a row */
.dz-step-item {
  display: flex;
  gap: 1rem;
  align-items: flex-start;
}

.dz-step-item.is-not-last {
  flex: 1;
}

.dz-step-number {
  width: 2.5rem;
  height: 2.5rem;
  background: var(--dz-hero-bg-from);
  color: white;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 700;
  font-size: var(--text-base);
  flex-shrink: 0;
}

.dz-step-content h3 {
  font-size: var(--text-base);
  font-weight: 600;
  margin: 0 0 0.25rem;
}

.dz-step-content p {
  color: var(--dz-text-body);
  margin: 0;
  line-height: var(--dz-line-height-body, 1.6);
  font-size: var(--text-sm);
}

/* Desktop: horizontal steps — stack number above text in a column */
@media (min-width: 1024px) {
  .dz-step-item {
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: 0.75rem;
  }

  .dz-step-number {
    width: 3rem;
    height: 3rem;
    font-size: var(--text-lg);
  }

  .dz-step-content h3 {
    font-size: var(--text-lg);
    margin: 0 0 0.5rem;
  }

  .dz-step-content p {
    font-size: var(--text-sm);
  }
}

.dz-logos-grid {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 3rem;
  align-items: center;
}

.dz-logo-item img {
  height: 2rem;
  opacity: 0.6;
  filter: grayscale(100%);
  transition: all var(--duration-fast, 150ms);
}

.dz-logo-item:hover img {
  opacity: 1;
  filter: grayscale(0%);
}

.dz-pricing-tier {
  background: var(--dz-surface-card);
  padding: 2rem;
  border-radius: var(--dz-radius-card, 0.75rem);
  box-shadow: var(--dz-shadow-card, 0 1px 3px oklch(0 0 0 / 0.1));
  display: flex;
  flex-direction: column;
  transition: transform var(--dz-transition-fast, 150ms);
}

.dz-pricing-tier:hover:not(.dz-pricing-highlighted) {
  transform: translateY(-4px);
}

.dz-pricing-tier h3 {
  font-size: var(--text-lg);
  font-weight: 600;
  margin: 0 0 0.5rem;
}

.dz-pricing-price {
  margin: 0 0 1.5rem;
}

.dz-price {
  font-size: var(--text-3xl);
  font-weight: 700;
}

.dz-period {
  font-size: var(--text-sm);
  color: var(--dz-text-muted);
}

.dz-pricing-features {
  list-style: none;
  padding: 0;
  margin: 0 0 1.5rem;
  flex: 1;
}

.dz-pricing-features li {
  padding: 0.5rem 0;
  border-bottom: 1px solid var(--dz-border-subtle);
}

.dz-pricing-highlighted {
  background: var(--dz-pricing-highlight-bg, var(--dz-hero-bg-from));
  color: white;
  transform: scale(1.05);
  box-shadow: var(--dz-shadow-lg);
}

.dz-pricing-highlighted .dz-period,
.dz-pricing-highlighted li {
  color: var(--dz-hero-text-dimmed);
  border-color: var(--dz-hero-border-dim);
}

.dz-testimonial-item:hover {
  transform: translateY(-4px);
  box-shadow: var(--dz-shadow-lg, 0 10px 15px -3px oklch(0 0 0 / 0.1));
}

.dz-testimonial-item blockquote {
  font-style: italic;
  color: var(--dz-text-emphasis);
  margin: 0 0 1rem;
  line-height: var(--dz-line-height-body, 1.6);
}

.dz-testimonial-author {
  display: flex;
  align-items: center;
  gap: 0.75rem;
}

.dz-testimonial-author strong {
  display: block;
  font-size: var(--text-sm);
}

.dz-testimonial-author span {
  display: block;
  font-size: var(--text-xs);
  color: var(--dz-text-muted);
}

.dz-prose {
  max-width: 48rem;
  margin: 0 auto;
  padding: 3rem 1.5rem;
}

.dz-prose h1, .dz-prose h2, .dz-prose h3 {
  text-align: left;
}

/* --- Legal page styling (terms, privacy) --- */
.dz-page-legal .dz-section-markdown .prose {
  max-width: 45rem;
  margin: 0 auto;
  padding: 2rem 1.5rem;
}

.dz-page-legal .dz-section-markdown .prose h1,
.dz-page-legal .dz-section-markdown .prose h2,
.dz-page-legal .dz-section-markdown .prose h3 {
  text-align: left;
}

.dz-page-legal .dz-section-markdown .prose p {
  line-height: 1.7;
}

.dz-page-legal .dz-section-markdown .prose h2 {
  margin-top: 2em;
  margin-bottom: 0.75em;
  font-size: var(--text-lg);
  font-weight: 600;
}

.dz-page-legal .dz-section-markdown .prose h3 {
  margin-top: 1.5em;
  margin-bottom: 0.5em;
  font-size: var(--text-base);
  font-weight: 600;
}

/* --- Task surface container (human task workflow pages) --- */
.dz-task-surface-container {
  max-width: 75rem;
  margin: 0 auto;
  padding: var(--dz-spacing-card-padding, 1.5rem);
}

.dz-loading {
  text-align: center;
  padding: 4rem;
  color: var(--dz-text-muted);
}

.dz-error {
  text-align: center;
  padding: 2rem;
  color: var(--dz-error-text);
}

/* ============================================================= */
/* Consent banner (v0.61.0 Phase 2 — Analytics, Consent & Privacy) */
/* ============================================================= */

.dz-consent-banner {
  position: fixed;
  inset: auto 0 0 0;
  z-index: 1000;
  padding: 1.25rem 1rem;
  background: var(--dz-bg-card, var(--colour-surface));
  border-top: 1px solid var(--dz-border, var(--colour-border));
  box-shadow: 0 -4px 24px rgba(0, 0, 0, 0.08);
}

.dz-consent-panel {
  max-width: 64rem;
  margin: 0 auto;
  color: var(--dz-text-primary, var(--colour-text));
  font-size: var(--text-sm);
  line-height: 1.5;
}

.dz-consent-title {
  margin: 0 0 0.5rem 0;
  font-size: var(--text-base);
  font-weight: 600;
}

.dz-consent-description {
  margin: 0 0 1rem 0;
  color: var(--dz-text-muted, var(--colour-text-muted));
  font-size: var(--text-sm);
}

.dz-consent-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  align-items: center;
  margin-top: 1rem;
}

.dz-consent-btn {
  appearance: none;
  border: 1px solid transparent;
  background: transparent;
  color: inherit;
  padding: 0.5rem 1rem;
  font: inherit;
  font-size: var(--text-sm);
  font-weight: 500;
  cursor: pointer;
  border-radius: 0.375rem;
  transition:
    background-color 0.15s ease,
    border-color 0.15s ease;
}

.dz-consent-btn:focus-visible {
  outline: 2px solid var(--dz-focus-ring, var(--colour-brand));
  outline-offset: 2px;
}

.dz-consent-btn-primary {
  background: var(--dz-primary, var(--colour-brand));
  color: var(--dz-primary-foreground, var(--colour-brand-contrast));
}

.dz-consent-btn-primary:hover {
  filter: brightness(1.05);
}

.dz-consent-btn-secondary {
  border-color: var(--dz-border, var(--colour-border));
  background: var(--dz-bg-card, var(--colour-surface));
}

.dz-consent-btn-secondary:hover {
  background: var(--dz-bg-hover, var(--colour-surface-muted));
}

.dz-consent-btn-link {
  text-decoration: underline;
  text-underline-offset: 2px;
  padding: 0.5rem 0;
  color: var(--dz-text-link, var(--colour-brand));
}

.dz-consent-btn-link:hover {
  text-decoration-thickness: 2px;
}

.dz-consent-customize {
  margin-top: 1rem;
  padding-top: 1rem;
  border-top: 1px solid var(--dz-border, var(--colour-border));
}

.dz-consent-category {
  border: 0;
  padding: 0.5rem 0;
  margin: 0;
}

.dz-consent-category + .dz-consent-category {
  border-top: 1px solid var(--dz-border, var(--colour-border));
}

.dz-consent-category label {
  display: inline-flex;
  gap: 0.5rem;
  align-items: center;
  font-weight: 500;
  cursor: pointer;
}

.dz-consent-category input[type="checkbox"]:disabled + .dz-consent-category-label {
  color: var(--dz-text-muted, var(--colour-text-muted));
  cursor: not-allowed;
}

.dz-consent-category-hint {
  margin: 0.25rem 0 0 1.5rem;
  color: var(--dz-text-muted, var(--colour-text-muted));
  font-size: var(--text-xs);
}

.dz-consent-links {
  margin: 0.75rem 0 0 0;
  font-size: var(--text-xs);
}

.dz-consent-links a {
  color: var(--dz-text-link, var(--colour-brand));
  text-decoration: underline;
  text-underline-offset: 2px;
}

@media (max-width: 640px) {
  .dz-consent-banner {
    padding: 1rem 0.75rem;
  }
  .dz-consent-actions {
    flex-direction: column;
    align-items: stretch;
  }
  .dz-consent-btn {
    width: 100%;
  }
  .dz-consent-btn-link {
    width: auto;
    align-self: center;
  }
}

/* =============================================================================
   #1113 — Per-stat wrapper + native toast. The `.dz-stats-grid` /
   `.dz-stat-value` / `.dz-stat-label` rules above (modernised in the
   same release) cover the section-level layout; `.dz-stat` here is the
   per-item flex column the section-builder emits. `.dz-stat-title` is
   the per-item label kept distinct from `.dz-stat-label` (the latter is
   used by the legacy `dz-stat-item` shape in templated marketing pages).
   Tokens-only — no literal hsl/hex.
   ============================================================================= */

.dz-stat {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-xs);
  padding: var(--space-md) var(--space-lg);
  flex: 1 1 auto;
  min-width: 0;
}

.dz-stat-title {
  font-size: var(--text-sm);
  color: var(--colour-text-muted);
}

/* Toast — replaces `alert alert-{level}` emitted by
 * `response_helpers.append_toast`. Tone keyed off `data-dz-toast-level`,
 * mirroring the `.dz-alert-banner[data-dz-alert-level=…]` pattern in
 * fragments.css. Slide-in/out animation inherited from the existing
 * `.dz-toast-enter` / `.dz-toast-leave` rules in dz.css. */
.dz-toast {
  display: flex;
  align-items: flex-start;
  gap: var(--space-sm);
  padding: var(--space-sm) var(--space-md);
  background: var(--colour-surface);
  color: var(--colour-text);
  border: 1px solid var(--colour-border);
  border-left-width: 4px;
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-md);
  font-size: var(--text-sm);
  line-height: var(--leading-tight);
  max-width: min(28rem, calc(100vw - var(--space-xl) * 2));
}

.dz-toast[data-dz-toast-level="info"] {
  border-left-color: var(--colour-brand);
  background: color-mix(in oklch, var(--colour-brand) 6%, var(--colour-surface));
}

.dz-toast[data-dz-toast-level="success"] {
  border-left-color: var(--colour-success);
  background: color-mix(in oklch, var(--colour-success) 6%, var(--colour-surface));
}

.dz-toast[data-dz-toast-level="warning"] {
  border-left-color: var(--colour-warning);
  background: color-mix(in oklch, var(--colour-warning) 6%, var(--colour-surface));
}

.dz-toast[data-dz-toast-level="error"] {
  border-left-color: var(--colour-danger);
  background: color-mix(in oklch, var(--colour-danger) 6%, var(--colour-surface));
}
/* --- hatchi-maxchi/components/skeleton.css --- */
/* HYPERPART: skeleton */
/*
 * dz-skeleton — loading placeholder. A muted block with a low-contrast
 * sheen sweep derived from the request lifecycle (taste principle 9,
 * TASTE-9): drop it into a swap target while `.htmx-request` is in flight,
 * so the loading state IS the design language, not a spinner bolted on.
 *
 * Shapes via data-dz-shape: `text` (a line of copy), `circle` (avatar),
 * `block` (media / card body). Purely decorative — hide the placeholder
 * from AT (aria-hidden on the region) and announce "loading" on the live
 * region that owns the swap, not here.
 */

.dz-skeleton {
  /* internal tokens (double-dash → never reprefixed, kept out of the API) */
  --dz-skeleton-base: var(--colour-surface-muted);
  --dz-skeleton-sheen: color-mix(in oklab, var(--colour-text) 8%, var(--colour-surface-muted));
  display: block;
  width: 100%;
  border-radius: var(--radius-sm);
  background-color: var(--dz-skeleton-base);
  background-image: linear-gradient(
    90deg,
    transparent 0%,
    var(--dz-skeleton-sheen) 50%,
    transparent 100%
  );
  background-size: 200% 100%;
  background-repeat: no-repeat;
  animation: dz-skeleton-sweep 1.4s var(--ease-out, ease) infinite;
}

.dz-skeleton[data-dz-shape="text"] {
  height: 0.75rem;
  border-radius: var(--radius-full);
}

.dz-skeleton[data-dz-shape="text"]:last-child {
  width: 65%;
}

.dz-skeleton[data-dz-shape="circle"] {
  width: 2.5rem;
  height: 2.5rem;
  flex: none;
  border-radius: var(--radius-full);
}

.dz-skeleton[data-dz-shape="block"] {
  min-height: 4rem;
  border-radius: var(--radius-md);
}

/* A stack of text-line skeletons — the most common placeholder shape
   (a paragraph loading). The last line is shortened by the [data-dz-shape]
   rule above for a natural ragged edge. */
.dz-skeleton-lines {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

@keyframes dz-skeleton-sweep {
  from { background-position: 150% 0; }
  to { background-position: -150% 0; }
}

@media (prefers-reduced-motion: reduce) {
  .dz-skeleton { animation: none; }
}
/* --- hatchi-maxchi/components/sparkline.css --- */
/* HYPERPART: sparkline */
/* Promoted VERBATIM from Dazzle regions.css (Tier C-a, fleet
 * convergence). The Dazzle emitter contract is unchanged. */

/* ─────────────────────────── sparkline region ───────────────────
 * Compact KPI tile: big-number + tiny line. SVG fills the width of
 * its container; geometry comes from the template's own viewBox
 * computation (180×32 grid) so we just size the host. */

.dz-sparkline-headline {
  display: flex;
  align-items: baseline;
  gap: var(--space-sm);
}

.dz-sparkline-value {
  font-size: var(--text-2xl);
  font-weight: var(--weight-semibold);
  color: var(--colour-text);
  line-height: 1;
}

.dz-sparkline-bucket-label {
  font-size: var(--text-xs);
  color: var(--colour-text-muted);
  font-family: var(--font-mono);
}

.dz-sparkline-svg {
  margin-block-start: var(--space-xs);
  width: 100%;
  height: 2rem;
}

.dz-sparkline-empty {
  text-align: center;
  padding-block: var(--space-sm);
  font-size: var(--text-sm);
  color: var(--colour-text-muted);
}
/* --- hatchi-maxchi/components/status-list.css --- */
/* HYPERPART: status-list */
/* Promoted VERBATIM from Dazzle regions.css (W1, fleet convergence).
 * The Dazzle emitter contract predates the move and is unchanged. */

/* ─────────────────────────── status_list region ─────────────────
 * Vertical list of icon + title + caption + state pill rows.
 * Pill colour keys off data-dz-state on the row (positive /
 * warning / destructive / accent / neutral). Pill is omitted for
 * neutral entries — the row reads as plain info rather than a
 * status row. */

.dz-status-list {
  display: flex;
  flex-direction: column;
  list-style: none;
  margin: 0;
  padding-inline-start: 0;
}

.dz-status-list > li + li {
  border-block-start: 1px solid var(--colour-border);
}

.dz-status-list-entry {
  display: flex;
  align-items: flex-start;
  gap: var(--space-md);
  padding-block: var(--space-md);
}

.dz-status-list-entry:first-child {
  padding-block-start: 0.25rem;
}

.dz-status-list-entry:last-child {
  padding-block-end: 0.25rem;
}

.dz-status-list-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.25rem;
  height: 1.25rem;
  margin-block-start: 0.125rem;
  flex-shrink: 0;
}

.dz-status-list-icon-spacer {
  width: 1.25rem;
  flex-shrink: 0;
}

.dz-status-list-text {
  flex: 1 1 0;
  min-width: 0;
}

.dz-status-list-title {
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  line-height: var(--leading-tight);
  color: var(--colour-text);
}

.dz-status-list-caption {
  font-size: var(--text-xs);
  line-height: var(--leading-tight);
  color: var(--colour-text-muted);
  margin-block-start: 0.125rem;
}

.dz-status-list-pill {
  flex-shrink: 0;
  padding-inline: var(--space-sm);
  padding-block: 0.125rem;
  border-radius: var(--radius-full);
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

/* State tints (moved from Dazzle dz-tones.css with the component). */
.dz-status-list-entry[data-dz-state="positive"] .dz-status-list-pill {
  background: var(--colour-success-soft);
  color: var(--colour-success);
}
.dz-status-list-entry[data-dz-state="warning"] .dz-status-list-pill {
  background: var(--colour-warning-soft);
  color: var(--colour-warning);
}
.dz-status-list-entry[data-dz-state="destructive"] .dz-status-list-pill {
  background: var(--colour-danger-soft);
  color: var(--colour-danger);
}
.dz-status-list-entry[data-dz-state="accent"] .dz-status-list-pill {
  background: var(--colour-brand-soft);
  color: var(--colour-brand);
}

/* Icon colour matches the state. Neutral entries fall back to the
 * default muted-foreground. */
.dz-status-list-entry[data-dz-state="positive"] .dz-status-list-icon {
  color: var(--colour-success);
}
.dz-status-list-entry[data-dz-state="warning"] .dz-status-list-icon {
  color: var(--colour-warning);
}
.dz-status-list-entry[data-dz-state="destructive"] .dz-status-list-icon {
  color: var(--colour-danger);
}
.dz-status-list-entry[data-dz-state="accent"] .dz-status-list-icon {
  color: var(--colour-brand);
}
.dz-status-list-entry[data-dz-state="neutral"] .dz-status-list-icon,
.dz-status-list-entry:not([data-dz-state]) .dz-status-list-icon {
  color: var(--colour-text-muted);
}
/* --- hatchi-maxchi/components/table.css --- */
/* HYPERPART: grid */
/* ─────────────────────────── outer wrapper ──────────────────────
 * Table card root — vertical flow of header / toolbar / scroll
 * container / pagination footer. */

.dz-table {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  /* TASTE-5: tabular numerals keep data columns optically aligned (Geist
     ships tnum); harmless no-op on fallback stacks without the feature. */
  font-variant-numeric: tabular-nums;
}

/* ─────────────────────────── toolbar row ────────────────────────
 * search input / filter chips / bulk actions. Wraps on narrow
 * containers. */

.dz-table-toolbar {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  flex-wrap: wrap;
}

.dz-table-toolbar-filters {
  flex: 1 1 0;
  min-width: 0;
}

/* ─────────────────────────── column visibility menu ─────────────
 * A native <details> disclosure (the .dz-menu idiom — no open/close
 * JS); the toggles ride the delegated dz-grid-cols extension (C2.1). */

.dz-table-col-menu {
  position: relative;
}

/* The trigger is the <summary> — strip the UA disclosure marker so the
 * existing button chrome applies cleanly. */
summary.dz-table-col-menu-trigger {
  list-style: none;
}
summary.dz-table-col-menu-trigger::-webkit-details-marker {
  display: none;
}

.dz-table-col-menu-trigger {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
  height: 1.75rem;
  padding-inline: var(--space-sm);
  border-radius: var(--radius-sm);
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  color: var(--colour-text-muted);
  background: var(--colour-bg);
  border: 1px solid var(--colour-border);
  cursor: pointer;
  user-select: none;
  transition: background var(--duration-fast) var(--ease-out);
}

.dz-table-col-menu-trigger:hover {
  background: var(--colour-surface);
}

.dz-table-col-menu-panel {
  position: absolute;
  right: 0;
  top: 100%;
  margin-block-start: var(--space-xs);
  width: 11rem;
  z-index: 100;
  background: var(--colour-surface);
  border: 1px solid var(--colour-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
  padding-block: var(--space-xs);
  overflow: hidden;
}

.dz-table-col-menu-item {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  padding-inline: var(--space-md);
  padding-block: var(--space-xs);
  cursor: pointer;
  font-size: var(--text-sm);
  color: var(--colour-text);
  transition: background var(--duration-fast) var(--ease-out);
}

.dz-table-col-menu-item:hover {
  background: var(--colour-bg);
}

/* #853 escape hatch: the "Show all columns" reset at the panel's foot. */
.dz-table-col-menu-reset {
  display: block;
  width: 100%;
  padding-inline: var(--space-md);
  padding-block: var(--space-xs);
  margin-block-start: var(--space-xs);
  border: none;
  border-block-start: 1px solid var(--colour-border);
  background: none;
  text-align: start;
  cursor: pointer;
  font-size: var(--text-sm);
  color: var(--colour-text-muted);
  transition: background var(--duration-fast) var(--ease-out);
}

.dz-table-col-menu-reset:hover {
  background: var(--colour-bg);
  color: var(--colour-text);
}

.dz-table-col-menu-checkbox {
  width: 0.875rem;
  height: 0.875rem;
  border-radius: var(--radius-sm);
  accent-color: var(--colour-brand);
  cursor: pointer;
}

/* ─────────────────────────── scroll container ───────────────────
 * Holds the loading overlay + horizontal scroll region + table.
 * Border + radius + clip live here so the sticky header doesn't
 * bleed past the rounded corners. */

.dz-table-scroll {
  position: relative;
  border: 1px solid var(--colour-border);
  border-radius: var(--radius-md);
  overflow: hidden;
  /* Reserve vertical space for the upcoming htmx fetch so the page below
     doesn't shift when rows arrive (#965 — CLS regression on list views).
     Floor: `--dz-list-rows` rows × row height + header. The template emits
     --dz-list-rows from table.page_size; without it we default to 10 rows,
     which catches the common case (CLS lands cleanly under 0.1 even when
     actual results are smaller than the reservation). */
  min-height: calc(min(var(--dz-list-rows, 10), 10) * 2.75rem + 3rem);
}

.dz-table-scroll-x {
  overflow-x: auto;
}

/* ─────────────────────────── loading overlay ────────────────────
 * #972: switched from Alpine `x-show="loading"` to pure-CSS keyed off
 * htmx's `.htmx-request` class on the outer dzTable wrapper. The Alpine
 * binding tripped ADR-0022 (idiomorph evaluates child bindings before
 * the ancestor x-data scope rebinds on morph). htmx applies
 * `.htmx-request` to the request-initiating element for the duration
 * of the in-flight request — that's the cleanest signal here.
 *
 * Default state: hidden (no overlay flash on initial paint, replaces
 * the prior x-cloak guard). htmx adds .htmx-request when a request is
 * in flight; CSS reveals the overlay. Backdrop blur + dimmed
 * background + centred spinner. Pointer-events none so the user can
 * still scroll mid-load. */

.dz-table-loading {
  position: absolute;
  inset: 0;
  z-index: 10;
  display: none;
  align-items: center;
  justify-content: center;
  background: color-mix(in oklch, var(--colour-bg) 60%, transparent);
  backdrop-filter: blur(1px);
  pointer-events: none;
  opacity: 0;
  transition: opacity 80ms;
}

/* htmx applies `.htmx-request` to the element named by hx-indicator
 * (the SR-only `#{table_id}-loading-sr` inside .dz-table) for the
 * duration of the in-flight request. `:has()` lets us trigger the
 * overlay from any descendant — Chrome 105+, Safari 15.4+, Firefox
 * 121+ all support it. */
.dz-table:has(.htmx-request) .dz-table-loading {
  display: flex;
  opacity: 1;
}

.dz-table-loading-spinner {
  width: 1.25rem;
  height: 1.25rem;
  color: var(--colour-text-muted);
  animation: dz-spin 1s linear infinite;
}

@keyframes dz-spin {
  to { transform: rotate(360deg); }
}

/* ─────────────────────────── the table itself ───────────────────
 * border-collapse so the sticky-header shadow sits flush. */

.dz-table-grid {
  width: 100%;
  border-collapse: collapse;
}

/* Sticky header — bg + bottom shadow stand in for a 1px border that
 * collapse would have stripped. */
.dz-table-th {
  position: relative;
  padding-inline: var(--space-md);
  padding-block: var(--space-sm);
  text-align: start;
  white-space: nowrap;
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--colour-text-muted);
  user-select: none;
}

.dz-table-th-select {
  width: 2.5rem;
}

.dz-table-th-actions {
  width: 4rem;
}

/* HYPERPART: grid (extension) */
/* Inline-edit editor elements (dz-grid-edit.js builds these on dblclick —
 * JS-assigned classNames, so the registry-class gate can't see them). */
.dz-inline-edit-input {
  width: 100%;
  height: 1.75rem;
  padding-inline: var(--space-sm);
  font-size: var(--text-sm);
  color: var(--colour-text);
  background: var(--colour-surface);
  border-radius: var(--radius-sm);
  border: 1px solid var(--colour-brand);
  outline: none;
  transition:
    border-color var(--duration-fast) var(--ease-out),
    box-shadow var(--duration-fast) var(--ease-out);
}

.dz-inline-edit-input:focus-visible {
  box-shadow: 0 0 0 2px color-mix(in oklch, var(--colour-brand) 20%, transparent);
}

.dz-inline-edit-input:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.dz-inline-edit-select {
  padding-inline-end: var(--space-lg);
  appearance: none;
}

.dz-inline-edit-checkbox {
  width: 1rem;
  height: 1rem;
  border-radius: var(--radius-sm);
  accent-color: var(--colour-brand);
  cursor: pointer;
}

.dz-inline-edit-checkbox:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/* NB the select-column <col> class (dz-table-col-select, in the grid's
 * colgroup) is SEMANTIC_ONLY: a structural marker with no rule — the table
 * stays layout:auto, so the col carries no width. */
/* Column-resize handle (dz-grid-resize.js extension): an 8px pointer strip
 * at the header's right edge driving col[data-col] widths via
 * dz-grid-resize.js. Decorative (aria-hidden); a faint hairline appears on
 * hover; while a drag is live the root carries data-dz-resizing. */
.dz-table-resize-handle {
  position: absolute;
  top: 0;
  /* Fully inside the th: a -4px overhang on the LAST column gets clipped by
   * .dz-table-scroll's overflow:hidden, halving its pointer target. */
  right: 0;
  width: 8px;
  height: 100%;
  cursor: col-resize;
  touch-action: none;
  z-index: 1;
}

.dz-table-resize-handle::after {
  content: "";
  position: absolute;
  top: 20%;
  bottom: 20%;
  right: 1px;
  width: 2px;
  border-radius: 1px;
  background: transparent;
  transition: background var(--duration-fast) var(--ease-out);
}

.dz-table-resize-handle:hover::after,
[data-dz-resizing] .dz-table-resize-handle::after {
  background: var(--colour-border);
}

[data-dz-grid][data-dz-resizing] {
  cursor: col-resize;
  user-select: none;
}

/* Touch (coarse pointer): the 8px strip is a mouse-precision target. Widen
 * the strip to 24px — the web-data-grid touch idiom (an edge slop, not a
 * 44px HIG block, which would eat half the header) — and keep the hairline
 * ALWAYS visible: hover doesn't exist on touch, so the affordance must not
 * depend on it. touch-action:none above already stops the drag being
 * swallowed by scroll panning. Worst case: at the 80px column minimum the
 * strip is 30% of the header and the sort label's rightmost text sits under
 * it — accepted for a user-chosen minimum width. */
@media (pointer: coarse) {
  .dz-table-resize-handle {
    width: 24px;
  }

  .dz-table-resize-handle::after {
    background: var(--colour-border);
  }
}

/* Sort button — inline within the th when the column is sortable. */
.dz-table-sort-button {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
  cursor: pointer;
  background: transparent;
  border: 0;
  padding: 0;
  font: inherit;
  color: inherit;
  text-transform: inherit;
  letter-spacing: inherit;
  transition: color var(--duration-fast) var(--ease-out);
}

.dz-table-sort-button:hover {
  color: var(--colour-text);
}

/* Sort caret: hidden while the column is unsorted (a faint hint on hover),
 * revealed + oriented by the th's `aria-sort` — the same attribute the
 * controller writes and assistive tech reads (no parallel state). chevron-up =
 * ascending; descending flips it 180deg. */
.dz-table-sort-icon {
  /* The caret is decorative (aria-hidden) and must be transparent to
   * pointer/touch so a tap on the <svg>/<use> icon (a natural target at the
   * header's right edge) never becomes the click's event.target. On older iOS
   * Safari an event target inside an SVG <use> can retarget into the use's
   * shadow content, from which `closest()` cannot escape → the delegated sort
   * handler receives null and the tap does nothing. Standard hardening for an
   * icon inside an interactive control: every tap falls through to the
   * <button>, so event.target is always the button. (Not reproduced locally —
   * headless WebKit taps the button fine — but a documented older-Safari
   * failure mode and the correct mitigation regardless.) */
  pointer-events: none;
  opacity: 0;
  transition:
    opacity var(--duration-fast) var(--ease-out),
    transform var(--duration-fast) var(--ease-out);
}

.dz-table-sort-button:hover .dz-table-sort-icon {
  opacity: 0.4;
}

.dz-table-th[aria-sort="ascending"] .dz-table-sort-icon {
  opacity: 1;
  transform: none;
}

.dz-table-th[aria-sort="descending"] .dz-table-sort-icon {
  opacity: 1;
  transform: rotate(180deg);
}

/* Resize handle — 4px wide vertical strip on the right edge of each
 * th, shows brand tint on hover. */
/* Body — divide rows with subtle border-top via adjacent-sibling. */
.dz-table-body > tr + tr {
  border-block-start: 1px solid var(--colour-border);
}

/* ─────────────────────────── empty state ────────────────────────
 * Visible (via :has selector) when tbody has no data rows. */

.dz-table-empty {
  display: none;
  flex-direction: column;
  align-items: center;
  gap: var(--space-md);
  padding-block: var(--space-3xl);
  color: var(--colour-text-muted);
}

/* Show the empty state only when the preceding table has no body cells (#1450).
 * The `tbody` is HTMX-loaded and lives INSIDE the `<table class="dz-table-grid">`,
 * which is a preceding SIBLING of `.dz-table-empty` — so the guard keys off the
 * grid (`.dz-table-grid ~ .dz-table-empty`), not a non-existent following-sibling
 * `tbody`. Using `tbody tr td` (any body cell) also drops the brittle
 * `data-dz-row-id` dependency the old guard required. Populated → no match → base
 * `display: none` hides it; empty (or pre-load) → shown. */
.dz-table-grid:not(:has(tbody tr td)) ~ .dz-table-empty {
  display: flex;
}

.dz-table-empty-icon {
  width: 2.5rem;
  height: 2.5rem;
  opacity: 0.3;
}

.dz-table-empty-title {
  font-size: var(--text-base);
  font-weight: var(--weight-medium);
  color: var(--colour-text);
  margin: 0;
}

.dz-table-empty-hint {
  font-size: var(--text-sm);
  margin: 0;
}

/* ─────────────────────────── pagination footer ──────────────────
 * Wrapper for the pagination fragment (own internal layout). */

.dz-table-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

/* Registry row-action icons (Phase 3): fixed 14px optical size. */
.dz-tr-action-icon {
  width: 0.875rem;
  height: 0.875rem;
  display: block;
}


/* ── data-table rows / filter bar / bulk actions ─────────────────────
 * Moved verbatim from Dazzle fragments.css (grid Hyperpart Slice 0b).
 * The `.dz-table` ancestor selectors here gate the bulk chrome (#978). */
/* #978: bulk-actions visibility is CSS-driven by `[data-dz-bulk-count]`
 * on the ancestor grid root — historically the `.dz-table` element (the
 * gallery), generalized to `[data-dz-grid]` for consumers whose root is a
 * wrapping region (Dazzle's list <section>, convergence C1.1). The count is
 * written by dz-grid.js's sync().
 * Default state: hidden (no flash on initial paint, replaces the prior
 * Alpine x-cloak guard). Show only when bulkCount > 0. */

.dz-bulk-actions {
  display: none;
  align-items: center;
  gap: var(--space-md);
}

.dz-table:not([data-dz-bulk-count="0"]) .dz-bulk-actions,
[data-dz-grid]:not([data-dz-bulk-count="0"]) .dz-bulk-actions {
  display: flex;
}

/* "items" vs "item" — show plural unless count is exactly 1. */
.dz-bulk-plural {
  display: inline;
}
.dz-table[data-dz-bulk-count="1"] .dz-bulk-plural,
[data-dz-grid][data-dz-bulk-count="1"] .dz-bulk-plural {
  display: none;
}

/* Pagination summary — selected vs row-count visibility, mirrors the
 * #978 pattern. The two spans render in the same slot; one shows when
 * the bulk count is positive, the other when it's zero. */
.dz-bulk-summary-selected {
  display: none;
}
.dz-bulk-summary-rows {
  display: inline;
}
.dz-table:not([data-dz-bulk-count="0"]) .dz-bulk-summary-selected,
[data-dz-grid]:not([data-dz-bulk-count="0"]) .dz-bulk-summary-selected {
  display: inline;
}
.dz-table:not([data-dz-bulk-count="0"]) .dz-bulk-summary-rows,
[data-dz-grid]:not([data-dz-bulk-count="0"]) .dz-bulk-summary-rows {
  display: none;
}

.dz-bulk-delete {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  height: 2rem;
  padding-inline: var(--space-md);
  border-radius: var(--radius-sm);
  font-size: var(--text-sm);
  color: var(--colour-danger);
  background: transparent;
  border: 0;
  cursor: pointer;
  transition: background var(--duration-fast) var(--ease-out);
}

.dz-bulk-delete:hover {
  background: color-mix(in oklch, var(--colour-danger) 8%, transparent);
}

.dz-bulk-clear {
  font-size: var(--text-sm);
  color: var(--colour-text-muted);
  background: transparent;
  border: 0;
  cursor: pointer;
  transition: color var(--duration-fast) var(--ease-out);
}

.dz-bulk-clear:hover {
  color: var(--colour-text);
}

/* "Select all N matching" — the all-matching escalation affordance. A quiet
 * brand-coloured text button (it sits between the count and Delete). Hidden
 * once the mode is active: its job is done, and the header select-all is the
 * exit. */
.dz-bulk-matching {
  font-size: var(--text-sm);
  color: var(--colour-brand-text);
  background: transparent;
  border: 0;
  cursor: pointer;
  transition: color var(--duration-fast) var(--ease-out);
}

.dz-bulk-matching:hover {
  text-decoration: underline;
}

.dz-table[data-dz-grid-all-matching="true"] .dz-bulk-matching,
[data-dz-grid][data-dz-grid-all-matching="true"] .dz-bulk-matching {
  display: none;
}

/* SR-only announcer for result-window changes: the pagination footer is
 * server-repainted wholesale, so screen readers can't track it — the grid
 * controller mirrors the summary into this aria-live region instead.
 * Standard visually-hidden pattern (present in the a11y tree, no pixels). */
.dz-grid-announce {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ─────────────────────────── filter_bar fragment ────────────────
 * Per-column filter inputs (selects + text + ref-loaded selects).
 * Outer row layout + label sizing only — inputs use the form
 * input chrome from form.css. */

.dz-filter-bar {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-sm);
  align-items: flex-end;
}

.dz-filter-cell {
  display: flex;
  flex-direction: column;
  gap: 0.125rem;
  width: 10rem;
}

.dz-filter-label {
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  color: var(--colour-text-muted);
}

.dz-filter-select,
.dz-filter-input {
  /* min-height (not a hard height) + an explicit padding-block: a fixed
   * `height: 2rem` on a border-box <select> left only ~14px of content
   * once the UA's default ~8px vertical padding + 1px borders were
   * subtracted, clipping the bottom of the text. min-height keeps the
   * ~2rem look but lets the control grow rather than crop. */
  min-height: 2rem;
  width: 100%;
  border-radius: var(--radius-sm);
  border: 1px solid var(--colour-border);
  background: var(--colour-surface);
  padding-inline: var(--space-sm);
  padding-block: 0.25rem;
  font-size: var(--text-sm);
  color: var(--colour-text);
  outline: none;
  transition: box-shadow var(--duration-base) var(--ease-out);
}

.dz-filter-select:focus-visible,
.dz-filter-input:focus-visible {
  box-shadow: 0 0 0 1px var(--colour-brand);
}

/* ─────────────────────────── table_rows fragment ────────────────
 * Inline-editable data table rows with bulk-select checkboxes,
 * per-cell display/edit modes, and hover-revealed action buttons.
 * Row state matrix:
 *   - .is-selected  → bulk-select checkbox checked → 10% brand tint
 *   - .is-saving    → editing.saving → 70% opacity
 *   - .is-error     → editing.error  → 2px danger left border
 *   - default       → hover row tint
 * Alpine `:class="{...}"` binds these modifiers based on its state. */

.dz-tr-row {
  height: 2.25rem;
  font-size: var(--text-sm);
  color: var(--colour-text);
  cursor: pointer;
  transition: background var(--duration-fast) var(--ease-out),
              opacity var(--duration-fast) var(--ease-out);
}

.dz-tr-row:hover {
  background: var(--colour-surface);
}

.dz-tr-row.is-selected {
  background: color-mix(in oklch, var(--colour-brand) 10%, transparent);
}

.dz-tr-row.is-saving {
  opacity: 0.7;
}

.dz-tr-row.is-error {
  border-inline-start: 2px solid var(--colour-danger);
}

.dz-tr-checkbox-cell {
  width: 2.5rem;
  padding-inline: var(--space-md);
}

.dz-tr-checkbox {
  width: 0.875rem;
  height: 0.875rem;
  border-radius: var(--radius-sm);
  accent-color: var(--colour-brand);
  cursor: pointer;
}

.dz-tr-cell {
  padding-inline: var(--space-md);
}

.dz-tr-cell.is-numeric {
  text-align: end;
  font-variant-numeric: tabular-nums;
}

.dz-tr-cell-display {
  display: block;
  width: 100%;
  border-radius: var(--radius-sm);
  padding-inline: 0.25rem;
  margin-inline: -0.25rem;
  padding-block: 0.125rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  transition: outline var(--duration-fast) var(--ease-out);
  /* double-tap must open the editor, not zoom (dblclick on touch) */
  touch-action: manipulation;
}

.dz-tr-cell-display:hover {
  outline: 1px solid var(--colour-border);
  cursor: text;
}

.dz-tr-cell-truncate {
  display: block;
  max-width: 15rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.dz-tr-actions-cell {
  width: 4rem;
  padding-inline: var(--space-sm);
  text-align: end;
}

.dz-tr-actions {
  display: inline-flex;
  align-items: center;
  gap: 0.125rem;
  opacity: 0;
  transition: opacity var(--duration-fast) var(--ease-out);
}

.dz-tr-row:hover .dz-tr-actions {
  opacity: 1;
}

.dz-tr-action {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.75rem;
  height: 1.75rem;
  border-radius: var(--radius-sm);
  color: var(--colour-text-muted);
  background: transparent;
  border: 0;
  cursor: pointer;
  transition: background var(--duration-fast) var(--ease-out),
              color var(--duration-fast) var(--ease-out);
  text-decoration: none;
}

.dz-tr-action:hover {
  background: color-mix(in oklch, var(--colour-bg) 50%, transparent);
  color: var(--colour-text);
}

.dz-tr-action.is-destructive:hover {
  background: color-mix(in oklch, var(--colour-danger) 10%, transparent);
  color: var(--colour-danger);
}

/* Empty-state row spans the full table */
.dz-tr-empty-cell {
  text-align: center;
  padding-block: var(--space-2xl);
  font-size: var(--text-sm);
  color: var(--colour-text-muted);
}

.dz-tr-empty-link {
  margin-inline-start: var(--space-sm);
  color: var(--colour-brand);
  text-decoration: none;
}

.dz-tr-empty-link:hover {
  text-decoration: underline;
}
/* --- hatchi-maxchi/components/tabs.css --- */
/* HYPERPART: tabs */
/*
 * dz-tabs — a lazy tab strip. An honest link-strip, NOT a half-built ARIA
 * tabs widget: the tabs are <button>s (natively focusable + Tab-navigable)
 * with `aria-current="true"` on the active one — no `role="tablist"` it
 * can't back with roving-tabindex/arrow-key navigation (the candor HM
 * already applies to menu: "disclosure, not ARIA menu").
 *
 * Each panel lazy-loads its content via htmx (`hx-get` on `intersect once`,
 * so a panel fetches the first time it's revealed); dz-tabs.js toggles the
 * active tab + shows/hides panels, scoped to each `.dz-tabs` root so N tab
 * groups coexist. The first panel is visible (and may carry its content
 * inline / load on `load`); the rest start `hidden`.
 */

.dz-tabs {
  display: block;
}

.dz-tabs__list {
  display: flex;
  border-block-end: 1px solid var(--colour-border);
  margin-block-end: var(--space-md);
}

.dz-tabs__tab {
  padding-inline: var(--space-md);
  padding-block: var(--space-sm);
  font-size: var(--text-sm);
  color: var(--colour-text-muted);
  background: transparent;
  border: 0;
  border-block-end: 2px solid transparent;
  margin-block-end: -1px; /* overlap the list border so the active bar is flush */
  cursor: pointer;
  transition: color var(--duration-fast) var(--ease-out);
}

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

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

.dz-tabs__tab[aria-current="true"] {
  border-block-end-color: var(--colour-brand);
  color: var(--colour-text);
  font-weight: var(--weight-medium);
}

.dz-tabs__loading {
  display: flex;
  justify-content: center;
  padding-block: var(--space-2xl);
  color: var(--colour-text-muted);
}

.dz-tabs__loading > svg {
  width: 1rem;
  height: 1rem;
  animation: dz-spin 1s linear infinite;
}

@media (prefers-reduced-motion: reduce) {
  .dz-tabs__tab { transition: none; }
  .dz-tabs__loading > svg { animation: none; }
}
/* --- hatchi-maxchi/components/tags.css --- */
/* HYPERPART: tags */
/* dz-tags — HM-native multi-value chips input (HMC-018 slice 2).
 *
 * Progressive enhancement over a real `<input type="text" data-dz-tags>`
 * carrying a comma-joined value: with JS off the native input is fully
 * usable (the user types "a, b, c"; the server splits on comma). On first
 * interaction controllers/dz-tags.js wraps it in a `.dz-tags` root — a
 * role=list of removable chips + a borderless entry input — and hides the
 * native input (kept in the DOM as the submit value + no-JS fallback). The
 * `.dz-tags` box carries the input chrome + focus-within ring + aria-invalid;
 * the entry is borderless inside it. Chrome mirrors the combobox /
 * search-select family. */

.dz-tags {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-xs);
  width: 100%;
  min-height: 2rem;
  padding-block: var(--space-xs);
  padding-inline: var(--space-sm);
  border-radius: var(--radius-sm);
  background: var(--colour-surface);
  border: 1px solid var(--colour-border);
  cursor: text;
  transition:
    box-shadow var(--duration-base) var(--ease-out),
    border-color var(--duration-base) var(--ease-out);
}

.dz-tags:focus-within {
  box-shadow: 0 0 0 1px var(--colour-brand);
  border-color: var(--colour-brand);
}

.dz-tags[aria-invalid="true"] {
  border-color: var(--colour-danger);
}

.dz-tags[aria-invalid="true"]:focus-within {
  box-shadow: 0 0 0 1px var(--colour-danger);
}

/* Native <input data-dz-tags> is the no-JS control + the submitted value;
 * once the controller wraps it and marks the root `data-dz-enhanced`, the
 * chips UI takes over and the native input leaves the visual layer (it stays
 * in the DOM — display:none still submits — to carry the comma value). */
.dz-tags[data-dz-enhanced] > input[data-dz-tags] {
  display: none;
}

/* The chips list participates directly in the box's flex flow (role=list
 * kept for AT via display:contents), so chips and the entry share one line
 * and wrap together. */
.dz-tags-list {
  display: contents;
}

/* A chip: a small rounded token with a remove control. */
.dz-tags-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
  padding-block: 0.125rem;
  padding-inline: var(--space-sm);
  border-radius: var(--radius-sm);
  background: var(--colour-surface-muted);
  color: var(--colour-text);
  font-size: var(--text-sm);
  line-height: 1.4;
}

.dz-tags-chip-label {
  white-space: nowrap;
}

.dz-tags-remove {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1rem;
  height: 1rem;
  padding: 0;
  border: none;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--colour-text-muted);
  font-size: 1rem;
  line-height: 1;
  cursor: pointer;
  transition: color var(--duration-base) var(--ease-out);
}

.dz-tags-remove:hover {
  color: var(--colour-text);
}

.dz-tags-remove:focus-visible {
  outline: none;
  box-shadow: 0 0 0 1px var(--colour-brand);
}

/* Borderless entry inside the box — the box owns the chrome + focus ring. */
.dz-tags-entry {
  flex: 1 1 6rem;
  min-width: 6rem;
  height: 1.5rem;
  padding: 0;
  border: none;
  outline: none;
  background: transparent;
  font-size: var(--text-sm);
  color: var(--colour-text);
}

.dz-tags-entry::placeholder {
  color: var(--colour-text-muted);
}
/* --- hatchi-maxchi/components/task-inbox.css --- */
/* HYPERPART: task-inbox */
/* Promoted VERBATIM from Dazzle regions.css (Tier C-b, fleet
 * convergence). The Dazzle emitter contract is unchanged. */

/* ─────────────────────────── task_inbox region ──────────────────
 * Summary chips (count + label pill) above a list of task rows
 * (#1015). The `.dz-task-inbox-chip*` family had NO css at all, so the
 * count and label spans rendered glued together ("0manuscripts");
 * the chip is now an inline-flex pill with a gap between count and
 * label, mirroring `.dz-progress-chip` (#1326). */

.dz-task-inbox-chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xs);
  margin-block-end: var(--space-sm);
}

.dz-task-inbox-chip {
  display: inline-flex;
  align-items: baseline;
  gap: var(--space-xs);
  padding-inline: var(--space-sm);
  padding-block: 0.125rem;
  border-radius: var(--radius-sm);
  font-size: var(--text-xs);
  border: 1px solid var(--colour-border);
  background: var(--colour-bg);
  color: var(--colour-text-muted);
  text-decoration: none;
}

.dz-task-inbox-chip-count {
  font-weight: var(--weight-semibold);
  color: var(--colour-text);
}

.dz-task-inbox-chip-label {
  color: var(--colour-text-muted);
}
/* --- hatchi-maxchi/components/timeline.css --- */
/* HYPERPART: timeline */
/* Promoted VERBATIM from Dazzle regions.css (W2, fleet convergence).
 * The Dazzle emitter contract predates the move and is unchanged. */

/* ─────────────────────────── timeline region ────────────────────
 * Vertical timeline (similar shape to activity_feed). Bullet
 * marker on the left rail, date column, content pad. Shares the
 * .dz-activity-* dot/bullet pattern but renders item-shape rather
 * than bare message. */

.dz-timeline-list {
  position: relative;
  padding-inline-start: var(--space-md);
  border-inline-start: 1px solid var(--colour-border);
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  list-style: none;
  margin: 0;
}

.dz-timeline-item {
  position: relative;
}

.dz-timeline-bullet-wrap {
  position: absolute;
  inset-inline-start: -1.3125rem;
  top: 0.375rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 0.75rem;
  height: 0.75rem;
}

.dz-timeline-bullet {
  width: 0.75rem;
  height: 0.75rem;
}

.dz-timeline-row {
  display: flex;
  gap: var(--space-md);
}

.dz-timeline-date {
  font-size: var(--text-xs);
  color: var(--colour-text-muted);
  min-width: 5rem;
  padding-block-start: 0.25rem;
}

.dz-timeline-content {
  flex: 1 1 0;
  padding-inline: var(--space-sm);
  padding-block: var(--space-xs);
  border-radius: var(--radius-sm);
}

.dz-timeline-content.is-clickable {
  cursor: pointer;
  transition: background var(--duration-fast) var(--ease-out);
}

.dz-timeline-content.is-clickable:hover {
  background: var(--colour-bg);
}

.dz-timeline-title {
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  color: var(--colour-text);
  margin: 0;
}

.dz-timeline-field {
  font-size: var(--text-xs);
  color: var(--colour-text-muted);
  margin: 0;
}

.dz-timeline-overflow {
  font-size: var(--text-xs);
  color: var(--colour-text-muted);
  margin-block-start: var(--space-sm);
}

/* attention_accent macro (moved with its consumer — the timeline's
 * bullets carry dz-attn-bullet + dz-attn-tone-*; kanban's card-attn
 * text also keys the attn contract). */
/* ─────────────────────────── attention_accent macro ─────────────
 * Cross-region attention/severity tier visual treatment. The macro
 * `attention_classes` returns a class string the caller pastes into
 * the element's `class=` attribute; this CSS resolves the (style ×
 * tone) matrix.
 *
 *   - border  — 4px inline-start accent (grid)
 *   - tint    — 0.06–0.08 background tint (list)
 *   - both    — border + 0.04 background tint (queue)
 *   - bullet  — text colour for the timeline dot (timeline)
 *
 * Tone semantics:
 *   - critical → danger
 *   - warning  → warning
 *   - notice   → brand
 *   - default  → brand (bullet only — falls back when no attention)
 */

.dz-attn-border {
  border-inline-start-width: 4px;
  border-inline-start-style: solid;
}

.dz-attn-border.dz-attn-tone-critical { border-inline-start-color: var(--colour-danger); }
.dz-attn-border.dz-attn-tone-warning  { border-inline-start-color: var(--colour-warning); }
.dz-attn-border.dz-attn-tone-notice   { border-inline-start-color: var(--colour-brand); }

.dz-attn-tint.dz-attn-tone-critical {
  background: color-mix(in oklch, var(--colour-danger) 8%, transparent);
}
.dz-attn-tint.dz-attn-tone-warning {
  background: color-mix(in oklch, var(--colour-warning) 8%, transparent);
}
.dz-attn-tint.dz-attn-tone-notice {
  background: color-mix(in oklch, var(--colour-brand) 6%, transparent);
}

.dz-attn-both {
  border-inline-start-width: 4px;
  border-inline-start-style: solid;
}

.dz-attn-both.dz-attn-tone-critical {
  border-inline-start-color: var(--colour-danger);
  background: color-mix(in oklch, var(--colour-danger) 4%, transparent);
}

.dz-attn-both.dz-attn-tone-warning {
  border-inline-start-color: var(--colour-warning);
  background: color-mix(in oklch, var(--colour-warning) 4%, transparent);
}

.dz-attn-both.dz-attn-tone-notice {
  border-inline-start-color: var(--colour-brand);
  background: color-mix(in oklch, var(--colour-brand) 4%, transparent);
}

.dz-attn-bullet.dz-attn-tone-critical { color: var(--colour-danger); }
.dz-attn-bullet.dz-attn-tone-warning  { color: var(--colour-warning); }
.dz-attn-bullet.dz-attn-tone-notice   { color: var(--colour-brand); }
.dz-attn-bullet.dz-attn-tone-default  { color: var(--colour-brand); }
/* --- hatchi-maxchi/components/touch-targets.css --- */
/*
 * touch-targets.css — mobile touch hit-area enforcement (#958 cycle 1).
 *
 * Bumps every interactive primitive's min-height + min-width to
 * `--dz-touch-target-min` (44px, Apple HIG) on touch devices. Desktop
 * keeps its dense pixel-perfect sizing.
 *
 * Why `@media (pointer: coarse)` rather than `(max-width: ...)`?
 *   - Touch tablets in landscape look "wide" to width queries but
 *     still need the bigger hit area.
 *   - Mouse users on small windows DON'T need 44px buttons (precise
 *     pointer).
 *   - `pointer: coarse` is the explicit "primary input is finger"
 *     signal, supported in all modern browsers.
 *
 * Selectors covered:
 *   - `.dz-button` (and primary/ghost/outline/destructive variants)
 *   - `.dz-icon-button`
 *   - `.dz-card-action-button`
 *   - `.dz-list-csv-button` (list-region CSV export, 28px at rest)
 *   - `.dz-sidebar-action-button`
 *   - `.dz-add-card-button`
 *   - Close buttons: `.dz-drawer__close`, `.dz-pdf-viewer-help-close`,
 *     `.dz-pdf-viewer-panel-close`
 *
 * Wrapped in @layer components so component-specific rules win when
 * they need to.
 */
@layer components {
  @media (pointer: coarse) {
    /* .dz-button covers every button — variants are data-dz-variant on the
       same base element, so no per-variant selector is needed here. */
    .dz-button,
    .dz-icon-button,
    .dz-card-action-button,
    .dz-list-csv-button,
    .dz-sidebar-action-button,
    .dz-add-card-button {
      min-height: var(--dz-touch-target-min);
      min-width: var(--dz-touch-target-min);
    }

    /* Close-X buttons are typically small icon-only — most likely
     * to under-deliver hit area. Bump explicitly. */
    .dz-drawer__close,
    .dz-pdf-viewer-help-close,
    .dz-pdf-viewer-panel-close {
      min-height: var(--dz-touch-target-min);
      min-width: var(--dz-touch-target-min);
    }

    /* Bare native buttons / links inside .dz-button regions still
     * benefit — a `<button>` without a Dazzle class shouldn't fall
     * below 44px on touch. */
    button,
    [role="button"],
    a.dz-link {
      min-height: var(--dz-touch-target-min);
    }
  }
}
/* --- hatchi-maxchi/components/transitions.css --- */
/*
 * transitions.css — page-level transitions & overlays (HaTchi-MaXchi).
 *
 * Migrated from Dazzle dz.css (HMC-006b part 2, 2026-07-09): toast enter/leave,
 * cross-document view-transitions for fragment navigation, the native <dialog>
 * backdrop + open animation, and the resize-drag body helpers. Values route
 * through HM tokens where one exists (--ease-*, --duration-*); a few remain
 * literal (specific durations / the scrim / the translate offset).
 *
 * HMC-011 FIX: the view-transition fade keyframes are named `dz-view-fade-*`
 * (NOT `dz-fade-*`) so they no longer collide with HM's own fade-only
 * `@keyframes dz-fade-in/out` in tokens.css — before this, dz.css's unlayered
 * slide+fade `dz-fade-*` shadowed HM's, so `--animation-fade-*` consumers
 * rendered the wrong (slide) animation. View-transitions keep their slide+fade.
 */

/* ── Toast notifications ─────────────────────────────────────────────── */
.dz-toast-enter {
  opacity: 0;
  transform: translateX(1rem);
  transition: opacity 200ms var(--ease-out), transform 200ms var(--ease-out);
}

.dz-toast-leave {
  opacity: 0;
  transform: translateX(1rem);
  transition:
    opacity var(--duration-base) var(--ease-in),
    transform var(--duration-base) var(--ease-in);
}

#dz-toast .alert {
  transition: opacity 200ms var(--ease-out), transform 200ms var(--ease-out);
}

/* ── View transitions for fragment navigation ────────────────────────── */
@view-transition {
  navigation: auto;
}

#main-content {
  view-transition-name: main-content;
}

::view-transition-old(main-content) {
  animation: dz-view-fade-out var(--duration-base) var(--ease-in) forwards;
}

::view-transition-new(main-content) {
  animation: dz-view-fade-in 200ms var(--ease-out);
}

@keyframes dz-view-fade-out {
  to {
    opacity: 0;
    transform: translateY(4px);
  }
}

@keyframes dz-view-fade-in {
  from {
    opacity: 0;
    transform: translateY(4px);
  }
}

/* ── Native dialog backdrop + open animation ─────────────────────────── */
dialog::backdrop {
  background: rgba(0, 0, 0, 0.5);
  backdrop-filter: blur(2px);
}

dialog[open] {
  animation: dz-dialog-in 200ms var(--ease-out);
}

@keyframes dz-dialog-in {
  from {
    opacity: 0;
    transform: scale(0.95);
  }
  to {
    opacity: 1;
    transform: scale(1);
  }
}

/* ── Resize-drag body helpers ────────────────────────────────────────── */
body.select-none {
  user-select: none;
  -webkit-user-select: none;
}

body.cursor-col-resize,
body.cursor-col-resize * {
  cursor: col-resize !important;
}

/* ── Reduced motion ──────────────────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
  ::view-transition-old(main-content),
  ::view-transition-new(main-content) {
    animation: none;
  }
}
/* --- hatchi-maxchi/components/tree.css --- */
/* HYPERPART: tree */
/* Promoted VERBATIM from Dazzle regions.css (Tier F, fleet
 * convergence). The Dazzle emitter contract is unchanged. */

/* ─────────────────────────── tree region ────────────────────────
 * Nested <details>/<summary> hierarchy. Each level indents via the
 * children container's padding-inline-start. The chevron rotates
 * via the [open] state on the parent details. */

.dz-tree-node {
  background: color-mix(in oklch, var(--colour-bg) 70%, transparent);
  border-radius: var(--radius-sm);
  margin-block-end: var(--space-xs);
}

.dz-tree-summary {
  font-size: var(--text-sm);
  padding-inline: var(--space-md);
  padding-block: 0.375rem;
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  cursor: pointer;
  list-style: none;
  border-radius: var(--radius-sm);
  transition: background var(--duration-fast) var(--ease-out);
}

/* Hide the default disclosure marker — we render our own chevron. */
.dz-tree-summary::-webkit-details-marker {
  display: none;
}

.dz-tree-summary:hover {
  background: var(--colour-bg);
}

.dz-tree-chevron {
  width: 0.75rem;
  height: 0.75rem;
  color: var(--colour-text-muted);
  transition: transform var(--duration-fast) var(--ease-out);
  flex-shrink: 0;
}

.dz-tree-node[open] > .dz-tree-summary .dz-tree-chevron {
  transform: rotate(90deg);
}

.dz-tree-label {
  font-weight: var(--weight-medium);
  color: var(--colour-text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.dz-tree-count {
  margin-inline-start: auto;
  display: inline-flex;
  align-items: center;
  padding-inline: 0.375rem;
  height: 1rem;
  border-radius: var(--radius-sm);
  font-size: var(--text-xs);
  color: var(--colour-text-muted);
  background: var(--colour-bg);
}

.dz-tree-children {
  padding-inline-start: var(--space-lg);
  padding-inline-end: var(--space-sm);
  padding-block-end: var(--space-xs);
}

.dz-tree-flat-item {
  padding-inline: var(--space-sm);
  padding-block: 0.375rem;
  font-size: var(--text-sm);
  color: var(--colour-text);
  border-block-end: 1px solid var(--colour-border);
}

.dz-tree-flat-item:last-child {
  border-block-end: 0;
}
/* --- hatchi-maxchi/components/two-factor.css --- */
/* HYPERPART: two-factor */
/* Promoted from Dazzle fragments.css (Tier A2, fleet convergence) —
 * the 2FA setup/settings/challenge chrome (two_factor_views.py
 * scaffolds + the dz-2fa-*.js-created rows/pills). Deltas from the
 * source, each fixing a latent mismatch:
 *   - .dz-auth-card-title/-subtitle: the scaffolds emit these names,
 *     but the rules were named .dz-auth-title/-subtitle (never
 *     matched — the 2FA card heading rendered unstyled). Renamed to
 *     the emitted classes.
 *   - .dz-auth-qr-container img: the setup JS appends a CLASSLESS
 *     <img>; the old .dz-auth-qr-image rule never matched. Restyled
 *     via the container.
 *   - .dz-auth-page: gains centering for standalone HM consumers. In
 *     Dazzle's own bundle the later site-sections.css gradient rule
 *     still wins (same layer, later) — the site-sections auth-block
 *     collision is filed as #1549.
 * The dead subset (product/title/subtitle/link-muted/link-button and pseudo-states /
 * qr/qr-image/qr-secret) was deleted at source. */

.dz-auth-page {
  min-height: 100dvh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-xl) var(--space-md);
  background: var(--colour-bg);
}

.dz-auth-card-title {
  text-align: center;
  font-size: var(--text-xl);
  font-weight: var(--weight-semibold);
  color: var(--colour-text);
  margin-block-end: var(--space-lg);
}

.dz-auth-card-subtitle {
  text-align: center;
  font-size: var(--text-sm);
  color: var(--colour-text-muted);
  margin-block: calc(var(--space-md) * -0.5) var(--space-md);
}

.dz-auth-qr-container img {
  inline-size: 12rem;
  block-size: 12rem;
  border-radius: var(--radius-md);
  border: 1px solid var(--colour-border);
  background: var(--colour-surface);
}

.dz-auth-card {
  width: 100%;
  max-width: 24rem;
  background: var(--colour-surface);
  border: 1px solid var(--colour-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  padding: var(--space-lg);
}

.dz-auth-error {
  display: none;  /* revealed by JS clearing the hidden attribute */
  flex-direction: row;
  gap: var(--space-sm);
  padding: var(--space-md);
  margin-block-end: var(--space-md);
  border-radius: var(--radius-sm);
  border: 1px solid var(--colour-danger);
  background: color-mix(in oklch, var(--colour-danger) 8%, transparent);
  font-size: var(--text-sm);
  color: var(--colour-text);
}

.dz-auth-error:not([hidden]) {
  display: flex;
}

/* Success alert — mirror of .dz-auth-error but brand-toned. Used by
 * the forgot-password flow's "email sent" confirmation. */
.dz-auth-success {
  display: none;
  flex-direction: row;
  gap: var(--space-sm);
  padding: var(--space-md);
  margin-block-end: var(--space-md);
  border-radius: var(--radius-sm);
  border: 1px solid var(--colour-brand);
  background: color-mix(in oklch, var(--colour-brand) 8%, transparent);
  font-size: var(--text-sm);
  color: var(--colour-text);
}

.dz-auth-success:not([hidden]) {
  display: flex;
}

.dz-auth-form {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

.dz-auth-field {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}

.dz-auth-label {
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  color: var(--colour-text);
}

.dz-auth-submit {
  inline-size: 100%;
  margin-block-start: var(--space-md);
  block-size: 2.25rem;
}

.dz-auth-input-code {
  inline-size: 100%;
  block-size: 2.5rem;
  padding-inline: var(--space-md);
  border-radius: var(--radius-sm);
  background: var(--colour-bg);
  border: 1px solid var(--colour-border);
  color: var(--colour-text);
  font-size: 1.25rem;
  font-weight: var(--weight-semibold);
  letter-spacing: 0.4em;
  text-align: center;
  transition: box-shadow var(--duration-base) var(--ease-out),
              border-color var(--duration-base) var(--ease-out);
}

.dz-auth-input-code::placeholder {
  color: var(--colour-text-muted);
  letter-spacing: 0.4em;
}

.dz-auth-input-code:focus-visible {
  outline: none;
  box-shadow: 0 0 0 1px var(--colour-brand);
}

.dz-auth-section-title {
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  color: var(--colour-text);
  margin-block-end: 0.25rem;
}

.dz-auth-section-body {
  font-size: var(--text-sm);
  color: var(--colour-text-muted);
  margin-block-end: var(--space-md);
}

.dz-auth-qr-container {
  display: flex;
  justify-content: center;
  margin-block: var(--space-md);
}

.dz-auth-secret-inline {
  font-family: var(--font-mono, monospace);
  font-size: 0.6875rem;
  color: var(--colour-text);
  background: var(--colour-bg);
  padding-inline: 0.25rem;
  padding-block: 0.125rem;
  border-radius: var(--radius-xs, 0.1875rem);
}

.dz-auth-hr {
  border: 0;
  border-block-start: 1px solid var(--colour-border);
  margin-block: var(--space-xl);
}

.dz-auth-recovery-alert {
  border-radius: var(--radius-sm);
  border: 1px solid var(--colour-border);
  background: var(--colour-bg);
  padding: var(--space-md);
}

.dz-auth-recovery-alert-title {
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  color: var(--colour-text);
}

.dz-auth-recovery-alert-body {
  font-size: var(--text-xs);
  color: var(--colour-text-muted);
  margin-block-start: 0.25rem;
}

.dz-auth-recovery-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-sm);
  margin-block-start: var(--space-md);
  font-family: var(--font-mono, monospace);
  font-size: var(--text-xs);
}

.dz-auth-recovery-pill {
  border-radius: var(--radius-sm);
  border: 1px solid var(--colour-border);
  background: var(--colour-surface);
  padding-inline: var(--space-sm);
  padding-block: var(--space-sm);
  text-align: center;
  font-size: var(--text-xs);
  color: var(--colour-text);
}

.dz-auth-back-link {
  display: block;
  inline-size: 100%;
  block-size: 2.25rem;
  line-height: 2.25rem;
  text-align: center;
  border-radius: var(--radius-sm);
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  color: var(--colour-text-muted);
  text-decoration: none;
  margin-block-start: var(--space-xl);
  transition: background var(--duration-fast) var(--ease-out),
              color var(--duration-fast) var(--ease-out);
}

.dz-auth-back-link:hover {
  background: var(--colour-bg);
  color: var(--colour-text);
}

.dz-auth-status-loading {
  text-align: center;
  font-size: var(--text-sm);
  color: var(--colour-text-muted);
  margin-block-start: var(--space-sm);
}

.dz-auth-status-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding-block: var(--space-md);
  border-block-end: 1px solid var(--colour-border);
}

.dz-auth-status-row.is-last {
  border-block-end: 0;
}

.dz-auth-status-label {
  font-size: var(--text-sm);
  color: var(--colour-text);
}
/* --- hatchi-maxchi/components/workspace-shell.css --- */
/* Internal workspace chrome (not a standalone gallery Hyperpart — no HYPERPART
 * marker, matching the other migrated chrome components detail/fragments/etc.). */
/* Moved from Dazzle dashboard.css (HMC-007c) — the workspace composition
 * shell: outer padding + heading row + primary actions, context selector,
 * and the edit toolbar (Reset/Save with the 5 saveState values). Byte-faithful,
 * already HM-tokenised; the dz-spin keyframe it uses lives in HM base. */

/* ─────────────────────────── outer shell ────────────────────────
 * Padded root for the workspace; no max-width constraint — caller
 * provides the layout container. */

.dz-workspace {
  padding: var(--space-md);
}

.dz-workspace-heading {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-md);
  margin-block-end: var(--space-md);
  /* Heading wraps when title + actions exceed the row — actions
   * fall under the title rather than bleeding past <main>'s right
   * edge (#985). */
  flex-wrap: wrap;
}

.dz-workspace-title {
  /* #983 — canonical app page-title scale. Was hard-coded to
   * `--text-lg`; promoting to the named token unifies workspace
   * titles with list-page titles + leaves the visual size
   * unchanged (token resolves to --text-lg). */
  font-size: var(--dz-heading-app-page-title);
  font-weight: var(--weight-medium);
  color: var(--colour-text);
  margin: 0;
}

.dz-workspace-primary-actions {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  /* #985: workspaces with many entities surface a long row of
   * "New <Entity>" buttons. Without flex-wrap the row would bleed
   * past <main>'s right edge (5 × 200px buttons + gaps overflows a
   * 1024px content column). flex-shrink:0 is intentionally dropped:
   * shrinking would squeeze button labels, wrapping is cleaner.
   * justify-content:flex-end keeps wrapped rows right-aligned to
   * match the heading's space-between layout. */
  flex-wrap: wrap;
  justify-content: flex-end;
}

/* Workspace-level primary action — same shape as .dz-button-primary
 * but slightly taller (h-2 instead of h-1.75) to match the heading. */
.dz-workspace-action {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
  height: 2rem;
  padding-inline: var(--space-md);
  border-radius: var(--radius-sm);
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  background: var(--colour-brand);
  color: var(--colour-brand-contrast);
  text-decoration: none;
  transition: opacity var(--duration-fast) var(--ease-out);
}

.dz-workspace-action:hover {
  opacity: 0.9;
}

/* Suppress the default <summary> disclosure triangle cross-browser. */
/* ─────────────────────────── context selector ───────────────────
 * Workspace-level context filter. Just a label + select. */

.dz-workspace-context {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  margin-block-end: var(--space-md);
}

.dz-workspace-context-label {
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  color: var(--colour-text);
}

.dz-workspace-context-select {
  height: 2rem;
  padding-inline: var(--space-sm);
  font-size: var(--text-sm);
  color: var(--colour-text);
  background: var(--colour-surface);
  border: 1px solid var(--colour-border);
  border-radius: var(--radius-sm);
}

.dz-workspace-context-select:focus-visible {
  outline: none;
  box-shadow: 0 0 0 1px var(--colour-brand);
  border-color: var(--colour-brand);
}

/* ─────────────────────────── edit toolbar ───────────────────────
 * Right-aligned row above the grid: Reset + Save buttons. Save
 * reflects saveState (clean/dirty/saving/saved/error) via a
 * data-dz-save-state attribute the template binds with x-bind. */

.dz-workspace-toolbar {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--space-sm);
  margin-block-end: var(--space-md);
}

.dz-workspace-toolbar-spacer {
  flex: 1 1 0;
}

.dz-workspace-reset {
  height: 2rem;
  padding-inline: var(--space-md);
  border-radius: var(--radius-sm);
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  color: var(--colour-text-muted);
  background: transparent;
  border: 1px solid transparent;
  cursor: pointer;
  transition: background var(--duration-fast) var(--ease-out),
              color var(--duration-fast) var(--ease-out);
}

.dz-workspace-reset:hover {
  background: var(--colour-bg);
  color: var(--colour-text);
}

.dz-workspace-save {
  height: 2rem;
  padding-inline: var(--space-md);
  border-radius: var(--radius-sm);
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  border: 1px solid transparent;
  cursor: pointer;
  transition:
    background var(--duration-fast) var(--ease-out),
    color var(--duration-fast) var(--ease-out),
    border-color var(--duration-fast) var(--ease-out),
    opacity var(--duration-fast) var(--ease-out);
}

/* Save-state span visibility (Tier F4e: replaces the Alpine
 * x-cloak/x-show pair) — the button's data-dz-save-state selects
 * exactly one label span. */
.dz-workspace-save [data-dz-when] {
  display: none;
}

.dz-workspace-save[data-dz-save-state="clean"] [data-dz-when="clean"],
.dz-workspace-save[data-dz-save-state="dirty"] [data-dz-when="dirty"],
.dz-workspace-save[data-dz-save-state="saving"] [data-dz-when="saving"],
.dz-workspace-save[data-dz-save-state="saved"] [data-dz-when="saved"],
.dz-workspace-save[data-dz-save-state="error"] [data-dz-when="error"] {
  display: inline-flex;
}

.dz-workspace-save[data-dz-save-state="clean"] {
  color: var(--colour-text-muted);
  cursor: default;
}

.dz-workspace-save[data-dz-save-state="dirty"] {
  background: var(--colour-brand);
  color: var(--colour-brand-contrast);
}

.dz-workspace-save[data-dz-save-state="saving"] {
  background: var(--colour-brand);
  color: var(--colour-brand-contrast);
  opacity: 0.7;
}

.dz-workspace-save[data-dz-save-state="saved"] {
  background: var(--success-600); /* fixed step — white text on fill */
  color: white;
}

.dz-workspace-save[data-dz-save-state="error"] {
  border-color: var(--colour-danger);
  color: var(--colour-danger);
}

.dz-workspace-save-busy {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
}

.dz-workspace-save-busy-icon {
  width: 0.75rem;
  height: 0.75rem;
  animation: dz-spin 1s linear infinite;
}
