/*!
 * Brickwork CSS v0.1.0
 * Framework-agnostic UI substrate. Generated by @brickwork/css build.
 * Layers: tokens -> reset -> primitives -> layout -> shell -> nav -> components -> marketing -> wave-b -> wave-e
 */

/* ==== layer: tokens ==== */
/* @brickwork/tokens design tokens. GENERATED from packages/tokens/src/dtcg/**.
 * Do NOT edit by hand: run `pnpm --filter @brickwork/tokens build`. Names are
 * the versioned public contract; values are brand-overridable. oklch, dark
 * authored not derived. */


:root {
  --bw-space-0: 0;
  --bw-space-1: 0.25rem;
  --bw-space-2: 0.5rem;
  --bw-space-3: 0.75rem;
  --bw-space-4: 1rem;
  --bw-space-5: 1.25rem;
  --bw-space-6: 1.5rem;
  --bw-space-7: 1.75rem;
  --bw-space-8: 2rem;
  --bw-space-9: 2.25rem;
  --bw-space-10: 2.5rem;
  --bw-space-11: 2.75rem;
  --bw-space-12: 3rem;
  --bw-space-16: 4rem;
  --bw-space-20: 5rem;
  --bw-space-24: 6rem;
  --bw-space-px: 1px;
  --bw-space-0-5: 0.125rem;
  --bw-space-1-5: 0.375rem;
  --bw-radius-none: 0;
  --bw-radius-sm: 0.25rem;
  --bw-radius-md: 0.375rem;
  --bw-radius-lg: 0.5rem;
  --bw-radius-xl: 0.75rem;
  --bw-radius-2xl: 1rem;
  --bw-radius-full: 9999px;
  --bw-size-border-hairline: 1px;
  --bw-size-border-thick: 2px;
  --bw-size-border-nav-marker: 3px;
  --bw-component-icon-size-sm: 1rem;
  --bw-component-icon-size-md: 1.25rem;
  --bw-component-icon-size-lg: 1.5rem;
  --bw-component-icon-size-xl: 2rem;
  --bw-component-icon-size-2xl: 2.5rem;
  --bw-size-control-height-sm: 2rem;
  --bw-size-control-height-md: var(--bw-density-control-height);
  --bw-size-control-height-lg: 3rem;
  --bw-size-touch-target-min: 2.75rem;
  --bw-size-max-width-prose: 65ch;
  --bw-size-max-width-form: 32rem;
  --bw-size-max-width-modal-sm: 28rem;
  --bw-size-max-width-modal-md: 32rem;
  --bw-size-max-width-modal-lg: 48rem;
  --bw-component-icon-size-sm: 1rem;
  --bw-component-icon-size-md: 1.25rem;
  --bw-component-icon-size-lg: 1.5rem;
  --bw-component-icon-size-xl: 2rem;
  --bw-component-icon-size-2xl: 2.5rem;
  --bw-component-icon-stroke-width: 2;
  --bw-component-button-radius: var(--bw-radius-md);
  --bw-component-button-elevation: var(--bw-elevation-1);
  --bw-component-button-elevation-hover: var(--bw-component-button-elevation);
  --bw-component-button-hover-translate: 0;
  --bw-component-menu-min-width: 12rem;
  --bw-component-select-indicator: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath fill='none' stroke='oklch(0.55 0.01 265)' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round' d='m4 6 4 4 4-4'/%3E%3C/svg%3E");
  --bw-component-checkbox-glyph: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' d='m2.5 6.5 2.5 2.5 4.5-5.5'/%3E%3C/svg%3E");
  --bw-component-toggle-track-width: 2.25rem;
  --bw-component-toggle-track-height: 1.25rem;
  --bw-component-toggle-thumb-size: 0.875rem;
  --bw-component-toggle-thumb-inset: 0.25rem;
  --bw-component-stat-tile-value-size: var(--bw-font-size-3xl);
  --bw-component-stat-tile-sparkline-height: 2rem;
  --bw-component-progress-track-height: 0.5rem;
  --bw-component-ranked-list-bar-thickness: 0.375rem;
  --bw-component-ranked-list-row-gap: 0.75rem;
  --bw-component-sparkline-stroke-width: 2;
  --bw-component-sparkline-marker-radius: 2.5;
  --bw-component-tooltip-max-width: 16rem;
  --bw-component-disabled-opacity: 0.5;
  --bw-component-content-max-width: 72rem;
  --bw-component-content-max-width-marketing: 80rem;
  --bw-component-docs-nav-inline-size: 16rem;
  --bw-component-section-gap-marketing: 6rem;
  --bw-component-hero-copy-max-width: var(--bw-size-max-width-prose);
  --bw-component-hero-heading-max-width: 100%;
  --bw-component-hero-lede-max-width: 100%;
  --bw-component-hero-decoration-opacity: 0.16;
  --bw-component-hero-decoration-inset: 2rem;
  --bw-component-hero-decoration-size: 14rem;
  --bw-component-hero-eyebrow-marker-inline-size: 2.4rem;
  --bw-component-hero-eyebrow-marker-block-size: 2px;
  --bw-component-hero-eyebrow-marker-color: var(--bw-color-accent);
  --bw-component-hero-media-circle-size: 14rem;
  --bw-component-logo-height: 2rem;
  --bw-component-topbar-position: sticky;
  --bw-component-drawer-width: min(20rem, 80vw);
  --bw-component-toast-max-width: 24rem;
  --bw-component-htmx-indicator-opacity: 0.6;
  --bw-focus-ring-width: 2px;
  --bw-focus-ring-offset: 2px;
  --bw-focus-ring-style: solid;
  --bw-z-base: 0;
  --bw-z-dropdown: 10;
  --bw-z-sticky: 20;
  --bw-z-drawer: 30;
  --bw-z-overlay: 40;
  --bw-z-modal: 50;
  --bw-z-skip-link: 60;
  --bw-z-toast: 70;
  --bw-z-tooltip: 80;
  --bw-opacity-muted: 0.7;
  --bw-opacity-sort-idle: 0.4;
  --bw-font-family-sans: system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji';
  --bw-font-family-display: ui-serif, 'Iowan Old Style', 'Palatino Linotype', Palatino, Georgia, 'Times New Roman', Times, serif;
  --bw-font-family-mono: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, 'Liberation Mono', 'Courier New', monospace;
  --bw-font-size-2xs: 0.6875rem;
  --bw-font-size-xs: 0.75rem;
  --bw-font-size-sm: 0.875rem;
  --bw-font-size-md: 1rem;
  --bw-font-size-lg: 1.125rem;
  --bw-font-size-xl: 1.5rem;
  --bw-font-size-2xl: 2rem;
  --bw-font-size-3xl: 2.5rem;
  --bw-font-size-4xl: 3rem;
  --bw-font-size-5xl: 3.75rem;
  --bw-font-weight-normal: 400;
  --bw-font-weight-medium: 500;
  --bw-font-weight-semibold: 600;
  --bw-font-weight-bold: 700;
  --bw-font-line-height-none: 1;
  --bw-font-line-height-tight: 1.25;
  --bw-font-line-height-snug: 1.375;
  --bw-font-line-height-normal: 1.5;
  --bw-font-line-height-relaxed: 1.625;
  --bw-font-line-height-loose: 2;
  --bw-font-tracking-tight: -0.025em;
  --bw-font-tracking-normal: 0em;
  --bw-font-tracking-wide: 0.025em;
  --bw-font-tracking-wider: 0.05em;
  --bw-component-gauge-diameter-sm: 4rem;
  --bw-component-gauge-diameter-md: 5rem;
  --bw-component-gauge-diameter-lg: 6rem;
  --bw-component-gauge-stroke-width: 8;
  --bw-component-scorecard-columns-base: 1;
  --bw-component-scorecard-columns-sm: 2;
  --bw-component-scorecard-columns-lg: 4;
  --bw-text-heading-display-family: var(--bw-font-family-display);
  --bw-text-heading-display-size: var(--bw-font-size-5xl);
  --bw-text-heading-display-line-height: var(--bw-font-line-height-tight);
  --bw-text-heading-display-weight: var(--bw-font-weight-bold);
  --bw-text-heading-display-tracking: var(--bw-font-tracking-tight);
  --bw-text-heading-2xl-family: var(--bw-font-family-display);
  --bw-text-heading-2xl-size: var(--bw-font-size-2xl);
  --bw-text-heading-2xl-line-height: var(--bw-font-line-height-tight);
  --bw-text-heading-2xl-weight: var(--bw-font-weight-bold);
  --bw-text-heading-2xl-tracking: var(--bw-font-tracking-tight);
  --bw-text-heading-xl-family: var(--bw-font-family-display);
  --bw-text-heading-xl-size: var(--bw-font-size-xl);
  --bw-text-heading-xl-line-height: var(--bw-font-line-height-tight);
  --bw-text-heading-xl-weight: var(--bw-font-weight-semibold);
  --bw-text-heading-xl-tracking: var(--bw-font-tracking-tight);
  --bw-text-heading-section-family: var(--bw-font-family-display);
  --bw-text-heading-section-size: var(--bw-font-size-xl);
  --bw-text-heading-section-line-height: var(--bw-font-line-height-tight);
  --bw-text-heading-section-weight: var(--bw-font-weight-semibold);
  --bw-text-heading-section-tracking: var(--bw-font-tracking-tight);
  --bw-text-heading-lg-family: var(--bw-font-family-sans);
  --bw-text-heading-lg-size: var(--bw-font-size-lg);
  --bw-text-heading-lg-line-height: var(--bw-font-line-height-snug);
  --bw-text-heading-lg-weight: var(--bw-font-weight-semibold);
  --bw-text-heading-lg-tracking: var(--bw-font-tracking-normal);
  --bw-text-heading-md-family: var(--bw-font-family-sans);
  --bw-text-heading-md-size: var(--bw-font-size-md);
  --bw-text-heading-md-line-height: var(--bw-font-line-height-snug);
  --bw-text-heading-md-weight: var(--bw-font-weight-semibold);
  --bw-text-heading-sm-family: var(--bw-font-family-sans);
  --bw-text-heading-sm-size: var(--bw-font-size-sm);
  --bw-text-heading-sm-line-height: var(--bw-font-line-height-snug);
  --bw-text-heading-sm-weight: var(--bw-font-weight-semibold);
  --bw-text-body-lg-size: var(--bw-font-size-lg);
  --bw-text-body-lg-line-height: var(--bw-font-line-height-relaxed);
  --bw-text-body-lg-weight: var(--bw-font-weight-normal);
  --bw-text-body-md-family: var(--bw-font-family-sans);
  --bw-text-body-md-size: var(--bw-font-size-md);
  --bw-text-body-md-line-height: var(--bw-font-line-height-normal);
  --bw-text-body-md-weight: var(--bw-font-weight-normal);
  --bw-text-body-sm-size: var(--bw-font-size-sm);
  --bw-text-body-sm-line-height: var(--bw-font-line-height-normal);
  --bw-text-body-sm-weight: var(--bw-font-weight-normal);
  --bw-text-label-family: var(--bw-font-family-sans);
  --bw-text-label-size: var(--bw-font-size-sm);
  --bw-text-label-line-height: var(--bw-font-line-height-none);
  --bw-text-label-weight: var(--bw-font-weight-medium);
  --bw-text-label-tracking: var(--bw-font-tracking-normal);
  --bw-text-caption-family: var(--bw-font-family-sans);
  --bw-text-caption-size: var(--bw-font-size-xs);
  --bw-text-caption-line-height: var(--bw-font-line-height-normal);
  --bw-text-caption-weight: var(--bw-font-weight-normal);
  --bw-text-caption-tracking: var(--bw-font-tracking-normal);
  --bw-text-overline-family: var(--bw-font-family-sans);
  --bw-text-overline-size: var(--bw-font-size-2xs);
  --bw-text-overline-line-height: var(--bw-font-line-height-none);
  --bw-text-overline-weight: var(--bw-font-weight-semibold);
  --bw-text-overline-tracking: var(--bw-font-tracking-wider);
  --bw-text-code-family: var(--bw-font-family-mono);
  --bw-text-code-size: var(--bw-font-size-sm);
  --bw-text-code-line-height: var(--bw-font-line-height-normal);
  --bw-text-code-weight: var(--bw-font-weight-normal);
  --bw-duration-instant: 1ms;
  --bw-duration-fast: 100ms;
  --bw-duration-normal: 200ms;
  --bw-duration-moderate: 250ms;
  --bw-duration-slow: 400ms;
  --bw-duration-shimmer: 1500ms;
  --bw-duration-spin: 700ms;
  --bw-duration-toast-short: 4000ms;
  --bw-duration-toast-normal: 6000ms;
  --bw-duration-toast-long: 10000ms;
  --bw-debounce-search: 300ms;
  --bw-ease-linear: linear;
  --bw-ease-standard: cubic-bezier(0.4, 0, 0.2, 1);
  --bw-ease-in: cubic-bezier(0.4, 0, 1, 1);
  --bw-ease-out: cubic-bezier(0, 0, 0.2, 1);
  --bw-ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
  --bw-ease-emphasised: cubic-bezier(0.2, 0, 0, 1);
  --bw-transition-colors: color var(--bw-duration-fast) var(--bw-ease-standard), background-color var(--bw-duration-fast) var(--bw-ease-standard), border-color var(--bw-duration-fast) var(--bw-ease-standard);
  --bw-transition-opacity: opacity var(--bw-duration-fast) var(--bw-ease-standard);
  --bw-transition-transform: transform var(--bw-duration-normal) var(--bw-ease-in-out);
  --bw-transition-shadow: box-shadow var(--bw-duration-fast) var(--bw-ease-standard);
  --bw-color-surface: oklch(1 0 0);
  --bw-color-surface-sunken: color-mix(in oklab, var(--bw-color-surface) 96.5%, black);
  --bw-color-surface-raised: var(--bw-color-surface);
  --bw-color-surface-inverse: var(--bw-color-fg);
  --bw-color-surface-overlay: color-mix(in oklab, var(--bw-color-surface-inverse) 50%, transparent);
  --bw-color-fg: oklch(0.205 0.005 265);
  --bw-color-fg-muted: color-mix(in oklab, var(--bw-color-fg) 62%, var(--bw-color-surface));
  --bw-color-fg-subtle: color-mix(in oklab, var(--bw-color-fg) 37%, var(--bw-color-surface));
  --bw-color-fg-on-accent: oklch(1 0 0);
  --bw-color-fg-on-inverse: var(--bw-color-surface);
  --bw-color-status-fg-ink: oklch(0.205 0.005 265);
  --bw-color-border: oklch(0.928 0.004 265);
  --bw-color-border-strong: color-mix(in oklab, var(--bw-color-border) 94%, black);
  --bw-color-border-control: color-mix(in oklab, var(--bw-color-fg) 44%, var(--bw-color-surface));
  --bw-color-action-disabled-bg: color-mix(in oklab, var(--bw-color-surface) 96.5%, black);
  --bw-color-action-disabled-text: color-mix(in oklab, var(--bw-color-fg) 62%, var(--bw-color-surface));
  --bw-color-action-disabled-border: color-mix(in oklab, var(--bw-color-fg) 44%, var(--bw-color-surface));
  --bw-color-accent: oklch(0.546 0.192 262);
  --bw-color-accent-hover: color-mix(in oklab, var(--bw-color-accent) 89%, black);
  --bw-color-accent-subtle: color-mix(in oklab, var(--bw-color-accent) 7%, var(--bw-color-surface));
  --bw-color-danger: oklch(0.577 0.215 27);
  --bw-color-danger-subtle: color-mix(in oklab, var(--bw-color-danger) 7%, var(--bw-color-surface));
  --bw-color-danger-border: color-mix(in oklab, var(--bw-color-danger) 30%, var(--bw-color-surface));
  --bw-color-danger-strong: color-mix(in oklab, var(--bw-color-danger) 88%, black);
  --bw-color-danger-fg: color-mix(in oklab, var(--bw-color-danger) 87%, var(--bw-color-status-fg-ink));
  --bw-color-danger-on-fg: oklch(1 0 0);
  --bw-color-warning: oklch(0.666 0.163 58);
  --bw-color-warning-subtle: color-mix(in oklab, var(--bw-color-warning) 7%, var(--bw-color-surface));
  --bw-color-warning-border: color-mix(in oklab, var(--bw-color-warning) 30%, var(--bw-color-surface));
  --bw-color-warning-strong: color-mix(in oklab, var(--bw-color-warning) 88%, black);
  --bw-color-warning-fg: color-mix(in oklab, var(--bw-color-warning) 68%, var(--bw-color-status-fg-ink));
  --bw-color-warning-on-fg: oklch(0.145 0.004 265);
  --bw-color-success: oklch(0.627 0.155 149);
  --bw-color-success-subtle: color-mix(in oklab, var(--bw-color-success) 7%, var(--bw-color-surface));
  --bw-color-success-border: color-mix(in oklab, var(--bw-color-success) 30%, var(--bw-color-surface));
  --bw-color-success-strong: color-mix(in oklab, var(--bw-color-success) 88%, black);
  --bw-color-success-fg: color-mix(in oklab, var(--bw-color-success) 69%, var(--bw-color-status-fg-ink));
  --bw-color-success-on-fg: oklch(0.145 0.004 265);
  --bw-color-info: oklch(0.6 0.11 225);
  --bw-color-info-subtle: color-mix(in oklab, var(--bw-color-info) 7%, var(--bw-color-surface));
  --bw-color-info-border: color-mix(in oklab, var(--bw-color-info) 30%, var(--bw-color-surface));
  --bw-color-info-strong: color-mix(in oklab, var(--bw-color-info) 88%, black);
  --bw-color-info-fg: color-mix(in oklab, var(--bw-color-info) 75%, var(--bw-color-status-fg-ink));
  --bw-color-info-on-fg: oklch(0.145 0.004 265);
  --bw-color-icon-muted: var(--bw-color-fg-subtle);
  --bw-color-focus-ring: color-mix(in oklab, var(--bw-color-accent) 95%, black);
  --bw-component-nav-item-active-bg: var(--bw-color-accent-subtle);
  --bw-component-nav-item-active-text: var(--bw-color-accent-hover);
  --bw-component-nav-item-active-border: var(--bw-color-accent);
  --bw-component-nav-item-disabled-text: var(--bw-color-fg-subtle);
  --bw-component-nav-section-text: var(--bw-color-fg-muted);
  --bw-component-breadcrumb-current: var(--bw-color-fg);
  --bw-component-breadcrumb-separator: var(--bw-color-fg-subtle);
  --bw-component-skeleton-bg: color-mix(in oklab, var(--bw-color-surface-sunken) 96%, black);
  --bw-component-skeleton-shimmer: color-mix(in oklab, var(--bw-component-skeleton-bg) 96%, black);
  --bw-color-surface-marketing-tint: color-mix(in oklab, var(--bw-color-accent) 8%, var(--bw-color-surface));
  --bw-color-stage-wash: color-mix(in oklab, var(--bw-color-accent) 8%, transparent);
  --bw-color-stage-grid: color-mix(in oklab, var(--bw-color-border) 60%, transparent);
  --bw-color-chart-1: oklch(0.590 0.151 46);
  --bw-color-chart-2: oklch(0.397 0.065 92);
  --bw-color-chart-3: oklch(0.639 0.151 130);
  --bw-color-chart-4: oklch(0.260 0.033 175);
  --bw-color-chart-5: oklch(0.552 0.095 226);
  --bw-color-chart-6: oklch(0.501 0.257 268);
  --bw-color-chart-7: oklch(0.357 0.159 308);
  --bw-color-chart-8: oklch(0.468 0.167 358);
  --bw-color-chart-axis: oklch(0.45 0.004 265);
  --bw-color-chart-grid: oklch(0.922 0.003 265);
  --bw-color-chart-axis-label: oklch(0.48 0.003 265);
  --bw-color-chart-tooltip-bg: oklch(0.205 0.005 265);
  --bw-color-chart-tooltip-text: oklch(0.985 0.001 265);
  --bw-color-chart-tooltip-border: oklch(0.279 0.006 265);
  --bw-color-sparkline-stroke: oklch(0.590 0.151 46);
  --bw-state-hover-overlay: oklch(0 0 0 / 0.04);
  --bw-state-active-overlay: oklch(0 0 0 / 0.08);
  --bw-state-selected-bg: var(--bw-color-accent-subtle);
  --bw-elevation-0: none;
  --bw-elevation-1: 0 1px 2px 0 oklch(0 0 0 / 0.05);
  --bw-elevation-2: 0 1px 3px 0 oklch(0 0 0 / 0.1), 0 1px 2px -1px oklch(0 0 0 / 0.1);
  --bw-elevation-3: 0 4px 6px -1px oklch(0 0 0 / 0.1), 0 2px 4px -2px oklch(0 0 0 / 0.1);
  --bw-elevation-4: 0 10px 15px -3px oklch(0 0 0 / 0.1), 0 4px 6px -4px oklch(0 0 0 / 0.1);
  --bw-elevation-5: 0 20px 25px -5px oklch(0 0 0 / 0.1), 0 8px 10px -6px oklch(0 0 0 / 0.1);
  --bw-density-control-height: 2.5rem;
  --bw-density-control-padding-inline: 0.75rem;
  --bw-density-row-padding-block: 0.625rem;
  --bw-density-row-padding-inline: 0.75rem;
  --bw-density-stack-gap: 1rem;
  --bw-density-sidebar-width: 16rem;
  --bw-density-sidebar-width-collapsed: 4rem;
  --bw-density-rail-width: 3.25rem;
  --bw-density-rail-width-expanded: 13.75rem;
  --bw-density-topbar-height: 3.5rem;
  --bw-density-section-gap: 2rem;
  --bw-density-card-padding: 1rem;
  --bw-density-page-gutter-inline: 1.5rem;
  --bw-breakpoint-sm: 40rem;
  --bw-breakpoint-md: 48rem;
  --bw-breakpoint-lg: 64rem;
  --bw-breakpoint-xl: 80rem;
}

[data-theme="light"] {
  --bw-color-surface: oklch(1 0 0);
  --bw-color-surface-sunken: color-mix(in oklab, var(--bw-color-surface) 96.5%, black);
  --bw-color-surface-raised: var(--bw-color-surface);
  --bw-color-surface-inverse: var(--bw-color-fg);
  --bw-color-surface-overlay: color-mix(in oklab, var(--bw-color-surface-inverse) 50%, transparent);
  --bw-color-fg: oklch(0.205 0.005 265);
  --bw-color-fg-muted: color-mix(in oklab, var(--bw-color-fg) 62%, var(--bw-color-surface));
  --bw-color-fg-subtle: color-mix(in oklab, var(--bw-color-fg) 37%, var(--bw-color-surface));
  --bw-color-fg-on-accent: oklch(1 0 0);
  --bw-color-fg-on-inverse: var(--bw-color-surface);
  --bw-color-status-fg-ink: oklch(0.205 0.005 265);
  --bw-color-border: oklch(0.928 0.004 265);
  --bw-color-border-strong: color-mix(in oklab, var(--bw-color-border) 94%, black);
  --bw-color-border-control: color-mix(in oklab, var(--bw-color-fg) 44%, var(--bw-color-surface));
  --bw-color-action-disabled-bg: color-mix(in oklab, var(--bw-color-surface) 96.5%, black);
  --bw-color-action-disabled-text: color-mix(in oklab, var(--bw-color-fg) 62%, var(--bw-color-surface));
  --bw-color-action-disabled-border: color-mix(in oklab, var(--bw-color-fg) 44%, var(--bw-color-surface));
  --bw-color-accent: oklch(0.546 0.192 262);
  --bw-color-accent-hover: color-mix(in oklab, var(--bw-color-accent) 89%, black);
  --bw-color-accent-subtle: color-mix(in oklab, var(--bw-color-accent) 7%, var(--bw-color-surface));
  --bw-color-danger: oklch(0.577 0.215 27);
  --bw-color-danger-subtle: color-mix(in oklab, var(--bw-color-danger) 7%, var(--bw-color-surface));
  --bw-color-danger-border: color-mix(in oklab, var(--bw-color-danger) 30%, var(--bw-color-surface));
  --bw-color-danger-strong: color-mix(in oklab, var(--bw-color-danger) 88%, black);
  --bw-color-danger-fg: color-mix(in oklab, var(--bw-color-danger) 87%, var(--bw-color-status-fg-ink));
  --bw-color-danger-on-fg: oklch(1 0 0);
  --bw-color-warning: oklch(0.666 0.163 58);
  --bw-color-warning-subtle: color-mix(in oklab, var(--bw-color-warning) 7%, var(--bw-color-surface));
  --bw-color-warning-border: color-mix(in oklab, var(--bw-color-warning) 30%, var(--bw-color-surface));
  --bw-color-warning-strong: color-mix(in oklab, var(--bw-color-warning) 88%, black);
  --bw-color-warning-fg: color-mix(in oklab, var(--bw-color-warning) 68%, var(--bw-color-status-fg-ink));
  --bw-color-warning-on-fg: oklch(0.145 0.004 265);
  --bw-color-success: oklch(0.627 0.155 149);
  --bw-color-success-subtle: color-mix(in oklab, var(--bw-color-success) 7%, var(--bw-color-surface));
  --bw-color-success-border: color-mix(in oklab, var(--bw-color-success) 30%, var(--bw-color-surface));
  --bw-color-success-strong: color-mix(in oklab, var(--bw-color-success) 88%, black);
  --bw-color-success-fg: color-mix(in oklab, var(--bw-color-success) 69%, var(--bw-color-status-fg-ink));
  --bw-color-success-on-fg: oklch(0.145 0.004 265);
  --bw-color-info: oklch(0.6 0.11 225);
  --bw-color-info-subtle: color-mix(in oklab, var(--bw-color-info) 7%, var(--bw-color-surface));
  --bw-color-info-border: color-mix(in oklab, var(--bw-color-info) 30%, var(--bw-color-surface));
  --bw-color-info-strong: color-mix(in oklab, var(--bw-color-info) 88%, black);
  --bw-color-info-fg: color-mix(in oklab, var(--bw-color-info) 75%, var(--bw-color-status-fg-ink));
  --bw-color-info-on-fg: oklch(0.145 0.004 265);
  --bw-color-icon-muted: var(--bw-color-fg-subtle);
  --bw-color-focus-ring: color-mix(in oklab, var(--bw-color-accent) 95%, black);
  --bw-component-nav-item-active-bg: var(--bw-color-accent-subtle);
  --bw-component-nav-item-active-text: var(--bw-color-accent-hover);
  --bw-component-nav-item-active-border: var(--bw-color-accent);
  --bw-component-nav-item-disabled-text: var(--bw-color-fg-subtle);
  --bw-component-nav-section-text: var(--bw-color-fg-muted);
  --bw-component-breadcrumb-current: var(--bw-color-fg);
  --bw-component-breadcrumb-separator: var(--bw-color-fg-subtle);
  --bw-component-skeleton-bg: color-mix(in oklab, var(--bw-color-surface-sunken) 96%, black);
  --bw-component-skeleton-shimmer: color-mix(in oklab, var(--bw-component-skeleton-bg) 96%, black);
  --bw-color-surface-marketing-tint: color-mix(in oklab, var(--bw-color-accent) 8%, var(--bw-color-surface));
  --bw-color-stage-wash: color-mix(in oklab, var(--bw-color-accent) 8%, transparent);
  --bw-color-stage-grid: color-mix(in oklab, var(--bw-color-border) 60%, transparent);
  --bw-color-chart-1: oklch(0.590 0.151 46);
  --bw-color-chart-2: oklch(0.397 0.065 92);
  --bw-color-chart-3: oklch(0.639 0.151 130);
  --bw-color-chart-4: oklch(0.260 0.033 175);
  --bw-color-chart-5: oklch(0.552 0.095 226);
  --bw-color-chart-6: oklch(0.501 0.257 268);
  --bw-color-chart-7: oklch(0.357 0.159 308);
  --bw-color-chart-8: oklch(0.468 0.167 358);
  --bw-color-chart-axis: oklch(0.45 0.004 265);
  --bw-color-chart-grid: oklch(0.922 0.003 265);
  --bw-color-chart-axis-label: oklch(0.48 0.003 265);
  --bw-color-chart-tooltip-bg: oklch(0.205 0.005 265);
  --bw-color-chart-tooltip-text: oklch(0.985 0.001 265);
  --bw-color-chart-tooltip-border: oklch(0.279 0.006 265);
  --bw-color-sparkline-stroke: oklch(0.590 0.151 46);
  --bw-state-hover-overlay: oklch(0 0 0 / 0.04);
  --bw-state-active-overlay: oklch(0 0 0 / 0.08);
  --bw-state-selected-bg: var(--bw-color-accent-subtle);
  --bw-elevation-0: none;
  --bw-elevation-1: 0 1px 2px 0 oklch(0 0 0 / 0.05);
  --bw-elevation-2: 0 1px 3px 0 oklch(0 0 0 / 0.1), 0 1px 2px -1px oklch(0 0 0 / 0.1);
  --bw-elevation-3: 0 4px 6px -1px oklch(0 0 0 / 0.1), 0 2px 4px -2px oklch(0 0 0 / 0.1);
  --bw-elevation-4: 0 10px 15px -3px oklch(0 0 0 / 0.1), 0 4px 6px -4px oklch(0 0 0 / 0.1);
  --bw-elevation-5: 0 20px 25px -5px oklch(0 0 0 / 0.1), 0 8px 10px -6px oklch(0 0 0 / 0.1);
}

[data-theme="dark"] {
  --bw-color-surface: oklch(0.18 0.005 265);
  --bw-color-surface-sunken: color-mix(in oklab, var(--bw-color-surface) 76%, black);
  --bw-color-surface-raised: color-mix(in oklab, var(--bw-color-surface) 93%, white);
  --bw-color-surface-inverse: var(--bw-color-fg);
  --bw-color-surface-overlay: oklch(0 0 0 / 0.6);
  --bw-color-fg: oklch(0.93 0.002 265);
  --bw-color-fg-muted: color-mix(in oklab, var(--bw-color-fg) 72%, var(--bw-color-surface));
  --bw-color-fg-subtle: color-mix(in oklab, var(--bw-color-fg) 49%, var(--bw-color-surface));
  --bw-color-fg-on-accent: oklch(0.145 0.004 265);
  --bw-color-fg-on-inverse: var(--bw-color-surface);
  --bw-color-status-fg-ink: oklch(0.93 0.002 265);
  --bw-color-border: oklch(0.269 0.006 265);
  --bw-color-border-strong: color-mix(in oklab, var(--bw-color-border) 86%, white);
  --bw-color-border-control: color-mix(in oklab, var(--bw-color-fg) 45%, var(--bw-color-surface));
  --bw-color-action-disabled-bg: color-mix(in oklab, var(--bw-color-surface) 76%, black);
  --bw-color-action-disabled-text: color-mix(in oklab, var(--bw-color-fg) 72%, var(--bw-color-surface));
  --bw-color-action-disabled-border: color-mix(in oklab, var(--bw-color-fg) 45%, var(--bw-color-surface));
  --bw-color-accent: oklch(0.707 0.165 254);
  --bw-color-accent-hover: color-mix(in oklab, var(--bw-color-accent) 88%, black);
  --bw-color-accent-subtle: color-mix(in oklab, var(--bw-color-accent) 30%, black);
  --bw-color-danger: oklch(0.637 0.208 25);
  --bw-color-danger-subtle: color-mix(in oklab, var(--bw-color-danger) 26%, black);
  --bw-color-danger-border: color-mix(in oklab, var(--bw-color-danger) 55%, black);
  --bw-color-danger-strong: color-mix(in oklab, var(--bw-color-danger) 96%, white);
  --bw-color-danger-fg: color-mix(in oklab, var(--bw-color-danger) 73%, white);
  --bw-color-danger-on-fg: oklch(0.145 0.004 265);
  --bw-color-warning: oklch(0.769 0.166 70);
  --bw-color-warning-subtle: color-mix(in oklab, var(--bw-color-warning) 22%, black);
  --bw-color-warning-border: color-mix(in oklab, var(--bw-color-warning) 55%, black);
  --bw-color-warning-strong: color-mix(in oklab, var(--bw-color-warning) 96%, white);
  --bw-color-warning-fg: color-mix(in oklab, var(--bw-color-warning) 78%, white);
  --bw-color-warning-on-fg: oklch(0.145 0.004 265);
  --bw-color-success: oklch(0.723 0.169 152);
  --bw-color-success-subtle: color-mix(in oklab, var(--bw-color-success) 23%, black);
  --bw-color-success-border: color-mix(in oklab, var(--bw-color-success) 55%, black);
  --bw-color-success-strong: color-mix(in oklab, var(--bw-color-success) 96%, white);
  --bw-color-success-fg: color-mix(in oklab, var(--bw-color-success) 78%, white);
  --bw-color-success-on-fg: oklch(0.145 0.004 265);
  --bw-color-info: oklch(0.72 0.11 220);
  --bw-color-info-subtle: color-mix(in oklab, var(--bw-color-info) 26%, black);
  --bw-color-info-border: color-mix(in oklab, var(--bw-color-info) 55%, black);
  --bw-color-info-strong: color-mix(in oklab, var(--bw-color-info) 96%, white);
  --bw-color-info-fg: color-mix(in oklab, var(--bw-color-info) 78%, white);
  --bw-color-info-on-fg: oklch(0.145 0.004 265);
  --bw-color-icon-muted: var(--bw-color-fg-subtle);
  --bw-color-focus-ring: color-mix(in oklab, var(--bw-color-accent) 82%, black);
  --bw-component-nav-item-active-bg: var(--bw-color-accent-subtle);
  --bw-component-nav-item-active-text: color-mix(in oklab, var(--bw-color-accent) 25%, white);
  --bw-component-nav-item-active-border: var(--bw-color-accent);
  --bw-component-nav-item-disabled-text: var(--bw-color-fg-subtle);
  --bw-component-nav-section-text: var(--bw-color-fg-muted);
  --bw-component-breadcrumb-current: var(--bw-color-fg);
  --bw-component-breadcrumb-separator: var(--bw-color-fg-subtle);
  --bw-component-skeleton-bg: color-mix(in oklab, var(--bw-color-surface-sunken) 82%, white);
  --bw-component-skeleton-shimmer: color-mix(in oklab, var(--bw-component-skeleton-bg) 86%, white);
  --bw-color-surface-marketing-tint: color-mix(in oklab, var(--bw-color-accent) 12%, var(--bw-color-surface));
  --bw-color-stage-wash: color-mix(in oklab, var(--bw-color-accent) 16%, transparent);
  --bw-color-stage-grid: color-mix(in oklab, var(--bw-color-border) 70%, transparent);
  --bw-color-chart-1: oklch(0.595 0.138 46);
  --bw-color-chart-2: oklch(0.874 0.163 92);
  --bw-color-chart-3: oklch(0.709 0.170 130);
  --bw-color-chart-4: oklch(0.529 0.086 175);
  --bw-color-chart-5: oklch(0.739 0.121 226);
  --bw-color-chart-6: oklch(0.881 0.054 268);
  --bw-color-chart-7: oklch(0.612 0.168 308);
  --bw-color-chart-8: oklch(0.756 0.135 358);
  --bw-color-chart-axis: oklch(0.62 0.004 265);
  --bw-color-chart-grid: oklch(0.33 0.004 265);
  --bw-color-chart-axis-label: oklch(0.72 0.003 265);
  --bw-color-chart-tooltip-bg: oklch(0.279 0.006 265);
  --bw-color-chart-tooltip-text: oklch(0.985 0.001 265);
  --bw-color-chart-tooltip-border: oklch(0.371 0.007 265);
  --bw-color-sparkline-stroke: oklch(0.595 0.138 46);
  --bw-state-hover-overlay: oklch(1 0 0 / 0.06);
  --bw-state-active-overlay: oklch(1 0 0 / 0.12);
  --bw-state-selected-bg: var(--bw-color-accent-subtle);
  --bw-elevation-0: none;
  --bw-elevation-1: 0 1px 2px 0 oklch(0 0 0 / 0.3), inset 0 1px 0 0 oklch(1 0 0 / 0.03);
  --bw-elevation-2: 0 1px 3px 0 oklch(0 0 0 / 0.36), 0 1px 2px -1px oklch(0 0 0 / 0.3), inset 0 1px 0 0 oklch(1 0 0 / 0.04);
  --bw-elevation-3: 0 4px 6px -1px oklch(0 0 0 / 0.4), 0 2px 4px -2px oklch(0 0 0 / 0.3), inset 0 1px 0 0 oklch(1 0 0 / 0.04);
  --bw-elevation-4: 0 10px 15px -3px oklch(0 0 0 / 0.45), 0 4px 6px -4px oklch(0 0 0 / 0.35), inset 0 1px 0 0 oklch(1 0 0 / 0.05);
  --bw-elevation-5: 0 20px 25px -5px oklch(0 0 0 / 0.5), 0 8px 10px -6px oklch(0 0 0 / 0.4), inset 0 1px 0 0 oklch(1 0 0 / 0.06);
}

.dark {
  --bw-color-surface: oklch(0.18 0.005 265);
  --bw-color-surface-sunken: color-mix(in oklab, var(--bw-color-surface) 76%, black);
  --bw-color-surface-raised: color-mix(in oklab, var(--bw-color-surface) 93%, white);
  --bw-color-surface-inverse: var(--bw-color-fg);
  --bw-color-surface-overlay: oklch(0 0 0 / 0.6);
  --bw-color-fg: oklch(0.93 0.002 265);
  --bw-color-fg-muted: color-mix(in oklab, var(--bw-color-fg) 72%, var(--bw-color-surface));
  --bw-color-fg-subtle: color-mix(in oklab, var(--bw-color-fg) 49%, var(--bw-color-surface));
  --bw-color-fg-on-accent: oklch(0.145 0.004 265);
  --bw-color-fg-on-inverse: var(--bw-color-surface);
  --bw-color-status-fg-ink: oklch(0.93 0.002 265);
  --bw-color-border: oklch(0.269 0.006 265);
  --bw-color-border-strong: color-mix(in oklab, var(--bw-color-border) 86%, white);
  --bw-color-border-control: color-mix(in oklab, var(--bw-color-fg) 45%, var(--bw-color-surface));
  --bw-color-action-disabled-bg: color-mix(in oklab, var(--bw-color-surface) 76%, black);
  --bw-color-action-disabled-text: color-mix(in oklab, var(--bw-color-fg) 72%, var(--bw-color-surface));
  --bw-color-action-disabled-border: color-mix(in oklab, var(--bw-color-fg) 45%, var(--bw-color-surface));
  --bw-color-accent: oklch(0.707 0.165 254);
  --bw-color-accent-hover: color-mix(in oklab, var(--bw-color-accent) 88%, black);
  --bw-color-accent-subtle: color-mix(in oklab, var(--bw-color-accent) 30%, black);
  --bw-color-danger: oklch(0.637 0.208 25);
  --bw-color-danger-subtle: color-mix(in oklab, var(--bw-color-danger) 26%, black);
  --bw-color-danger-border: color-mix(in oklab, var(--bw-color-danger) 55%, black);
  --bw-color-danger-strong: color-mix(in oklab, var(--bw-color-danger) 96%, white);
  --bw-color-danger-fg: color-mix(in oklab, var(--bw-color-danger) 73%, white);
  --bw-color-danger-on-fg: oklch(0.145 0.004 265);
  --bw-color-warning: oklch(0.769 0.166 70);
  --bw-color-warning-subtle: color-mix(in oklab, var(--bw-color-warning) 22%, black);
  --bw-color-warning-border: color-mix(in oklab, var(--bw-color-warning) 55%, black);
  --bw-color-warning-strong: color-mix(in oklab, var(--bw-color-warning) 96%, white);
  --bw-color-warning-fg: color-mix(in oklab, var(--bw-color-warning) 78%, white);
  --bw-color-warning-on-fg: oklch(0.145 0.004 265);
  --bw-color-success: oklch(0.723 0.169 152);
  --bw-color-success-subtle: color-mix(in oklab, var(--bw-color-success) 23%, black);
  --bw-color-success-border: color-mix(in oklab, var(--bw-color-success) 55%, black);
  --bw-color-success-strong: color-mix(in oklab, var(--bw-color-success) 96%, white);
  --bw-color-success-fg: color-mix(in oklab, var(--bw-color-success) 78%, white);
  --bw-color-success-on-fg: oklch(0.145 0.004 265);
  --bw-color-info: oklch(0.72 0.11 220);
  --bw-color-info-subtle: color-mix(in oklab, var(--bw-color-info) 26%, black);
  --bw-color-info-border: color-mix(in oklab, var(--bw-color-info) 55%, black);
  --bw-color-info-strong: color-mix(in oklab, var(--bw-color-info) 96%, white);
  --bw-color-info-fg: color-mix(in oklab, var(--bw-color-info) 78%, white);
  --bw-color-info-on-fg: oklch(0.145 0.004 265);
  --bw-color-icon-muted: var(--bw-color-fg-subtle);
  --bw-color-focus-ring: color-mix(in oklab, var(--bw-color-accent) 82%, black);
  --bw-component-nav-item-active-bg: var(--bw-color-accent-subtle);
  --bw-component-nav-item-active-text: color-mix(in oklab, var(--bw-color-accent) 25%, white);
  --bw-component-nav-item-active-border: var(--bw-color-accent);
  --bw-component-nav-item-disabled-text: var(--bw-color-fg-subtle);
  --bw-component-nav-section-text: var(--bw-color-fg-muted);
  --bw-component-breadcrumb-current: var(--bw-color-fg);
  --bw-component-breadcrumb-separator: var(--bw-color-fg-subtle);
  --bw-component-skeleton-bg: color-mix(in oklab, var(--bw-color-surface-sunken) 82%, white);
  --bw-component-skeleton-shimmer: color-mix(in oklab, var(--bw-component-skeleton-bg) 86%, white);
  --bw-color-surface-marketing-tint: color-mix(in oklab, var(--bw-color-accent) 12%, var(--bw-color-surface));
  --bw-color-stage-wash: color-mix(in oklab, var(--bw-color-accent) 16%, transparent);
  --bw-color-stage-grid: color-mix(in oklab, var(--bw-color-border) 70%, transparent);
  --bw-color-chart-1: oklch(0.595 0.138 46);
  --bw-color-chart-2: oklch(0.874 0.163 92);
  --bw-color-chart-3: oklch(0.709 0.170 130);
  --bw-color-chart-4: oklch(0.529 0.086 175);
  --bw-color-chart-5: oklch(0.739 0.121 226);
  --bw-color-chart-6: oklch(0.881 0.054 268);
  --bw-color-chart-7: oklch(0.612 0.168 308);
  --bw-color-chart-8: oklch(0.756 0.135 358);
  --bw-color-chart-axis: oklch(0.62 0.004 265);
  --bw-color-chart-grid: oklch(0.33 0.004 265);
  --bw-color-chart-axis-label: oklch(0.72 0.003 265);
  --bw-color-chart-tooltip-bg: oklch(0.279 0.006 265);
  --bw-color-chart-tooltip-text: oklch(0.985 0.001 265);
  --bw-color-chart-tooltip-border: oklch(0.371 0.007 265);
  --bw-color-sparkline-stroke: oklch(0.595 0.138 46);
  --bw-state-hover-overlay: oklch(1 0 0 / 0.06);
  --bw-state-active-overlay: oklch(1 0 0 / 0.12);
  --bw-state-selected-bg: var(--bw-color-accent-subtle);
  --bw-elevation-0: none;
  --bw-elevation-1: 0 1px 2px 0 oklch(0 0 0 / 0.3), inset 0 1px 0 0 oklch(1 0 0 / 0.03);
  --bw-elevation-2: 0 1px 3px 0 oklch(0 0 0 / 0.36), 0 1px 2px -1px oklch(0 0 0 / 0.3), inset 0 1px 0 0 oklch(1 0 0 / 0.04);
  --bw-elevation-3: 0 4px 6px -1px oklch(0 0 0 / 0.4), 0 2px 4px -2px oklch(0 0 0 / 0.3), inset 0 1px 0 0 oklch(1 0 0 / 0.04);
  --bw-elevation-4: 0 10px 15px -3px oklch(0 0 0 / 0.45), 0 4px 6px -4px oklch(0 0 0 / 0.35), inset 0 1px 0 0 oklch(1 0 0 / 0.05);
  --bw-elevation-5: 0 20px 25px -5px oklch(0 0 0 / 0.5), 0 8px 10px -6px oklch(0 0 0 / 0.4), inset 0 1px 0 0 oklch(1 0 0 / 0.06);
}

[data-density="comfortable"] {
  --bw-density-control-height: 2.5rem;
  --bw-density-control-padding-inline: 0.75rem;
  --bw-density-row-padding-block: 0.625rem;
  --bw-density-row-padding-inline: 0.75rem;
  --bw-density-stack-gap: 1rem;
  --bw-density-sidebar-width: 16rem;
  --bw-density-sidebar-width-collapsed: 4rem;
  --bw-density-rail-width: 3.25rem;
  --bw-density-rail-width-expanded: 13.75rem;
  --bw-density-topbar-height: 3.5rem;
  --bw-density-section-gap: 2rem;
  --bw-density-card-padding: 1rem;
  --bw-density-page-gutter-inline: 1.5rem;
}

[data-density="compact"] {
  --bw-density-control-height: 2rem;
  --bw-density-control-padding-inline: 0.5rem;
  --bw-density-row-padding-block: 0.375rem;
  --bw-density-row-padding-inline: 0.5rem;
  --bw-density-stack-gap: 0.625rem;
  --bw-density-sidebar-width: 13rem;
  --bw-density-sidebar-width-collapsed: 3.5rem;
  --bw-density-rail-width: 3.25rem;
  --bw-density-rail-width-expanded: 12.5rem;
  --bw-density-topbar-height: 3rem;
  --bw-density-section-gap: 1.5rem;
  --bw-density-card-padding: 0.75rem;
  --bw-density-page-gutter-inline: 1rem;
}

[data-density="spacious"] {
  --bw-density-control-height: 3rem;
  --bw-density-control-padding-inline: 1rem;
  --bw-density-row-padding-block: 0.875rem;
  --bw-density-row-padding-inline: 1rem;
  --bw-density-stack-gap: 1.5rem;
  --bw-density-sidebar-width: 18rem;
  --bw-density-sidebar-width-collapsed: 4.5rem;
  --bw-density-rail-width: 3.5rem;
  --bw-density-rail-width-expanded: 14rem;
  --bw-density-topbar-height: 4rem;
  --bw-density-section-gap: 3rem;
  --bw-density-card-padding: 1.5rem;
  --bw-density-page-gutter-inline: 2rem;
}

:root {
  --bw-button-elevation: var(--bw-component-button-elevation);
  --bw-button-elevation-hover: var(--bw-component-button-elevation-hover);
  --bw-button-hover-translate: var(--bw-component-button-hover-translate);
  --bw-button-radius: var(--bw-component-button-radius);
  --bw-checkbox-glyph: var(--bw-component-checkbox-glyph);
  --bw-color-breadcrumb-current: var(--bw-component-breadcrumb-current);
  --bw-color-breadcrumb-separator: var(--bw-component-breadcrumb-separator);
  --bw-color-nav-item-active-bg: var(--bw-component-nav-item-active-bg);
  --bw-color-nav-item-active-border: var(--bw-component-nav-item-active-border);
  --bw-color-nav-item-active-text: var(--bw-component-nav-item-active-text);
  --bw-color-nav-item-disabled-text: var(--bw-component-nav-item-disabled-text);
  --bw-color-nav-section-text: var(--bw-component-nav-section-text);
  --bw-color-skeleton-bg: var(--bw-component-skeleton-bg);
  --bw-color-skeleton-shimmer: var(--bw-component-skeleton-shimmer);
  --bw-content-max-width: var(--bw-component-content-max-width);
  --bw-content-max-width-marketing: var(--bw-component-content-max-width-marketing);
  --bw-disabled-opacity: var(--bw-component-disabled-opacity);
  --bw-docs-nav-inline-size: var(--bw-component-docs-nav-inline-size);
  --bw-drawer-width: var(--bw-component-drawer-width);
  --bw-gauge-diameter-lg: var(--bw-component-gauge-diameter-lg);
  --bw-gauge-diameter-md: var(--bw-component-gauge-diameter-md);
  --bw-gauge-diameter-sm: var(--bw-component-gauge-diameter-sm);
  --bw-gauge-stroke-width: var(--bw-component-gauge-stroke-width);
  --bw-hero-copy-max-width: var(--bw-component-hero-copy-max-width);
  --bw-hero-decoration-inset: var(--bw-component-hero-decoration-inset);
  --bw-hero-decoration-opacity: var(--bw-component-hero-decoration-opacity);
  --bw-hero-decoration-size: var(--bw-component-hero-decoration-size);
  --bw-hero-eyebrow-marker-block-size: var(--bw-component-hero-eyebrow-marker-block-size);
  --bw-hero-eyebrow-marker-color: var(--bw-component-hero-eyebrow-marker-color);
  --bw-hero-eyebrow-marker-inline-size: var(--bw-component-hero-eyebrow-marker-inline-size);
  --bw-hero-heading-max-width: var(--bw-component-hero-heading-max-width);
  --bw-hero-lede-max-width: var(--bw-component-hero-lede-max-width);
  --bw-hero-media-circle-size: var(--bw-component-hero-media-circle-size);
  --bw-htmx-indicator-opacity: var(--bw-component-htmx-indicator-opacity);
  --bw-icon-size-2xl: var(--bw-component-icon-size-2xl);
  --bw-icon-size-lg: var(--bw-component-icon-size-lg);
  --bw-icon-size-md: var(--bw-component-icon-size-md);
  --bw-icon-size-sm: var(--bw-component-icon-size-sm);
  --bw-icon-size-xl: var(--bw-component-icon-size-xl);
  --bw-icon-stroke-width: var(--bw-component-icon-stroke-width);
  --bw-logo-height: var(--bw-component-logo-height);
  --bw-menu-min-width: var(--bw-component-menu-min-width);
  --bw-progress-track-height: var(--bw-component-progress-track-height);
  --bw-ranked-list-bar-thickness: var(--bw-component-ranked-list-bar-thickness);
  --bw-ranked-list-row-gap: var(--bw-component-ranked-list-row-gap);
  --bw-scorecard-columns-base: var(--bw-component-scorecard-columns-base);
  --bw-scorecard-columns-lg: var(--bw-component-scorecard-columns-lg);
  --bw-scorecard-columns-sm: var(--bw-component-scorecard-columns-sm);
  --bw-section-gap-marketing: var(--bw-component-section-gap-marketing);
  --bw-select-indicator: var(--bw-component-select-indicator);
  --bw-size-icon-2xl: var(--bw-component-icon-size-2xl);
  --bw-size-icon-lg: var(--bw-component-icon-size-lg);
  --bw-size-icon-md: var(--bw-component-icon-size-md);
  --bw-size-icon-sm: var(--bw-component-icon-size-sm);
  --bw-size-icon-xl: var(--bw-component-icon-size-xl);
  --bw-size-radius-2xl: var(--bw-radius-2xl);
  --bw-size-radius-full: var(--bw-radius-full);
  --bw-size-radius-lg: var(--bw-radius-lg);
  --bw-size-radius-md: var(--bw-radius-md);
  --bw-size-radius-none: var(--bw-radius-none);
  --bw-size-radius-sm: var(--bw-radius-sm);
  --bw-size-radius-xl: var(--bw-radius-xl);
  --bw-size-space-0: var(--bw-space-0);
  --bw-size-space-0-5: var(--bw-space-0-5);
  --bw-size-space-1: var(--bw-space-1);
  --bw-size-space-1-5: var(--bw-space-1-5);
  --bw-size-space-10: var(--bw-space-10);
  --bw-size-space-11: var(--bw-space-11);
  --bw-size-space-12: var(--bw-space-12);
  --bw-size-space-16: var(--bw-space-16);
  --bw-size-space-2: var(--bw-space-2);
  --bw-size-space-20: var(--bw-space-20);
  --bw-size-space-24: var(--bw-space-24);
  --bw-size-space-3: var(--bw-space-3);
  --bw-size-space-4: var(--bw-space-4);
  --bw-size-space-5: var(--bw-space-5);
  --bw-size-space-6: var(--bw-space-6);
  --bw-size-space-7: var(--bw-space-7);
  --bw-size-space-8: var(--bw-space-8);
  --bw-size-space-9: var(--bw-space-9);
  --bw-size-space-px: var(--bw-space-px);
  --bw-sparkline-marker-radius: var(--bw-component-sparkline-marker-radius);
  --bw-sparkline-stroke-width: var(--bw-component-sparkline-stroke-width);
  --bw-stat-tile-sparkline-height: var(--bw-component-stat-tile-sparkline-height);
  --bw-stat-tile-value-size: var(--bw-component-stat-tile-value-size);
  --bw-toast-max-width: var(--bw-component-toast-max-width);
  --bw-toggle-thumb-inset: var(--bw-component-toggle-thumb-inset);
  --bw-toggle-thumb-size: var(--bw-component-toggle-thumb-size);
  --bw-toggle-track-height: var(--bw-component-toggle-track-height);
  --bw-toggle-track-width: var(--bw-component-toggle-track-width);
  --bw-tooltip-max-width: var(--bw-component-tooltip-max-width);
  --bw-topbar-position: var(--bw-component-topbar-position);
}


/* ==== layer: reset ==== */
/*
 * Brickwork reset
 * A small, modern normalize. Framework-agnostic — safe to include once at the
 * root of any HTML document. Assumes @brickwork/tokens is loaded for the
 * --bw-* custom properties referenced here.
 */

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

* {
  margin: 0;
}

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

body {
  min-height: 100vh;
  line-height: var(--bw-text-body-md-line-height, 1.6);
  font-family: var(--bw-text-body-md-family, system-ui, sans-serif);
  font-size: var(--bw-text-body-md-size, 1rem);
  -webkit-font-smoothing: antialiased;
  color: var(--bw-color-fg);
  background-color: var(--bw-color-bg);
}

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

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

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

button:disabled {
  cursor: not-allowed;
}

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

a {
  color: var(--bw-color-accent);
  text-decoration: none;
}

a:hover {
  text-decoration: underline;
}

:where(:focus-visible) {
  outline: 2px solid var(--bw-color-ring, var(--bw-color-accent));
  outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}


/* ==== layer: primitives ==== */
/*
 * Brickwork primitives
 * Low-level utility classes that map directly to semantic tokens. These are the
 * shared vocabulary every component (and consumer app) builds on. They are
 * intentionally small and non-exhaustive — Brickwork is a substrate, not a
 * utility framework. Pair with Tailwind or your own layout classes as needed.
 */

/* Typography bundles ----------------------------------------------------- */

.bw-display-2xl,
.bw-display-xl,
.bw-display-lg,
.bw-heading-xl,
.bw-heading-lg,
.bw-heading-md,
.bw-heading-sm,
.bw-body-lg,
.bw-body-md,
.bw-body-sm,
.bw-label,
.bw-code {
  font-family: var(--bw-family);
  font-size: var(--bw-size);
  line-height: var(--bw-line-height);
  font-weight: var(--bw-weight);
  letter-spacing: var(--bw-tracking, normal);
}

.bw-display-2xl {
  --bw-family: var(--bw-text-display-2xl-family);
  --bw-size: var(--bw-text-display-2xl-size);
  --bw-line-height: var(--bw-text-display-2xl-line-height);
  --bw-weight: var(--bw-text-display-2xl-weight);
  --bw-tracking: var(--bw-text-display-2xl-tracking);
}
.bw-display-xl {
  --bw-family: var(--bw-text-display-xl-family);
  --bw-size: var(--bw-text-display-xl-size);
  --bw-line-height: var(--bw-text-display-xl-line-height);
  --bw-weight: var(--bw-text-display-xl-weight);
  --bw-tracking: var(--bw-text-display-xl-tracking);
}
.bw-display-lg {
  --bw-family: var(--bw-text-display-lg-family);
  --bw-size: var(--bw-text-display-lg-size);
  --bw-line-height: var(--bw-text-display-lg-line-height);
  --bw-weight: var(--bw-text-display-lg-weight);
  --bw-tracking: var(--bw-text-display-lg-tracking);
}
.bw-heading-xl {
  --bw-family: var(--bw-text-heading-xl-family);
  --bw-size: var(--bw-text-heading-xl-size);
  --bw-line-height: var(--bw-text-heading-xl-line-height);
  --bw-weight: var(--bw-text-heading-xl-weight);
}
.bw-heading-lg {
  --bw-family: var(--bw-text-heading-lg-family);
  --bw-size: var(--bw-text-heading-lg-size);
  --bw-line-height: var(--bw-text-heading-lg-line-height);
  --bw-weight: var(--bw-text-heading-lg-weight);
}
.bw-heading-md {
  --bw-family: var(--bw-text-heading-md-family);
  --bw-size: var(--bw-text-heading-md-size);
  --bw-line-height: var(--bw-text-heading-md-line-height);
  --bw-weight: var(--bw-text-heading-md-weight);
}
.bw-heading-sm {
  --bw-family: var(--bw-text-heading-sm-family);
  --bw-size: var(--bw-text-heading-sm-size);
  --bw-line-height: var(--bw-text-heading-sm-line-height);
  --bw-weight: var(--bw-text-heading-sm-weight);
}
.bw-body-lg {
  --bw-family: var(--bw-text-body-lg-family);
  --bw-size: var(--bw-text-body-lg-size);
  --bw-line-height: var(--bw-text-body-lg-line-height);
  --bw-weight: var(--bw-text-body-lg-weight);
}
.bw-body-md {
  --bw-family: var(--bw-text-body-md-family);
  --bw-size: var(--bw-text-body-md-size);
  --bw-line-height: var(--bw-text-body-md-line-height);
  --bw-weight: var(--bw-text-body-md-weight);
}
.bw-body-sm {
  --bw-family: var(--bw-text-body-sm-family);
  --bw-size: var(--bw-text-body-sm-size);
  --bw-line-height: var(--bw-text-body-sm-line-height);
  --bw-weight: var(--bw-text-body-sm-weight);
}
.bw-label {
  --bw-family: var(--bw-text-label-family);
  --bw-size: var(--bw-text-label-size);
  --bw-line-height: var(--bw-text-label-line-height);
  --bw-weight: var(--bw-text-label-weight);
  --bw-tracking: var(--bw-text-label-tracking);
}
.bw-code {
  --bw-family: var(--bw-text-code-family);
  --bw-size: var(--bw-text-code-size);
  --bw-line-height: var(--bw-text-code-line-height);
  --bw-weight: var(--bw-text-code-weight);
}

/* Color roles ------------------------------------------------------------ */

.bw-fg {
  color: var(--bw-color-fg);
}
.bw-fg-muted {
  color: var(--bw-color-fg-muted);
}
.bw-fg-subtle {
  color: var(--bw-color-fg-subtle);
}
.bw-bg {
  background-color: var(--bw-color-bg);
}
.bw-surface {
  background-color: var(--bw-color-surface);
}
.bw-surface-raised {
  background-color: var(--bw-color-surface-raised);
}

/* Surface helper: a bordered, rounded panel on the raised surface color. */
.bw-panel {
  background-color: var(--bw-color-surface);
  border: 1px solid var(--bw-color-border);
  border-radius: var(--bw-radius-lg, 0.75rem);
}


/* ==== layer: layout ==== */
/*
 * Layout primitives and gallery type aliases.
 * These are the composition floor every HTML gallery page expects.
 */

.bw-skip-link {
  position: absolute;
  inset-inline-start: var(--bw-space-4);
  inset-block-start: var(--bw-space-4);
  z-index: var(--bw-z-skip-link);
  padding: var(--bw-space-2) var(--bw-space-3);
  border-radius: var(--bw-radius-md);
  background: var(--bw-color-accent);
  color: var(--bw-color-accent-fg);
  transform: translateY(-200%);
  transition: transform var(--bw-duration-fast, 0.15s) ease;
}

.bw-skip-link:focus {
  transform: translateY(0);
  outline: var(--bw-focus-ring-width, 2px) var(--bw-focus-ring-style, solid)
    var(--bw-color-focus-ring, var(--bw-color-accent));
  outline-offset: var(--bw-focus-ring-offset, 2px);
}

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

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

.bw-grid {
  display: grid;
  gap: var(--bw-space-6);
}

.bw-centre,
.bw-center {
  margin-inline: auto;
  max-inline-size: var(--bw-size-max-width-content, 72rem);
}

.bw-container {
  width: min(100% - 2 * var(--bw-space-6), var(--bw-size-max-width-content, 72rem));
  margin-inline: auto;
}

.bw-divider,
.bw-separator {
  border: 0;
  border-block-start: var(--bw-size-border-hairline, 1px) solid var(--bw-color-border);
  margin-block: var(--bw-space-6);
}

/* Type role aliases used by gallery copy-own pages */
.bw-text-display-md {
  font-family: var(--bw-text-display-lg-family);
  font-size: var(--bw-text-display-lg-size);
  line-height: var(--bw-text-display-lg-line-height);
  font-weight: var(--bw-text-display-lg-weight);
}

.bw-text-title-lg,
.bw-text-title-md,
.bw-text-title-sm {
  font-family: var(--bw-text-heading-lg-family);
  font-weight: var(--bw-text-heading-lg-weight);
  line-height: var(--bw-text-heading-lg-line-height);
}

.bw-text-title-lg { font-size: var(--bw-text-heading-xl-size); }
.bw-text-title-md { font-size: var(--bw-text-heading-lg-size); }
.bw-text-title-sm { font-size: var(--bw-text-heading-md-size); }

.bw-text-body-lg,
.bw-text-body-md,
.bw-text-body-sm {
  font-family: var(--bw-text-body-md-family);
  font-weight: var(--bw-text-body-md-weight);
}

.bw-text-body-lg {
  font-size: var(--bw-text-body-lg-size);
  line-height: var(--bw-text-body-lg-line-height);
}
.bw-text-body-md {
  font-size: var(--bw-text-body-md-size);
  line-height: var(--bw-text-body-md-line-height);
}
.bw-text-body-sm {
  font-size: var(--bw-text-body-sm-size);
  line-height: var(--bw-text-body-sm-line-height);
}

.bw-text-label-md {
  font-family: var(--bw-text-label-family);
  font-size: var(--bw-text-label-size);
  line-height: var(--bw-text-label-line-height);
  font-weight: var(--bw-text-label-weight);
  letter-spacing: var(--bw-text-label-tracking);
  text-transform: uppercase;
}

/* Primary CTA alias used in early gallery drafts */
.bw-btn--solid {
  background-color: var(--bw-color-accent);
  color: var(--bw-color-accent-fg);
  border-color: transparent;
}
.bw-btn--solid:hover {
  background-color: var(--bw-color-accent-hover, var(--bw-color-accent));
}


/* ==== layer: shell ==== */
/* brickwork shell layout: app / auth / centred. Structure only; colour comes
 * from the semantic tokens (theme-aware) and spacing from the density tokens.
 * Logical CSS properties throughout (BR-BW-TOK-005), so RTL is handled by the
 * `dir` attribute alone, no mirrored stylesheet. Nothing here needs JavaScript
 * (BR-BW-HTMX-001): the sidebar toggle and mobile drawer are native <details>.
 */

.bw-body {
  margin: 0;
  min-height: 100vh;
  background: var(--bw-color-surface-sunken);
  color: var(--bw-color-fg);
  /* Consume the font tokens so a consumer rebrands typography token-first
     (override --bw-font-family-sans) rather than reaching into these classes. */
  font-family: var(--bw-font-family-sans);
  font-size: var(--bw-font-size-md);
  line-height: var(--bw-font-line-height-normal);
}

/* Branded text selection (0.7.0 craft): a 22% accent wash over whatever sits
 * beneath, so selecting text feels like the product, not the UA default. The
 * text keeps its own ink; fg measures 13.18:1 light / 10.71:1 dark over the
 * composited wash on the surface, comfortably AA. */
.bw-body ::selection,
.bw-body::selection {
  background-color: color-mix(in oklab, var(--bw-color-accent) 22%, transparent);
}

/* The stable modal swap target (BR-BW-HTMX-005): an empty container the
 * shell ships for the htmx path's hx-target="#bw-modal-root". display:
 * contents keeps it zero-footprint whether empty or holding the
 * fixed-position modal, so an insertion never reflows the page
 * (BR-BW-HTMX-009). */
#bw-modal-root {
  display: contents;
}

/* The stable slide-over swap target (BR-BW-HTMX-005, 0.14.0, brickwork#55):
 * a SEPARATE root from #bw-modal-root so a modal and a slide-over can be
 * open at once. Same zero-footprint contract. */
#bw-slide-over-root {
  display: contents;
}

/* --- App shell: sidebar + main column -------------------------------------- */

.bw-app {
  display: grid;
  grid-template-columns: var(--bw-density-sidebar-width) 1fr;
  min-height: 100vh;
}

/* The collapsed column tracks the sidebar's own width so the grid narrows
 * with it (SHL-003/004, brickwork#58); [data-bw-collapsed] lives on
 * .bw-sidebar (set by bwSidebarCollapse), so this selector reaches up via
 * :has() to retarget the OUTER grid template. Chromium/Firefox/Safari all
 * support :has() as of this package's baseline; the graceful fallback with
 * no :has() support is simply an un-narrowed main column beside a narrowed
 * sidebar, a cosmetic gap only, never a functional break. */
/* Sidebar/topbar layouts only: regions mode owns its tracks via
 * --bw-shell-sidebar-track (see [data-layout="regions"] below). */
.bw-app:not([data-layout="regions"]):has(.bw-sidebar[data-bw-collapsed]) {
  grid-template-columns: var(--bw-density-sidebar-width-collapsed) 1fr;
}

/* Capability rail: sibling region for layout="regions" only (brickwork#700).
 * Hidden in sidebar/topbar layouts so the default two-column shell stays
 * byte-stable; regions mode turns it on and seats it beside the sidebar. */
.bw-rail {
  display: none;
  flex-direction: column;
  background: var(--bw-color-surface);
  border-inline-end: var(--bw-size-border-hairline) solid
    color-mix(in oklab, var(--bw-color-fg) 14%, var(--bw-color-surface));
  padding-block: var(--bw-space-2) var(--bw-density-stack-gap);
  padding-inline: var(--bw-space-2);
  inline-size: var(--bw-density-rail-width);
  overflow: hidden;
}

.bw-rail__nav {
  flex: 1 1 auto;
  overflow-y: auto;
  scrollbar-width: thin;
  scrollbar-color: var(--bw-color-border) transparent;
}

/* Transparent in sidebar/topbar so subnav/breadcrumbs/workspace remain flex
 * children of .bw-app__main (pre-#700 box tree). Regions mode promotes this
 * to a real column under the full-width topbar. */
.bw-app__body {
  display: contents;
}

.bw-sidebar {
  display: flex;
  flex-direction: column;
  background: var(--bw-color-surface);
  /* Stronger edge mix so the rail separates from a white workspace without
   * touching nav link / active overlays (border only, no fill change). */
  border-inline-end: var(--bw-size-border-hairline) solid
    color-mix(in oklab, var(--bw-color-fg) 14%, var(--bw-color-surface));
  /* space-2 at the top puts the first nav row on the topbar's centreline. */
  padding-block: var(--bw-space-2) var(--bw-density-stack-gap);
  padding-inline: var(--bw-space-3);
  inline-size: var(--bw-density-sidebar-width);
  transition: inline-size var(--bw-duration-normal) var(--bw-ease-standard);
}

/* --- Collapsed (icon-only) state (SHL-003/004, brickwork#58) --------------- */

/* [data-bw-collapsed] narrows the sidebar to the collapsed-width token and
 * hides text labels while KEEPING every icon and every accessible name: the
 * label text is visually hidden (.bw-visually-hidden's own clip technique),
 * never aria-hidden or removed, so a screen reader still announces the full
 * nav item name regardless of the visual collapse state (SHL-004's own
 * "narrows, hides text, keeps icons" contract). */
.bw-sidebar[data-bw-collapsed] {
  inline-size: var(--bw-density-sidebar-width-collapsed);
}
.bw-sidebar[data-bw-collapsed] .bw-nav__label,
.bw-sidebar[data-bw-collapsed] .bw-nav__badge,
.bw-sidebar[data-bw-collapsed] .bw-nav__external,
.bw-sidebar[data-bw-collapsed] .bw-sidebar__switcher,
.bw-sidebar[data-bw-collapsed] .bw-sidebar__footer,
.bw-sidebar[data-bw-collapsed] .bw-sidebar__brand-wordmark {
  position: absolute;
  inline-size: var(--bw-space-px);
  block-size: var(--bw-space-px);
  margin: calc(var(--bw-space-px) * -1);
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}
/* The nav row centres its icon once the label collapses out of flow (the
 * label's own flex:1 auto no longer exists to push things, but the row
 * still reserves label space via its own layout unless re-centred). */
.bw-sidebar[data-bw-collapsed] .bw-nav__link,
.bw-sidebar[data-bw-collapsed] .bw-nav__link--disabled {
  justify-content: center;
}
/* Section headers read as pure text with no icon; collapsing them to
 * clipped text would leave an empty, confusing row, so the whole header
 * hides (its accessible name is redundant with the icon-only items below it
 * once collapsed, unlike a nav link whose name IS the destination). */
.bw-sidebar[data-bw-collapsed] .bw-nav__section-label {
  display: none;
}
/* Nested (child) lists never show icon-only: a collapsed sidebar has no
 * room to indicate hierarchy with icons alone, so submenus simply do not
 * render open in the collapsed rail (consistent with SHL-004: this package
 * ships no flyout-on-hover mechanism; a consumer wanting one composes
 * bw_dropdown per nav item, its own template work). */
.bw-sidebar[data-bw-collapsed] .bw-nav__list--nested {
  display: none;
}
@media (prefers-reduced-motion: reduce) {
  .bw-sidebar {
    transition: none;
  }
}

.bw-sidebar__header {
  display: flex;
  align-items: center;
  gap: var(--bw-space-2);
  padding-inline: var(--bw-space-2);
  padding-block-end: var(--bw-density-stack-gap);
}

/* The brand wrappers (app.html) give the collapsed rail a stable element to
 * hide (the wordmark) and one to keep (the mark), independent of the
 * consumer's own inner markup or classes. They are inline-flex so they sit in
 * the header's flex row exactly where the bare blocks did (the consumer's
 * inner anchor/mark/text is unchanged); an unfilled block leaves an :empty
 * wrapper, which takes no space. Note: NOT `display: contents` here, because
 * the collapsed rule below clips the wordmark via position/clip-path, which a
 * box-less `display: contents` element cannot honour. */
.bw-sidebar__brand-mark,
.bw-sidebar__brand-wordmark {
  display: inline-flex;
  align-items: center;
}
.bw-sidebar__brand-mark:empty,
.bw-sidebar__brand-wordmark:empty {
  display: none;
}

/* The collapse toggle seats at the row end regardless of whether
 * brand_logo/brand_wordmark are filled (brickwork#58). */
.bw-sidebar__toggle {
  margin-inline-start: auto;
  flex-shrink: 0;
}
/* Collapsed: the wordmark clips like the nav labels above (SHL-004), but the
 * MARK stays visible (brickwork#93: an icon-only rail keeps its brand mark,
 * the standard collapsed-sidebar affordance), so the header centres the
 * remaining mark and toggle instead of pinning the toggle to a now-empty
 * end. The mark wrapper needs no collapsed override: it keeps its base
 * inline-flex box and simply is not in the clip group above. */
.bw-sidebar[data-bw-collapsed] .bw-sidebar__toggle {
  margin-inline-start: 0;
}
.bw-sidebar[data-bw-collapsed] .bw-sidebar__header {
  justify-content: center;
}

/* The nav slot (brickwork#21, BR-BW-NAV-007): a quiet, positioned seat for a
   consumer-authored switcher above the nav list, shared by the sidebar and
   the mobile drawer (one class, both containers, mirroring the nav's own one
   render). Structure and spacing only, never switcher behaviour; the gap to
   the nav below is the density section gap so a consumer never guesses it.
   Inline padding matches the sidebar header's inset so slot content aligns
   with the rows above and below. */
.bw-sidebar__switcher {
  padding-inline: var(--bw-space-2);
  padding-block: var(--bw-space-2);
  margin-block-end: var(--bw-density-section-gap);
  border-block-end: var(--bw-size-border-hairline) solid var(--bw-color-border);
}

.bw-sidebar__nav {
  flex: 1 1 auto;
  overflow-y: auto;
  /* Thin, border-toned scrollbar (0.7.0 craft): navigation chrome scrolls
   * quietly instead of hoisting the UA's full-width gutter. Decorative. */
  scrollbar-width: thin;
  scrollbar-color: var(--bw-color-border) transparent;
}

.bw-sidebar__footer {
  padding-block-start: var(--bw-density-stack-gap);
  border-block-start: var(--bw-size-border-hairline) solid var(--bw-color-border);
}

.bw-app__main {
  display: flex;
  flex-direction: column;
  min-width: 0; /* let the workspace shrink rather than force overflow */
}

/* flex-wrap (icvoss/django-brickwork#209): the drawer trigger, search,
 * notifications and account cluster are all fixed-content flex children
 * with no shrink floor of their own; a long account label (a real
 * consumer's name/email, not the short "Account" default) pushes the row
 * past a narrow viewport with nowhere to give, scrolling the whole page
 * sideways. Wrapping lets the account cluster (and notifications, if
 * present) drop to a second row instead, the same fix shape #125 used for
 * the marketing header. */
.bw-topbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--bw-space-3);
  padding-inline: var(--bw-density-page-gutter-inline);
  min-block-size: var(--bw-density-topbar-height);
  background: var(--bw-color-surface);
  border-block-end: var(--bw-size-border-hairline) solid var(--bw-color-border);
  /* Light elev-1 + soft ambient so sticky chrome reads on a white canvas. */
  box-shadow:
    var(--bw-elevation-1),
    0 4px 14px -4px oklch(0 0 0 / 0.1);
  position: var(--bw-component-topbar-position);
  inset-block-start: 0;
  z-index: var(--bw-z-sticky);
}

.bw-topbar__spacer {
  flex: 1 1 auto;
}

.bw-topbar__search {
  flex: 1 1 18rem;
  min-inline-size: 0;
  max-inline-size: 42rem;
}

/* The account cluster is the widest fixed-content item on the row (icon +
 * label + caret, and a real consumer's name/email can be long); it must not
 * be forced to shrink below its own content, which is exactly what pushed
 * the row past the viewport at #209's narrow widths. flex-shrink: 0 lets
 * flex-wrap do the work instead of the item compressing into an unreadable
 * sliver first. */
.bw-topbar__notifications,
.bw-topbar__account {
  flex-shrink: 0;
}

.bw-workspace {
  flex: 1 1 auto;
  padding: var(--bw-density-stack-gap) var(--bw-density-page-gutter-inline);
  outline: none; /* focus ring on the skip-link target is handled by :focus-visible */
}

.bw-content {
  max-inline-size: var(--bw-component-content-max-width);
  margin-inline: auto; /* centres the column when --bw-component-content-max-width is finite */
}

/* --- Content typography floor ---------------------------------------------- */

/* Unclassed consumer markup inside .bw-content gets designed defaults instead
 * of UA styles, so "our defaults are beautiful" holds for content, not just
 * chrome. :not([class]) keeps every bw-* component out of scope; :where()
 * keeps specificity at the bare .bw-content class so consumer CSS still wins.
 * Roles are theme-aware tokens, so both themes resolve correctly.
 *
 * htmx lifecycle classes (interactions-2, 0.9.0): htmx stamps exactly one of
 * htmx-added / htmx-settling / htmx-swapping onto an otherwise-unclassed
 * swapped-in element during its transition window, which makes :not([class])
 * stop matching mid-settle and the UA default margins flip back in (a CLS
 * bounce on every swap into classless prose). [class="htmx-<x>"] is an EXACT
 * attribute match, so it only ever matches an element carrying that one
 * lifecycle class and nothing else; a real author class alongside it still
 * falls outside :where() here, exactly as :not([class]) already excludes one. */
.bw-content
  :where(
    h1:not([class]),
    h1[class="htmx-added"],
    h1[class="htmx-settling"],
    h1[class="htmx-swapping"]
  ) {
  font-family: var(--bw-text-heading-xl-family);
  font-size: var(--bw-text-heading-xl-size);
  font-weight: var(--bw-text-heading-xl-weight);
  line-height: var(--bw-text-heading-xl-line-height);
  letter-spacing: var(--bw-text-heading-xl-tracking);
  margin-block: 0 var(--bw-space-4);
}
.bw-content
  :where(
    h2:not([class]),
    h2[class="htmx-added"],
    h2[class="htmx-settling"],
    h2[class="htmx-swapping"]
  ) {
  font-family: var(--bw-text-heading-lg-family);
  font-size: var(--bw-text-heading-lg-size);
  font-weight: var(--bw-text-heading-lg-weight);
  line-height: var(--bw-text-heading-lg-line-height);
  letter-spacing: var(--bw-text-heading-lg-tracking);
  margin-block: var(--bw-density-section-gap) var(--bw-space-3);
}
.bw-content
  :where(
    h3:not([class]),
    h3[class="htmx-added"],
    h3[class="htmx-settling"],
    h3[class="htmx-swapping"]
  ) {
  font-family: var(--bw-text-heading-md-family);
  font-size: var(--bw-text-heading-md-size);
  font-weight: var(--bw-text-heading-md-weight);
  line-height: var(--bw-text-heading-md-line-height);
  margin-block: var(--bw-space-6) var(--bw-space-2);
}
.bw-content
  :where(
    h4:not([class]), h5:not([class]), h6:not([class]),
    h4[class="htmx-added"], h5[class="htmx-added"], h6[class="htmx-added"],
    h4[class="htmx-settling"], h5[class="htmx-settling"], h6[class="htmx-settling"],
    h4[class="htmx-swapping"], h5[class="htmx-swapping"], h6[class="htmx-swapping"]
  ) {
  font-family: var(--bw-text-heading-sm-family);
  font-size: var(--bw-text-heading-sm-size);
  font-weight: var(--bw-text-heading-sm-weight);
  line-height: var(--bw-text-heading-sm-line-height);
  margin-block: var(--bw-space-4) var(--bw-space-2);
}
.bw-content
  :where(
    p:not([class]),
    p[class="htmx-added"],
    p[class="htmx-settling"],
    p[class="htmx-swapping"]
  ) {
  font-size: var(--bw-text-body-md-size);
  line-height: var(--bw-text-body-md-line-height);
  margin-block: 0 var(--bw-space-3);
  max-inline-size: var(--bw-size-max-width-prose);
}
.bw-content
  :where(
    ul:not([class]), ol:not([class]),
    ul[class="htmx-added"], ol[class="htmx-added"],
    ul[class="htmx-settling"], ol[class="htmx-settling"],
    ul[class="htmx-swapping"], ol[class="htmx-swapping"]
  ) {
  margin-block: 0 var(--bw-space-3);
  padding-inline-start: var(--bw-space-5);
  max-inline-size: var(--bw-size-max-width-prose);
}
.bw-content
  :where(
    ul:not([class]), ol:not([class]),
    ul[class="htmx-added"], ol[class="htmx-added"],
    ul[class="htmx-settling"], ol[class="htmx-settling"],
    ul[class="htmx-swapping"], ol[class="htmx-swapping"]
  )
  :where(li)::marker {
  color: var(--bw-color-fg-subtle); /* decorative; adjacent text carries meaning */
}

.bw-page-header-region {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--bw-space-4);
  margin-block-end: var(--bw-density-section-gap);
}

/* --- Page patterns (0.5.0): composition scaffolding only -------------------- */

/* The dashboard stat row (patterns/dashboard.html): a responsive auto-fit
   grid the consumer drops _stat.html includes into, section-gap-spaced per
   the pattern contract (04-interfaces.md 4a). 14rem is the structural floor
   below which a tabular-numeral tile stops reading as a tile; min() keeps a
   single column on viewports narrower than one tile. */
.bw-stat-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(14rem, 100%), 1fr));
  gap: var(--bw-density-section-gap);
}

/* The detail section stack (patterns/detail.html): section-gap between
   sibling cards, no other opinion (the pattern imposes no count or shape). */
.bw-section-stack {
  display: grid;
  gap: var(--bw-density-section-gap);
}
/* A grid item's default min-inline-size is auto, not 0: without this on every
 * direct child, a wide child (a .bw-card wrapping a data table scroll wrap)
 * refuses to shrink below its content width, blows out the track, and the
 * scroll container inside never gets a constrained width to scroll within
 * (icvoss/django-brickwork#136). */
.bw-section-stack > * {
  min-inline-size: 0;
}

/* The app-surface N-column arrangement primitive (icvoss/django-brickwork#399,
   ruling on #371 gap 3): before this, an app-surface page could express a
   vertical stack of full-width bands and nothing else. Two ranked lists side
   by side is the motivating case (#371).

   Deliberately NOT `bw-feature-grid`/`bw-media-text`/`bw-listing-list__item`,
   which ship in the same `brickwork.css` bundle and would work mechanically:
   those are marketing-family, styled for marketing surfaces and restyled for
   marketing reasons, so permitting them here would make a marketing visual
   change silently an app-surface regression. `bw-band-grid` is the app
   family's own primitive; `tests/test_family_boundary.py` is the check that
   keeps marketing-family classes out of app-surface templates.

   Same auto-fit idiom as `.bw-stat-grid` above: `minmax(min(X, 100%), 1fr)`
   keeps a single column on viewports narrower than one tile. The floor
   widens as the explicit column count drops, so a 2-up pair of ranked lists
   does not fragment into four narrow columns on a wide viewport: the bare
   class and `--2` share a 24rem floor (two comfortable reading-width
   columns), `--3` steps to 18rem, and `--4` reuses `.bw-stat-grid`'s own
   14rem floor, below which a tile stops reading as a tile. */
.bw-band-grid,
.bw-band-grid--2 {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(24rem, 100%), 1fr));
  gap: var(--bw-density-section-gap);
}
.bw-band-grid--3 {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(18rem, 100%), 1fr));
  gap: var(--bw-density-section-gap);
}
.bw-band-grid--4 {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(14rem, 100%), 1fr));
  gap: var(--bw-density-section-gap);
}
/* Same trap, same guard, same fix as .bw-section-stack above
 * (icvoss/django-brickwork#136): a grid item's default min-inline-size is
 * auto, not 0, so a wide child refuses to shrink below its content width,
 * blows out the track, and an inner scroll container never gets a
 * constrained width to scroll within. */
.bw-band-grid > *,
.bw-band-grid--2 > *,
.bw-band-grid--3 > *,
.bw-band-grid--4 > * {
  min-inline-size: 0;
}

/* An unfilled pattern region renders nothing, never empty chrome or a stray
   gap (BR-BW-TPL-006). */
.bw-stat-grid:empty,
.bw-section-stack:empty,
.bw-band-grid:empty,
.bw-band-grid--2:empty,
.bw-band-grid--3:empty,
.bw-band-grid--4:empty {
  display: none;
}

.bw-footer {
  margin-block-start: var(--bw-density-section-gap);
  padding-block-start: var(--bw-space-4);
  padding-block-end: var(--bw-space-6);
  border-block-start: var(--bw-size-border-hairline) solid var(--bw-color-border);
  color: var(--bw-color-fg-muted);
  font-family: var(--bw-text-caption-family);
  font-size: var(--bw-text-caption-size);
  line-height: var(--bw-text-caption-line-height);
  font-weight: var(--bw-text-caption-weight);
  letter-spacing: var(--bw-text-caption-tracking);
}

.bw-subnav:empty,
.bw-breadcrumbs:empty,
.bw-footer:empty,
.bw-page-actions:empty,
.bw-sidebar__header:empty,
.bw-sidebar__footer:empty,
.bw-sidebar__switcher:empty {
  display: none; /* empty blocks render nothing, never a placeholder (SHL-006/20, AC-BW-078) */
}

/* Breadcrumbs list (#25): the <ol> inside the shell's labelled bw-breadcrumbs
   <nav>. A "/" separator is drawn before every item except the first, via a
   pseudo-element. Pseudo-element content IS read by some screen readers, so the
   alternative-text form (content: "/" / "") gives it an empty accessible name.
   Logical properties for RTL. */
.bw-breadcrumbs {
  /* Align the crumbs with the page title's gutter below and clear the topbar
     border above; the :empty rule keeps hidden breadcrumbs paddingless. */
  padding-inline: var(--bw-density-page-gutter-inline);
  padding-block: var(--bw-space-4) 0;
}
/* Crumbs are context for the title: when breadcrumbs render, tighten the
   workspace's top padding so the crumbs bind to the title (16/8 rhythm), not
   to the topbar. Graceful no-op in engines without :has(). */
.bw-app__main:has(.bw-breadcrumbs:not(:empty)) .bw-workspace {
  padding-block-start: var(--bw-space-2);
}
.bw-breadcrumbs__list {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--bw-space-2);
  margin: 0;
  padding: 0;
  list-style: none;
  font-size: var(--bw-text-body-sm-size);
  line-height: var(--bw-text-body-sm-line-height);
}
.bw-breadcrumbs__item {
  display: inline-flex;
  align-items: center;
  gap: var(--bw-space-2);
}
.bw-breadcrumbs__item + .bw-breadcrumbs__item::before {
  content: "/" / "";
  color: var(--bw-component-breadcrumb-separator);
}
/* Two-tier target size (icvoss/django-brickwork#208, then #242): a
 * breadcrumb is a navigational control, not inline prose, so WCAG 2.5.8's
 * text-run exception does not apply; the crumb was sized to its line-height
 * alone (21px at 375px). The AA floor here is min-block-size: 1.5rem
 * (24px), unconditional. #242 adds a coarse-pointer-only 44px tier below,
 * the WCAG 2.5.5/platform-HIG bar, above what the package's AA claim
 * requires; align-items keeps the crumb centred, so a fine-pointer desktop
 * click never sees the larger target and the trail's layout does not shift
 * there. */
.bw-breadcrumbs__link {
  display: inline-flex;
  align-items: center;
  min-block-size: 1.5rem;
  color: var(--bw-color-fg-muted);
  text-decoration: none;
  transition: var(--bw-transition-colors);
}
.bw-breadcrumbs__link:hover {
  color: var(--bw-color-accent);
  text-decoration: underline;
}

@media (pointer: coarse) {
  .bw-breadcrumbs__link {
    min-block-size: var(--bw-size-touch-target-min);
  }
}
.bw-breadcrumbs__text {
  color: var(--bw-color-fg-muted);
}
.bw-breadcrumbs__current {
  color: var(--bw-component-breadcrumb-current);
  font-weight: var(--bw-font-weight-medium);
}

/* --- Native <details> drawer for mobile nav (no JS, SHL-015/016) ----------- */

.bw-drawer {
  display: none; /* shown only at the mobile breakpoint */
}
.bw-drawer__trigger {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: var(--bw-space-2);
  border-radius: var(--bw-radius-md);
  min-block-size: var(--bw-size-touch-target-min);
  min-inline-size: var(--bw-size-touch-target-min);
  cursor: pointer;
  list-style: none;
  transition: var(--bw-transition-colors);
}
.bw-drawer__trigger:hover {
  background-color: var(--bw-state-hover-overlay);
}
.bw-drawer__trigger::-webkit-details-marker {
  display: none;
}
.bw-drawer__panel {
  position: absolute;
  inset-block-start: var(--bw-density-topbar-height); /* sits flush under the topbar */
  inset-inline-start: 0;
  inline-size: var(--bw-component-drawer-width);
  background: var(--bw-color-surface-raised);
  border-inline-end: var(--bw-size-border-hairline) solid var(--bw-color-border);
  box-shadow: var(--bw-elevation-4);
  padding: var(--bw-density-stack-gap) var(--bw-space-3);
  z-index: var(--bw-z-drawer);
}

/* --- Shared site chrome (ADR-113 / BR-BW-TPL-008) --------------------------
 * Family-neutral header/footer band and measure. Canonical classes are
 * .bw-site-*. The marketing shell emits these names only (4.0.0 closed the
 * dual-class marketing alias window). Docs and other non-marketing surfaces
 * include _site_header.html / _site_footer.html and wear .bw-site-* only
 * (ADR-090). Overlay (ADR-105) stays in marketing.css. App / auth / centred
 * shells do not compose this chrome by default.
 */

.bw-site-header {
  background: var(--bw-color-surface);
  border-block-end: var(--bw-size-border-hairline) solid var(--bw-color-border);
  box-shadow:
    var(--bw-elevation-1),
    0 4px 14px -4px oklch(0 0 0 / 0.1);
  position: sticky;
  inset-block-start: 0;
  z-index: var(--bw-z-sticky);
}

.bw-site-header__inner {
  max-inline-size: var(--bw-component-content-max-width-marketing);
  margin-inline: auto;
  padding-inline: var(--bw-density-page-gutter-inline);
  padding-block: var(--bw-space-3);
}

.bw-site-header__brand {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--bw-space-2) var(--bw-space-6);
  position: relative;
}

.bw-site-header__brand-mark,
.bw-site-header__brand-wordmark {
  display: inline-flex;
  align-items: center;
  flex: 0 0 auto;
}

.bw-site-header__brand-mark:empty,
.bw-site-header__brand-wordmark:empty {
  display: none;
}

.bw-site-header__brand-mark :where(img, svg),
.bw-site-header__brand-wordmark :where(img, svg) {
  block-size: var(--bw-component-logo-height);
  inline-size: auto;
}

.bw-site-header__nav {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--bw-space-4);
  flex: 1 1 auto;
  min-inline-size: 0;
}

.bw-site-header__nav:empty {
  display: none;
}

.bw-site-header__nav :where(a):not(.bw-btn) {
  display: inline-flex;
  align-items: center;
  min-block-size: 1.5rem;
  color: var(--bw-color-fg-muted);
  font-size: var(--bw-text-label-size);
  font-weight: var(--bw-text-label-weight);
  text-decoration: none;
  transition: var(--bw-transition-colors);
}

.bw-site-header__nav :where(a:hover):not(.bw-btn) {
  color: var(--bw-color-fg);
}

@media (pointer: coarse) {
  .bw-site-header__nav :where(a):not(.bw-btn) {
    min-block-size: var(--bw-size-touch-target-min);
  }
}

.bw-site-header__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: var(--bw-space-2);
}

.bw-site-header__actions:empty {
  display: none;
}

.bw-site-header__actions > :where(a):not(.bw-btn) {
  display: inline-flex;
  align-items: center;
  min-block-size: 1.5rem;
  color: var(--bw-color-fg-muted);
  font-size: var(--bw-text-label-size);
  font-weight: var(--bw-text-label-weight);
  text-decoration: none;
  transition: var(--bw-transition-colors);
}

.bw-site-header__actions > :where(a:hover):not(.bw-btn) {
  color: var(--bw-color-fg);
}

@media (pointer: coarse) {
  .bw-site-header__actions > :where(a):not(.bw-btn) {
    min-block-size: var(--bw-size-touch-target-min);
  }
}

@media (width >= 64rem) {
  .bw-site-header__brand {
    flex-wrap: nowrap;
  }
}

.bw-site-footer {
  border-block-start: var(--bw-size-border-hairline) solid var(--bw-color-border);
  background: var(--bw-color-surface-sunken);
  margin-block-start: auto;
}

.bw-site-footer__inner {
  max-inline-size: var(--bw-component-content-max-width-marketing);
  margin-inline: auto;
  padding-inline: var(--bw-density-page-gutter-inline);
  padding-block: var(--bw-space-8) var(--bw-space-4);
}

.bw-site-footer__inner :where(a):not(.bw-btn) {
  display: inline-flex;
  align-items: center;
  min-block-size: 1.5rem;
  color: var(--bw-color-fg-muted);
  text-decoration: none;
  transition: var(--bw-transition-colors);
}

.bw-site-footer__inner :where(a:hover):not(.bw-btn) {
  color: var(--bw-color-fg);
}

@media (pointer: coarse) {
  .bw-site-footer__inner :where(a):not(.bw-btn) {
    min-block-size: var(--bw-size-touch-target-min);
  }
}

.bw-site-footer__legal {
  margin-block-start: var(--bw-space-6);
  padding-block-start: var(--bw-space-4);
  border-block-start: var(--bw-size-border-hairline) solid var(--bw-color-border);
  color: var(--bw-color-fg-muted);
  font-size: var(--bw-text-caption-size);
  line-height: var(--bw-text-caption-line-height);
}

.bw-site-footer__legal:empty {
  display: none;
}

.bw-site-footer:has(.bw-site-footer__legal:only-child:empty) {
  display: none;
}

[data-theme="dark"] .bw-site-header {
  box-shadow: var(--bw-elevation-1);
}

/* --- Docs shell: [article | rail] two-column layout (ADR-091, #439) ------- */

/* Site-chrome column (icvoss/django-brickwork#448 item 1): .bw-docs-shell
 * wraps the whole shell (docs_site_header_region, <main class="bw-docs">,
 * docs_site_footer_region) in a flex column at min-block-size: 100vh, the
 * docs-family counterpart of .bw-marketing's own role for the marketing
 * shell. A DIFFERENT class name deliberately: tests/test_family_boundary.py
 * forbids a marketing-family class (one marketing.css itself defines a rule
 * for) from appearing in a non-marketing-family shell, so this rule and
 * .bw-marketing's are independent, coincidentally identical declarations
 * rather than a shared class. flex: 1 1 auto on <main class="bw-docs"> below
 * makes it the column's one growing member, and docs_site_footer_region's
 * <footer class="bw-docs-site-footer"> takes margin-block-start: auto (the
 * same mechanism as .bw-marketing-footer), so a short docs page still pins
 * its site footer to the viewport bottom instead of floating it mid-page.
 * With docs_site_header/docs_site_footer both unfilled the two <header>/
 * <footer> elements collapse via the :empty rule below, so this wrapper
 * changes nothing visible for an existing 3.15.0 consumer. */
.bw-docs-shell {
  display: flex;
  flex-direction: column;
  min-block-size: 100vh;
}

.bw-docs-site-header:empty,
.bw-docs-site-footer:empty {
  display: none;
}

.bw-docs-site-header {
  display: flex;
  align-items: center;
  gap: var(--bw-space-4);
  padding-inline: var(--bw-density-page-gutter-inline);
  padding-block: var(--bw-space-3);
  border-block-end: var(--bw-size-border-hairline) solid var(--bw-color-border);
  background: var(--bw-color-surface);
}

/* Brand mark and search in docs_site_header (Beat S7 craft): the header is a
 * flex row; brand stays put, search claims the remaining inline space so the
 * control stays discoverable at every viewport without a Plus-shaped chrome
 * redesign. max-inline-size keeps a wide monitor from stretching the input
 * into a full-bleed bar. */
.bw-docs-site-header__brand {
  flex: 0 0 auto;
  color: var(--bw-color-fg);
  font-family: var(--bw-font-family-display);
  font-weight: var(--bw-font-weight-semibold);
  text-decoration: none;
}

.bw-docs-site-header .bw-search {
  flex: 1 1 12rem;
  max-inline-size: 28rem;
  margin-inline-start: auto;
}

.bw-docs-site-footer {
  margin-block-start: auto;
}

/* A bare <a> in docs_site_header/docs_site_footer takes themed ink, never
 * the UA default blue, the same contrast defect .bw-docs-layout__footer's
 * own rule fixes below and for the same reason: a consumer's site chrome is
 * arbitrary markup, and a plain anchor is the obvious thing to write in a
 * site nav or footer link list. Written at zero specificity via :where() so
 * a consumer's own class wins without !important. :not(.bw-btn) excludes a
 * bw_button composed into either slot, matching every other bare-link rule
 * in this file. The 1.5rem floor is the WCAG 2.5.8 AA target size
 * (icvoss/django-brickwork#208) and the coarse-pointer tier below is
 * 2.5.5/platform-HIG (icvoss/django-brickwork#242). */
.bw-docs-site-header :where(a):not(.bw-btn),
.bw-docs-site-footer :where(a):not(.bw-btn) {
  display: inline-flex;
  align-items: center;
  min-block-size: 1.5rem;
  color: var(--bw-color-fg-muted);
  text-decoration: none;
  transition: var(--bw-transition-colors);
}

.bw-docs-site-header :where(a:hover):not(.bw-btn),
.bw-docs-site-footer :where(a:hover):not(.bw-btn) {
  color: var(--bw-color-fg);
}

@media (pointer: coarse) {
  .bw-docs-site-header :where(a):not(.bw-btn),
  .bw-docs-site-footer :where(a):not(.bw-btn) {
    min-block-size: var(--bw-size-touch-target-min);
  }
}

/* .bw-docs is the shell's own <main>; the layout root .bw-docs-layout is a
 * SINGLE DIRECT CHILD of it, so the shell can apply the content measure,
 * inline centring, and page gutter to the whole layout once rather than each
 * column claiming it independently (carried from the consumer's own
 * main.css:930-933, ADR-091 decision 4). flex: 1 1 auto makes it the
 * .bw-docs-shell column's one growing member (icvoss/django-brickwork#448
 * item 1), the same role .bw-marketing__content plays inside .bw-marketing.
 *
 * inline-size: 100% is load-bearing under the column flex parent
 * (icvoss/django-brickwork#668): without it, auto margin-inline on a flex
 * item sizes the item fit-content, so pages with different longest lines
 * render .bw-docs at different widths and the rail/article jump sideways
 * when navigating. The auto margins then only centre within the measure. */
.bw-docs {
  display: block;
  flex: 1 1 auto;
  inline-size: 100%;
  max-inline-size: var(--bw-component-content-max-width-marketing);
  margin-inline: auto;
  padding-inline: var(--bw-density-page-gutter-inline);
  padding-block: var(--bw-density-section-gap);
}

/* Mobile-first: the BASE (narrow, < --bw-breakpoint-lg) layout is a single
 * column. Source order in docs.html is article-then-rail (ADR-091 decision
 * 3, the normative constraint): the article renders first so its own
 * heading is reachable before the rail on every viewport, and the rail
 * collapses to a <details> disclosure below it. */
.bw-docs-layout {
  display: grid;
  gap: var(--bw-density-section-gap);
}

/* Mobile-only jump to the section rail (icvoss/django-brickwork#523). Sticky
 * so it stays above the fold while the reader starts the article; hidden at
 * the two-column breakpoint where the rail is already visible. */
.bw-docs-nav-jump {
  position: sticky;
  inset-block-start: 0;
  z-index: 1;
  margin: 0;
  margin-block-end: var(--bw-space-3);
  padding-block: var(--bw-space-2);
  background: var(--bw-color-surface);
}
.bw-docs-nav-jump a {
  display: inline-flex;
  align-items: center;
  min-block-size: var(--bw-size-touch-target-min);
  padding-inline: var(--bw-space-3);
  border: var(--bw-size-border-hairline) solid var(--bw-color-border);
  border-radius: var(--bw-radius-md);
  color: var(--bw-color-fg);
  font-family: var(--bw-font-family-display);
  font-weight: var(--bw-font-weight-semibold);
  text-decoration: none;
}
.bw-docs-nav-jump a:hover {
  background: var(--bw-color-surface-raised);
}
.bw-docs-nav-jump a:focus-visible {
  outline: 2px solid var(--bw-color-accent);
  outline-offset: 2px;
}

.bw-docs-layout__nav-summary {
  display: flex;
  align-items: center;
  min-block-size: var(--bw-size-touch-target-min);
  padding-inline: var(--bw-space-3);
  border: var(--bw-size-border-hairline) solid var(--bw-color-border);
  border-radius: var(--bw-radius-md);
  font-family: var(--bw-font-family-display);
  font-weight: var(--bw-font-weight-semibold);
  color: var(--bw-color-fg);
  cursor: pointer;
}

.bw-docs-layout__nav-summary:focus-visible {
  outline: 2px solid var(--bw-color-accent);
  outline-offset: 2px;
}

.bw-docs-layout__nav-body {
  padding-block-start: var(--bw-space-3);
}

/* Desktop scale-up (>= --bw-breakpoint-lg, 64rem): a persistent two-column
 * layout. --bw-breakpoint-md (48rem, the sidebar/drawer collapse point) is
 * the wrong token here: a 16rem rail plus a readable measure does not fit at
 * 48rem, and --bw-breakpoint-lg is already exactly the value a consumer
 * chose independently, so no new token is added (ADR-091 decision 5).
 *
 * The <details> summary is hidden (nothing to disclose, the rail is always
 * shown) and the nav body is forced visible regardless of the [open] state,
 * so a user who collapsed the rail on a narrow viewport before resizing
 * still sees it at this breakpoint. `order: -1` puts the rail back in the
 * FIRST visual grid column even though it is second in DOM order (the
 * mobile-first source order above): at this single persistent two-column
 * viewport both the rail and the article are always visible together, so
 * reordering the grid item here is the standard, a11y-safe use of `order`
 * (no content is hidden or reachable only out of visual sequence, unlike an
 * `order`-based mobile reflow). If a future change ever hides either column
 * at any viewport, this `order` usage must be re-examined, not preserved
 * (ADR-091 decision 3). */
@media (width >= 64rem) {
  .bw-docs-layout {
    grid-template-columns: var(--bw-component-docs-nav-inline-size) minmax(0, 1fr);
    gap: var(--bw-space-10);
    align-items: start;
  }

  .bw-docs-nav-jump {
    display: none;
  }

  /* Sticky rail scroll (icvoss/django-brickwork#671): without a max block
   * size the rail sticks with its tail below the fold when it has more
   * links than fit the viewport. scrollbar-gutter keeps the column width
   * stable when the overflow scrollbar appears. */
  .bw-docs-layout__nav {
    order: -1;
    position: sticky;
    inset-block-start: var(--bw-space-6);
    max-block-size: calc(100dvh - 2 * var(--bw-space-6));
    overflow-y: auto;
    scrollbar-gutter: stable;
  }

  .bw-docs-layout__nav-summary {
    display: none;
  }

  /* Reveal the rail at this breakpoint (icvoss/django-brickwork#451).
   *
   * A closed <details> collapses everything that is not its <summary>,
   * and it does so on the ::details-content pseudo-element that wraps
   * that content, NOT on the content's own boxes. So a display rule on
   * .bw-docs-layout__nav-body below cannot reveal anything while the
   * details is closed: the child is inside a collapsed wrapper, and its
   * own display value never gets the chance to matter. The shell sets no
   * [open] and the summary above is display:none here, so before this
   * rule a desktop docs page rendered a 16rem grid track containing a
   * zero-height rail, with every link present in the DOM and no control
   * anywhere on the page to open it. Shipped in 3.15.0 and found on a
   * rendered page rather than by any suite: the markup is correct, so
   * the template tests pass, and links that are still in the
   * accessibility tree are not a WCAG violation, so axe passes too.
   *
   * block-size is set alongside content-visibility because the closed
   * state zeroes the pseudo's block-size as well; revealing the content
   * without it leaves the same 0px rail. */
  .bw-docs-layout__nav::details-content {
    content-visibility: visible;
    block-size: auto;
  }

  .bw-docs-layout__nav-body {
    display: block;
    padding-block-start: 0;
  }
}

/* A grid item's default min-inline-size is auto, not 0: without this the
 * article column refuses to shrink below its content width (a wide code
 * sample or data table) and blows out the track, the same trap
 * .bw-section-stack and .bw-band-grid guard against (icvoss/django-brickwork#136). */
.bw-docs-layout > * {
  min-inline-size: 0;
}

/* An unfilled region renders nothing, never empty chrome or a stray gap
 * (BR-BW-TPL-006), matching .bw-subnav/.bw-breadcrumbs/.bw-footer above. */
.bw-docs-layout__header:empty,
.bw-docs-layout__footer:empty {
  display: none;
}

/* Versioned-content archetype (#414): title stack beside the version
 * switcher in docs_header. Wraps on narrow viewports so the switcher stays
 * reachable under the heading rather than competing for one row. */
.bw-docs-version-header {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--bw-space-4);
}
.bw-docs-version-header > :first-child {
  flex: 1 1 16rem;
  min-inline-size: 0;
}
.bw-docs-version-header .bw-version-switch {
  flex: 0 0 auto;
  margin-block-start: var(--bw-space-1);
}

/* In-page TOC composition (Beat S7 craft, first-class since #627): ADR-091
 * declines a package-owned TOC region because heading trees are a
 * content-pipeline concern. {% bw_toc %} / _toc.html is the consumer-authored
 * control once heading ids are known: a labelled nav landmark, not a
 * generator. */
.bw-docs-toc {
  margin: 0;
  margin-block-end: var(--bw-space-6);
  padding: var(--bw-space-4) var(--bw-space-5);
  border: var(--bw-size-border-hairline) solid var(--bw-color-border);
  border-radius: var(--bw-radius-lg);
  background: var(--bw-color-surface-raised);
  max-inline-size: var(--bw-size-max-width-prose);
}

.bw-docs-toc__title {
  margin: 0;
  margin-block-end: var(--bw-space-3);
  color: var(--bw-color-fg);
  font-family: var(--bw-font-family-display);
  font-size: var(--bw-text-label-size);
  font-weight: var(--bw-font-weight-semibold);
}

.bw-docs-toc__list {
  margin: 0;
  padding: 0;
  list-style: none;
}

.bw-docs-toc__list > li + li {
  margin-block-start: var(--bw-space-2);
}

.bw-docs-toc__list a {
  display: inline-flex;
  align-items: center;
  /* WCAG 2.5.8 AA target size (#208): TOC entries are navigational controls. */
  min-block-size: 1.5rem;
  color: var(--bw-color-fg-muted);
  font-size: var(--bw-text-body-sm-size);
  line-height: var(--bw-text-body-sm-line-height);
  text-decoration: none;
}

.bw-docs-toc__list a:hover {
  color: var(--bw-color-accent);
}

.bw-docs-toc__list a:focus-visible {
  outline: 2px solid var(--bw-color-accent);
  outline-offset: 2px;
}

/* Nested heading entries read indented without inventing a second TOC
 * chrome; the indent is typography only. */
.bw-docs-toc__list .bw-docs-toc__sub {
  padding-inline-start: var(--bw-space-4);
}

/* A bare <a> in docs_header, docs_footer or the docs rail takes themed ink,
 * never the UA default blue. This mirrors the marketing footer's rule
 * (BR-BW-MKT-002, .bw-marketing-footer__inner) and exists for the same
 * reason: those blocks are consumer-owned prose, so the obvious thing to
 * write there is a plain anchor, and without this it renders #0000ee.
 * Against the dark theme's surface that measured 2.12:1, an axe
 * color-contrast failure at the 4.5:1 AA bar. First found by the docs
 * fixture's own dark-theme sweep on the footer before this shell had
 * shipped to anyone; re-found on docs_header by the editorial article
 * archetype (icvoss/django-brickwork#415) whose byline author link sits
 * outside .bw-prose. Written at zero specificity via :where() so a
 * consumer's own class on the anchor wins without !important.
 * :not(.bw-btn) excludes a bw_button composed into the slot, since buttons
 * carry their own tier, matching the marketing rule this follows.
 * The 1.5rem floor is the WCAG 2.5.8 AA target size
 * (icvoss/django-brickwork#208) and the coarse-pointer tier below is
 * 2.5.5/platform-HIG (icvoss/django-brickwork#242); a fine-pointer desktop
 * render stays at the 24px floor and the layout does not shift. */
.bw-docs-layout__header :where(a):not(.bw-btn),
.bw-docs-layout__footer :where(a):not(.bw-btn),
.bw-docs-layout__nav-body :where(a):not(.bw-btn) {
  display: inline-flex;
  align-items: center;
  min-block-size: 1.5rem;
  color: var(--bw-color-fg-muted);
  text-decoration: none;
  transition: var(--bw-transition-colors);
}

.bw-docs-layout__header :where(a:hover):not(.bw-btn),
.bw-docs-layout__footer :where(a:hover):not(.bw-btn),
.bw-docs-layout__nav-body :where(a:hover):not(.bw-btn) {
  color: var(--bw-color-fg);
}

@media (pointer: coarse) {
  .bw-docs-layout__header :where(a):not(.bw-btn),
  .bw-docs-layout__footer :where(a):not(.bw-btn),
  .bw-docs-layout__nav-body :where(a):not(.bw-btn) {
    min-block-size: var(--bw-size-touch-target-min);
  }
}

/* --- Auth + centred shells ------------------------------------------------- */

/* grid-template-columns (icvoss/django-brickwork#209): a single implicit
 * grid track defaults to auto, sized to fit its content's min-content
 * width; unbreakable content inside .bw-auth__panel (a long URL, an
 * unwrapped error string) grows that track past the viewport, and since the
 * panel's own `inline-size: min(28rem, 100%)` resolves its 100% against
 * that oversized track rather than the viewport, the panel itself follows
 * it wide. minmax(0, 1fr) caps the track at the available inline space,
 * the grid counterpart of the min-inline-size: 0 fix flex items need for
 * the same "child refuses to shrink below its content" hazard (see #125's
 * note on .bw-marketing-header__nav). */
.bw-auth,
.bw-centred {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  place-items: center;
  min-block-size: 100vh;
  padding: var(--bw-space-6);
}

.bw-auth__panel,
.bw-centred__panel {
  inline-size: min(var(--bw-size-max-width-modal-sm), 100%);
  background: var(--bw-color-surface-raised);
  border: var(--bw-size-border-hairline) solid
    color-mix(in oklab, var(--bw-color-fg) 12%, var(--bw-color-surface-raised));
  border-radius: var(--bw-radius-lg);
  /* Alone on a sunken page: elev-2 plus soft ambient under the token. */
  box-shadow:
    var(--bw-elevation-2),
    0 8px 20px -6px oklch(0 0 0 / 0.12);
  padding: var(--bw-space-8);
  outline: none;
}

.bw-auth__header {
  display: flex;
  justify-content: center;
  margin-block-end: var(--bw-density-stack-gap);
}

/* A plain <a> in the panel's prose ("Back to sign in", "Forgotten your
 * password?") is the documented default shape for auth examples, but the
 * panel itself sets no colour for it, so it fell back to the UA default
 * blue, invisible against a dark-theme surface (WCAG 1.4.3, caught by the
 * W0.3 archetype harness). Same fix as .bw-marketing-footer__inner :where(a)
 * (BR-BW-MKT-002): theme it explicitly, never leave a prose link unstyled. */
.bw-auth__panel :where(a),
.bw-centred__panel :where(a) {
  color: var(--bw-color-accent);
}

.bw-auth__panel :where(a:hover),
.bw-centred__panel :where(a:hover) {
  color: var(--bw-color-accent-hover);
}

/* --- Three-region layout (SHL-001 / brickwork#700): [data-layout="regions"] -- */

/* Full-width topbar above sibling rail + contextual sidebar + workspace.
 * .bw-app__main dissolves (display: contents) so the topbar and .bw-app__body
 * become grid items; the body is a real flex column in the workspace area.
 * Track widths are CSS variables so an empty or omitted rail or sidebar
 * collapses to 0 and the workspace reclaims the space (brickwork#703). The
 * rail tokens name the complete grid track, including its chrome. */

.bw-app[data-layout="regions"] {
  --bw-shell-rail-track: var(--bw-density-rail-width);
  --bw-shell-sidebar-track: var(--bw-density-sidebar-width);
  display: grid;
  /* Fallbacks keep these local cascade hooks off the emitted-token gate. */
  grid-template-columns: var(--bw-shell-rail-track, 0px) var(--bw-shell-sidebar-track, 0px) minmax(0, 1fr);
  grid-template-rows: auto minmax(0, 1fr);
  grid-template-areas:
    "topbar topbar topbar"
    "rail sidebar main";
}

.bw-app[data-layout="regions"] .bw-rail {
  display: flex;
  grid-area: rail;
  inline-size: auto; /* track owns the width, including rail chrome */
  min-inline-size: 0;
  /* Icon links carry their own inline target padding. Keeping this outer
   * chrome at zero lets the density token remain the complete rail track. */
  padding-inline: 0;
  /* The icon list owns clipping in its resting state. This parent must not
   * clip its expanded child at the track edge. */
  overflow: visible;
}

.bw-app[data-layout="regions"] .bw-sidebar {
  grid-area: sidebar;
  inline-size: auto;
  min-inline-size: 0;
}

.bw-app[data-layout="regions"] .bw-app__main {
  display: contents;
}

.bw-app[data-layout="regions"] .bw-topbar {
  grid-area: topbar;
}

.bw-app[data-layout="regions"] .bw-app__body {
  display: flex;
  flex-direction: column;
  min-width: 0;
  grid-area: main;
}

/* Empty-suppression / width reclaim (brickwork#703): unfilled rail or
 * sidebar blocks leave truly :empty nav containers; an overridden region can
 * omit its aside altogether. Both cases collapse the matching track, so no
 * empty strip remains. */
.bw-app[data-layout="regions"]:has(.bw-rail__nav:empty) {
  --bw-shell-rail-track: 0px;
}
.bw-app[data-layout="regions"]:has(.bw-rail__nav:empty) .bw-rail {
  display: none;
}
.bw-app[data-layout="regions"]:has(.bw-sidebar__nav:empty) {
  --bw-shell-sidebar-track: 0px;
}
.bw-app[data-layout="regions"]:has(.bw-sidebar__nav:empty) .bw-sidebar {
  display: none;
}
.bw-app[data-layout="regions"]:not(:has(.bw-rail)) {
  --bw-shell-rail-track: 0px;
}
.bw-app[data-layout="regions"]:not(:has(.bw-sidebar)) {
  --bw-shell-sidebar-track: 0px;
}

/* The icon rail's renderer grows on hover and focus-within. Grow this grid
 * track to the documented expanded rail token too; the list owns its own
 * resting-state clipping, while the parent remains visible at the edge. */
.bw-app[data-layout="regions"]:has(.bw-nav-rail__list--icons:hover),
.bw-app[data-layout="regions"]:has(.bw-nav-rail__list--icons:focus-within) {
  --bw-shell-rail-track: var(--bw-density-rail-width-expanded);
}

/* Collapsed contextual sidebar still narrows its track in regions mode. */
.bw-app[data-layout="regions"]:has(.bw-sidebar[data-bw-collapsed]) {
  --bw-shell-sidebar-track: var(--bw-density-sidebar-width-collapsed);
}

/* --- Topbar-primary layout (SHL-001): [data-layout="topbar"] ---------------- */

/* Same DOM as the sidebar layout, restyled: the aside becomes a full-width
 * horizontal nav band directly under the topbar. `.bw-app__main` dissolves
 * (display: contents) so the topbar, the band, and the workspace order as
 * siblings in one column; `order: -1` seats the topbar first while everything
 * else keeps document order (band, subnav, breadcrumbs, workspace). Every rule
 * here is scoped under the attribute, so no sidebar-layout default changes.
 *
 * Reading-order note (A11Y-015): the shared DOM keeps the primary nav before
 * the topbar in reading/tab order in BOTH layouts; here the topbar is lifted
 * visually above the band, so visual order and DOM order differ by that one
 * adjacent swap. This is the deliberate cost of one DOM for two shapes (no
 * template fork); the skip link remains the first tab stop either way.
 */

.bw-app[data-layout="topbar"] {
  display: flex; /* flex column absorbs the optional subnav/breadcrumbs rows */
  flex-direction: column;
}

[data-layout="topbar"] .bw-app__main {
  display: contents;
}

[data-layout="topbar"] .bw-topbar {
  order: -1;
}

/* The band: full width, hairline below instead of at the inline end,
 * background unchanged. No z-index of its own: the sticky topbar's
 * --bw-z-sticky already paints above the static band on scroll. */
[data-layout="topbar"] .bw-sidebar {
  flex-direction: row;
  align-items: center;
  border-inline-end: none;
  border-block-end: var(--bw-size-border-hairline) solid var(--bw-color-border);
  padding-block: 0; /* vertical rhythm comes from the nav row's own padding */
  padding-inline: var(--bw-density-page-gutter-inline);
}

[data-layout="topbar"] .bw-sidebar__header {
  padding-block-end: 0;
}

/* The switcher slot sits inline at the row start: auto width, the hairline
 * rotates from below (block-end) to the trailing edge (inline-end), and the
 * gap to the nav stays the density section gap, rotated to the inline axis
 * so a consumer still never guesses it. */
[data-layout="topbar"] .bw-sidebar > .bw-sidebar__switcher {
  flex: 0 0 auto;
  margin-block-end: 0;
  margin-inline-end: var(--bw-density-section-gap);
  border-block-end: none;
  border-inline-end: var(--bw-size-border-hairline) solid var(--bw-color-border);
}

/* The band never scrolls internally; the wrapping nav row grows it instead. */
[data-layout="topbar"] .bw-sidebar__nav {
  overflow-y: visible;
}

/* Footer content seats at the row end, its divider rotated to match. */
[data-layout="topbar"] .bw-sidebar__footer {
  margin-inline-start: auto;
  padding-block-start: 0;
  padding-inline-start: var(--bw-density-stack-gap);
  border-block-start: none;
  border-inline-start: var(--bw-size-border-hairline) solid var(--bw-color-border);
}

/* --- Responsive: collapse the sidebar into the mobile drawer at md --------- */

@media (width < 48rem) { /* SHL-017: the fixed sidebar-collapse point, ADR-079 */
  .bw-app {
    grid-template-columns: 1fr; /* sidebar out of flow, drawer takes over */
  }
  .bw-app[data-layout="regions"] {
    grid-template-columns: 1fr;
    grid-template-areas:
      "topbar"
      "main";
  }
  .bw-sidebar,
  .bw-rail,
  .bw-app[data-layout="regions"] .bw-sidebar,
  .bw-app[data-layout="regions"] .bw-rail {
    /* Covers sidebar, topbar-band, and regions columns: desktop nav hides
       below md and the single drawer takes over in every shape. One drawer
       tree (never two): fill mobile_nav with the full reachable nav. */
    display: none;
  }
  .bw-drawer {
    display: block;
  }
}

/* --- Dark-theme resets for light-only shell ambient (beautiful defaults) --- */

/* Soft ambient under elevation-* is a LIGHT-theme treatment so chrome reads
 * on white canvas; dark elevation tokens already carry their own depth. */
[data-theme="dark"] .bw-topbar {
  box-shadow: var(--bw-elevation-1);
}
[data-theme="dark"] .bw-auth__panel,
[data-theme="dark"] .bw-centred__panel {
  box-shadow: var(--bw-elevation-2);
}


/* ==== layer: nav ==== */
/* Navigation styling. Active state uses a leading (inline-start) border plus a
 * background tint (NAV-007), both from theme-aware tokens with a logical border
 * so it flips correctly under RTL. Structure only; visibility/active state is
 * computed server-side.
 */

.bw-nav__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--bw-space-0-5);
}

.bw-nav__list--nested {
  margin-inline-start: var(--bw-space-4);
  margin-block-start: var(--bw-space-0-5);
}

.bw-nav__link {
  display: flex;
  align-items: center;
  gap: var(--bw-space-2);
  /* Fill the list column and allow shrink below content min-size so
   * .bw-nav__label's text-overflow: ellipsis can engage in a narrow rail
   * (docs shell, long page titles). Without this the flex link sizes to its
   * label and overflows the column; measured on icvoss.com docs rail
   * (icvoss/django-brickwork#623). Horizontal bands reset inline-size below. */
  inline-size: 100%;
  min-inline-size: 0;
  padding-block: var(--bw-density-row-padding-block);
  padding-inline: var(--bw-density-row-padding-inline);
  border-radius: var(--bw-radius-md);
  color: var(--bw-color-fg);
  /* Chrome runs at body-sm so the workspace content (body-md) is the point of
   * the page; matches the breadcrumbs and account menu. */
  font-size: var(--bw-text-body-sm-size);
  line-height: var(--bw-text-body-sm-line-height);
  text-decoration: none;
  border-inline-start: var(--bw-size-border-nav-marker) solid transparent;
  transition: var(--bw-transition-colors);
}

/* Transparent resting background, so the transient overlays apply directly as
 * background-color; the active item instead layers them via background-image
 * so the persistent tint composes rather than being replaced.
 */
.bw-nav__link:hover {
  background-color: var(--bw-state-hover-overlay);
}

.bw-nav__link:active {
  background-color: var(--bw-state-active-overlay);
  transition-duration: var(--bw-duration-instant);
}

.bw-nav__link--active {
  background-color: var(--bw-component-nav-item-active-bg);
  color: var(--bw-component-nav-item-active-text);
  border-inline-start-color: var(--bw-component-nav-item-active-border);
  font-weight: var(--bw-font-weight-semibold);
}

/* Re-assert background-color: the plain :hover/:active rules outrank
 * .bw-nav__link--active alone, and the tint must not vanish under the faint
 * overlay.
 */
.bw-nav__link--active:hover {
  background-color: var(--bw-component-nav-item-active-bg);
  background-image: linear-gradient(
    var(--bw-state-hover-overlay),
    var(--bw-state-hover-overlay)
  );
}

.bw-nav__link--active:active {
  background-color: var(--bw-component-nav-item-active-bg);
  background-image: linear-gradient(
    var(--bw-state-active-overlay),
    var(--bw-state-active-overlay)
  );
  transition-duration: var(--bw-duration-instant);
}

/* An ancestor of the active leaf gets a lighter treatment (NAV-008). */
.bw-nav__link--active-ancestor {
  color: var(--bw-component-nav-item-active-text);
  font-weight: var(--bw-font-weight-medium);
}

.bw-nav__link--disabled {
  display: flex;
  align-items: center;
  gap: var(--bw-space-2);
  padding-block: var(--bw-density-row-padding-block);
  padding-inline: var(--bw-density-row-padding-inline);
  border-radius: var(--bw-radius-md);
  /* Flat disabled ink (COL-018 pattern): colour token alone, never opacity.
     Stacking opacity on top historically double-dimmed to roughly 1.9:1. */
  color: var(--bw-component-nav-item-disabled-text);
  font-size: var(--bw-text-body-sm-size);
  line-height: var(--bw-text-body-sm-line-height);
  cursor: not-allowed;
  /* Transparent marker matches the enabled link's so rows stay aligned. */
  border-inline-start: var(--bw-size-border-nav-marker) solid transparent;
}

.bw-nav__label {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* labels="wrap" on {% bw_nav %} (icvoss/django-brickwork#671): clear the
 * truncate defaults so long document titles remain readable in a docs rail.
 * App sidebars keep the truncate default by omitting the option. */
.bw-nav__list--labels-wrap .bw-nav__label {
  overflow: visible;
  text-overflow: unset;
  white-space: normal;
}

.bw-nav__badge {
  flex-shrink: 0;
  min-inline-size: var(--bw-space-5);
  padding-block: var(--bw-space-0-5);
  padding-inline: var(--bw-space-1);
  border-radius: var(--bw-radius-full);
  /* A quiet bordered count chip: the number stays legible without out-shouting
   * the primary button (the inverse pill was the darkest object on the page). */
  background: var(--bw-color-surface-sunken);
  color: var(--bw-color-fg);
  border: var(--bw-size-border-hairline) solid var(--bw-color-border);
  font-size: var(--bw-text-caption-size);
  font-weight: var(--bw-font-weight-medium);
  font-variant-numeric: tabular-nums;
  line-height: var(--bw-font-line-height-none);
  text-align: center;
}

.bw-nav__external {
  flex-shrink: 0;
  color: var(--bw-color-fg-muted);
  transition: var(--bw-transition-colors);
}

/* The icon follows the row's hover/active colour so it reads as part of the
 * row rather than a detached glyph.
 */
.bw-nav__link:hover .bw-nav__external,
.bw-nav__link--active .bw-nav__external {
  color: inherit;
}

.bw-nav__section {
  margin-block-start: var(--bw-density-stack-gap);
}

/* Section labels adopt the overline type role (uppercase applied here at the
 * component, per the role contract).
 */
.bw-nav__section-label {
  display: block;
  padding-inline: var(--bw-density-row-padding-inline);
  padding-block-end: var(--bw-space-1);
  color: var(--bw-component-nav-section-text);
  font-family: var(--bw-text-overline-family);
  font-size: var(--bw-text-overline-size);
  font-weight: var(--bw-text-overline-weight);
  line-height: var(--bw-text-overline-line-height);
  text-transform: uppercase;
  letter-spacing: var(--bw-text-overline-tracking);
}

/* --- Horizontal band (SHL-001 topbar + {% bw_nav orientation="horizontal" }) */

/* Two entry points share one shape (NAV-024 / #430):
 *   1. [data-layout="topbar"] .bw-sidebar... : shell layout forces a horizontal
 *      band; the SAME nav render inside the mobile drawer keeps its vertical
 *      column (NAV-016: one DOM, two shapes).
 *   2. .bw-nav__list--horizontal : explicit orientation on {% bw_nav %} so an
 *      app-shell consumer can place a horizontal row without reaching into
 *      package-private BEM.
 * Nested lists (.bw-nav__list--nested) deliberately keep their vertical stack
 * inside the parent item: there is no dropdown JS on the no-JS floor
 * (BR-BW-HTMX-001), so a nested group simply flows in the wrap as a taller
 * item. Known limitation of the horizontal band; a consumer wanting true
 * dropdown submenus layers them via progressive enhancement (BR-BW-TPL-002).
 */

/* The top-level list flows as a wrapping inline row; a section's own list
 * joins the row so its items sit beside its label. */
[data-layout="topbar"] .bw-sidebar__nav > .bw-nav__list,
[data-layout="topbar"] .bw-sidebar .bw-nav__section > .bw-nav__list,
.bw-nav__list--horizontal,
.bw-nav__list--horizontal > .bw-nav__section > .bw-nav__list {
  flex-direction: row;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--bw-space-1);
}

[data-layout="topbar"] .bw-sidebar__nav > .bw-nav__list,
.bw-nav__list--horizontal {
  padding-block: var(--bw-density-row-padding-block);
}

/* Links keep their radius/hover/active treatment; the 3px active marker moves
 * from the leading (inline-start) edge to the bottom (block-end) edge, where
 * it reads correctly in a horizontal bar. Same marker width token; the
 * transparent resting border keeps rows aligned exactly as in the sidebar.
 * .bw-nav__link--disabled carries .bw-nav__link too, so both are covered. */
[data-layout="topbar"] .bw-sidebar .bw-nav__link,
.bw-nav__list--horizontal .bw-nav__link {
  /* Content-sized chips in a wrapping row; do not stretch to 100% of the band. */
  inline-size: auto;
  border-inline-start: none;
  border-block-end: var(--bw-size-border-nav-marker) solid transparent;
}

[data-layout="topbar"] .bw-sidebar .bw-nav__link--active,
.bw-nav__list--horizontal .bw-nav__link--active {
  border-block-end-color: var(--bw-component-nav-item-active-border);
}

/* Sections sit inline in the row; their labels become overline separators. */
[data-layout="topbar"] .bw-sidebar .bw-nav__section,
.bw-nav__list--horizontal > .bw-nav__section {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  margin-block-start: 0;
}

[data-layout="topbar"] .bw-sidebar .bw-nav__section-label,
.bw-nav__list--horizontal > .bw-nav__section > .bw-nav__section-label {
  padding-block-end: 0;
  margin-inline: var(--bw-space-2);
}

/* --- Marketing-header renderer ({% bw_nav_header %}, brickwork#102) --------- */

/* A horizontal row of plain-weight links over the same prepared NavItem tree,
 * matching the visual weight the marketing shell already gives plain anchors
 * (.bw-marketing-header__nav :where(a) in marketing.css: label type role,
 * muted resting ink, ink on hover), plus the active treatment plain anchors
 * cannot express. Flat by design: the template renders no nested lists, so no
 * dropdown CSS exists here (BR-BW-HTMX-001; a consumer layering a disclosure
 * does so as progressive enhancement, BR-BW-TPL-002).
 *
 * Every link rule is scoped under .bw-nav-header__list (the renderer's own
 * markup guarantees the ancestor): marketing.css's
 * `.bw-marketing-header__nav :where(a)` rule shares single-class specificity
 * and sits LATER in the compiled bundle, so a bare .bw-nav-header__link rule
 * would lose the cascade inside the marketing header (notably the active
 * underline against its `text-decoration: none`). The descendant scope keeps
 * these rules winning wherever the row is placed.
 */
.bw-nav-header__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  /* the same gap the marketing shell gives its plain-anchor nav row */
  gap: var(--bw-space-4);
}

.bw-nav-header__item {
  display: inline-flex;
  align-items: center;
}

/* min-block-size (icvoss/django-brickwork#208, same shape swept in on the
 * fix for the three named components): a horizontal nav-bar link is a
 * navigational control, not inline prose, so it needs the WCAG 2.5.8 floor;
 * it was sized to its label's line-height alone (16-18px at 375px). */
.bw-nav-header__list .bw-nav-header__link {
  display: inline-flex;
  align-items: center;
  gap: var(--bw-space-1);
  min-block-size: 1.5rem;
  color: var(--bw-color-fg-muted);
  font-size: var(--bw-text-label-size);
  font-weight: var(--bw-text-label-weight);
  line-height: var(--bw-text-label-line-height);
  text-decoration: none;
  transition: var(--bw-transition-colors);
}

.bw-nav-header__list .bw-nav-header__link:hover {
  color: var(--bw-color-fg);
}

/* Active: full ink plus an accent underline marker, so the current page is
 * signalled by more than colour alone (the horizontal counterpart of the
 * sidebar's inline-start marker, NAV-007). The exact active item carries
 * aria-current="page"; an ancestor of the active route (whose children this
 * flat renderer does not show) gets the same visual treatment. */
.bw-nav-header__list .bw-nav-header__link--active,
.bw-nav-header__list .bw-nav-header__link--active-ancestor {
  color: var(--bw-color-fg);
  text-decoration-line: underline;
  text-decoration-color: var(--bw-color-accent);
  text-decoration-thickness: var(--bw-size-border-nav-marker);
  text-underline-offset: var(--bw-space-1-5);
}

/* The colour token alone dims the entry (the documented nav exception); the
 * hover override keeps a disabled entry inert under the pointer. */
.bw-nav-header__list .bw-nav-header__link--disabled,
.bw-nav-header__list .bw-nav-header__link--disabled:hover {
  color: var(--bw-component-nav-item-disabled-text);
  cursor: not-allowed;
}

/* The same quiet bordered count chip as the sidebar renderer's badge. */
.bw-nav-header__badge {
  flex-shrink: 0;
  min-inline-size: var(--bw-space-5);
  padding-block: var(--bw-space-0-5);
  padding-inline: var(--bw-space-1);
  border-radius: var(--bw-radius-full);
  background: var(--bw-color-surface-sunken);
  color: var(--bw-color-fg);
  border: var(--bw-size-border-hairline) solid var(--bw-color-border);
  font-size: var(--bw-text-caption-size);
  font-weight: var(--bw-font-weight-medium);
  font-variant-numeric: tabular-nums;
  line-height: var(--bw-font-line-height-none);
  text-align: center;
}

.bw-nav-header__external {
  flex-shrink: 0;
  color: var(--bw-color-fg-muted);
  transition: var(--bw-transition-colors);
}

.bw-nav-header__list .bw-nav-header__link:hover .bw-nav-header__external {
  color: inherit;
}

/* --- Compact rail renderer ({% bw_nav_rail %}, brickwork#82) ---------------- */

/* Tier one of the capability-rail + contextual-sidebar (two-tier) layout: a
 * compact icon+label column over the same prepared NavItem tree. Labels stay
 * VISIBLE at caption size in the default density (a designed compact state,
 * not the collapsed sidebar's clipped labels, SHL-004). density="icons"
 * (brickwork#701) rests icon-only and expands on hover/focus-within. Link
 * entries are real anchors; menu_trigger items share the link skin as
 * buttons (brickwork#702). Children belong to the paired contextual
 * {% bw_nav %}, so no flyout panel CSS ships (BR-BW-HTMX-001). */
.bw-nav-rail__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--bw-space-1);
  /* compact by construction: as wide as the widest entry, floored at the
   * collapsed-sidebar width so a short label set still reads as a rail */
  inline-size: max-content;
  min-inline-size: var(--bw-density-sidebar-width-collapsed);
}

.bw-nav-rail__link {
  position: relative; /* seats the badge on the entry's corner */
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--bw-space-0-5);
  padding-block: var(--bw-space-2);
  padding-inline: var(--bw-space-2);
  border-radius: var(--bw-radius-md);
  color: var(--bw-color-fg);
  font-size: var(--bw-text-caption-size);
  font-weight: var(--bw-font-weight-medium);
  line-height: var(--bw-text-caption-line-height);
  text-align: center;
  text-decoration: none;
  border: none;
  border-inline-start: var(--bw-size-border-nav-marker) solid transparent;
  background: transparent;
  cursor: pointer;
  font-family: inherit;
  transition: var(--bw-transition-colors);
}

.bw-nav-rail__link:hover {
  background-color: var(--bw-state-hover-overlay);
}

.bw-nav-rail__link:active {
  background-color: var(--bw-state-active-overlay);
  transition-duration: var(--bw-duration-instant);
}

/* Active state mirrors the sidebar treatment (NAV-007): tint, active ink, the
 * inline-start marker. The rail highlights its AREA: the exact active entry
 * and an ancestor of the active route (whose children live in the contextual
 * tier) read the same; aria-current sits on the exact item only. */
.bw-nav-rail__link--active,
.bw-nav-rail__link--active-ancestor {
  background-color: var(--bw-component-nav-item-active-bg);
  color: var(--bw-component-nav-item-active-text);
  border-inline-start-color: var(--bw-component-nav-item-active-border);
  font-weight: var(--bw-font-weight-semibold);
}

/* Re-assert background-color under the transient overlays, exactly as the
 * sidebar renderer does: the persistent tint composes, never vanishes. */
.bw-nav-rail__link--active:hover,
.bw-nav-rail__link--active-ancestor:hover {
  background-color: var(--bw-component-nav-item-active-bg);
  background-image: linear-gradient(
    var(--bw-state-hover-overlay),
    var(--bw-state-hover-overlay)
  );
}

.bw-nav-rail__link--active:active,
.bw-nav-rail__link--active-ancestor:active {
  background-color: var(--bw-component-nav-item-active-bg);
  background-image: linear-gradient(
    var(--bw-state-active-overlay),
    var(--bw-state-active-overlay)
  );
  transition-duration: var(--bw-duration-instant);
}

/* Colour token alone dims the entry (the documented nav exception); the hover
 * override keeps a disabled entry inert under the pointer. */
.bw-nav-rail__link--disabled,
.bw-nav-rail__link--disabled:hover {
  background-color: transparent;
  color: var(--bw-component-nav-item-disabled-text);
  cursor: not-allowed;
}

/* Wraps rather than truncates: a rail entry's visible name IS the accessible
 * name and the destination, so it never ellipsises away. */
.bw-nav-rail__label {
  overflow-wrap: anywhere;
}

/* The count chip seats on the entry's top trailing corner, the standard rail
 * badge affordance; same quiet bordered treatment as the sidebar badge. */
.bw-nav-rail__badge {
  position: absolute;
  inset-block-start: var(--bw-space-1);
  inset-inline-end: var(--bw-space-1);
  min-inline-size: var(--bw-space-5);
  padding-block: var(--bw-space-0-5);
  padding-inline: var(--bw-space-1);
  border-radius: var(--bw-radius-full);
  background: var(--bw-color-surface-sunken);
  color: var(--bw-color-fg);
  border: var(--bw-size-border-hairline) solid var(--bw-color-border);
  font-size: var(--bw-text-caption-size);
  font-weight: var(--bw-font-weight-medium);
  font-variant-numeric: tabular-nums;
  line-height: var(--bw-font-line-height-none);
  text-align: center;
}

.bw-nav-rail__external {
  color: var(--bw-color-fg-muted);
  transition: var(--bw-transition-colors);
}

.bw-nav-rail__link:hover .bw-nav-rail__external,
.bw-nav-rail__link--active .bw-nav-rail__external {
  color: inherit;
}

/* --- Icon-only / hover-expand density (brickwork#701) ----------------------- */

/* Resting: --bw-density-rail-width, labels clipped (still in the a11y tree).
 * Hover or focus-within: expand to --bw-density-rail-width-expanded and
 * reveal labels. No JS required; prefers-reduced-motion skips the width
 * transition only. */
.bw-nav-rail__list--icons {
  inline-size: var(--bw-density-rail-width);
  min-inline-size: var(--bw-density-rail-width);
  max-inline-size: var(--bw-density-rail-width-expanded);
  overflow: hidden;
  transition: inline-size var(--bw-duration-normal) var(--bw-ease-standard);
}

.bw-nav-rail__list--icons .bw-nav-rail__link {
  flex-direction: row;
  justify-content: flex-start;
  gap: var(--bw-space-2);
  text-align: start;
  min-block-size: var(--bw-size-touch-target-min);
  padding-inline: var(--bw-space-2);
}

.bw-nav-rail__list--icons .bw-nav-rail__icon {
  flex-shrink: 0;
}

.bw-nav-rail__list--icons .bw-nav-rail__label,
.bw-nav-rail__list--icons .bw-nav-rail__external {
  position: absolute;
  inline-size: var(--bw-space-px);
  block-size: var(--bw-space-px);
  margin: calc(var(--bw-space-px) * -1);
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.bw-nav-rail__list--icons:hover,
.bw-nav-rail__list--icons:focus-within {
  inline-size: var(--bw-density-rail-width-expanded);
  min-inline-size: var(--bw-density-rail-width-expanded);
}

.bw-nav-rail__list--icons:hover .bw-nav-rail__label,
.bw-nav-rail__list--icons:focus-within .bw-nav-rail__label,
.bw-nav-rail__list--icons:hover .bw-nav-rail__external,
.bw-nav-rail__list--icons:focus-within .bw-nav-rail__external {
  position: static;
  inline-size: auto;
  block-size: auto;
  margin: 0;
  overflow: visible;
  clip-path: none;
  white-space: normal;
}

@media (prefers-reduced-motion: reduce) {
  .bw-nav-rail__list--icons {
    transition: none;
  }
}

/* --- The two-tier pairing container (brickwork#82) -------------------------- */

/* A consumer-placed wrapper inside {% block sidebar %}: the rail (tier one)
 * beside a contextual {% bw_nav %} (tier two), hairline-divided. The sidebar's
 * own width token seats the pair: widen --bw-density-sidebar-width in the
 * brand CSS to fit both tiers; the wrapper imposes no width of its own.
 * Prefer layout="regions" (brickwork#700) when the rail and sidebar should be
 * sibling shell columns under a full-width topbar instead. */
.bw-nav-two-tier {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  column-gap: var(--bw-space-3);
  align-items: start;
  block-size: 100%;
}

.bw-nav-two-tier > .bw-nav-rail__list {
  block-size: 100%;
  padding-inline-end: var(--bw-space-2);
  border-inline-end: var(--bw-size-border-hairline) solid var(--bw-color-border);
}


/* ==== layer: components ==== */
/* Component styling. Structure + theme-token colours + density-token spacing.
 * Logical CSS properties throughout (RTL via `dir`). Nothing here needs JS.
 */

/* --- Buttons --------------------------------------------------------------- */

.bw-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--bw-space-2);
  padding-block: 0;
  padding-inline: var(--bw-density-control-padding-inline);
  block-size: var(--bw-density-control-height);
  border: var(--bw-size-border-hairline) solid transparent;
  border-radius: var(--bw-component-button-radius);
  font: inherit;
  font-family: var(--bw-text-label-family);
  font-size: var(--bw-text-label-size);
  font-weight: var(--bw-text-label-weight);
  line-height: var(--bw-text-label-line-height);
  letter-spacing: var(--bw-text-label-tracking);
  cursor: pointer;
  text-decoration: none;
  transition:
    var(--bw-transition-colors),
    var(--bw-transition-shadow),
    var(--bw-transition-transform);
}
.bw-btn--sm {
  block-size: var(--bw-size-control-height-sm);
  padding-inline: var(--bw-space-2);
}
/* md is the base .bw-btn size (icvoss/django-brickwork#130): every shipped
 * template that names a size emits bw-btn--md explicitly rather than leaving
 * it implicit, so the class needs its own rule even though it restates the
 * base values, exactly as .bw-btn--sm and .bw-btn--lg restate theirs relative
 * to the base. Without this rule the class existed in markup with nothing in
 * the stylesheet naming it, which is the defect class this issue is about:
 * the base values happened to make it look correct by accident. */
.bw-btn--md {
  block-size: var(--bw-density-control-height);
  padding-inline: var(--bw-density-control-padding-inline);
}
.bw-btn--lg {
  block-size: var(--bw-size-control-height-lg);
  padding-inline: var(--bw-space-4);
}
/* Solid buttons carry a border in their own hue (0.7.0 craft): 80% of the fill
 * toward black is the Radix solid-button edge, a shade the fill visibly sits
 * inside (decorative against the fill at 1.59:1 light / 1.73:1 dark; it also
 * defines the button against the surface at 8.10:1 / 4.12:1 for primary and
 * 7.74:1 / 3.00:1 for danger). The edge deliberately outlives hover: the
 * hover fill (89% toward black) stays lighter than the 80% edge. */
.bw-btn--primary {
  background: var(--bw-color-accent);
  color: var(--bw-color-fg-on-accent);
  border-color: color-mix(in oklab, var(--bw-color-accent) 80%, black);
}
.bw-btn--primary:not(.bw-btn--disabled):hover {
  background: var(--bw-color-accent-hover);
  box-shadow: var(--bw-component-button-elevation-hover), inset 0 1px 0 0 oklch(1 0 0 / 0.12);
  transform: translateY(var(--bw-component-button-hover-translate));
}
/* Secondary is a raised chip (0.7.0 craft): surface fill, hairline border and
 * resting button elevation; hover sinks the fill to the sunken surface and
 * firms the border instead of layering the translucent wash (ink on the
 * sunken fill measures 16.28:1 light / 16.25:1 dark). */
.bw-btn--secondary {
  background: var(--bw-color-surface);
  color: var(--bw-color-fg);
  border-color: var(--bw-color-border);
}
.bw-btn--secondary:not(.bw-btn--disabled) {
  box-shadow: var(--bw-component-button-elevation);
}
.bw-btn--secondary:not(.bw-btn--disabled):hover {
  border-color: var(--bw-color-border-strong);
  background-color: var(--bw-color-surface-sunken);
  box-shadow: var(--bw-component-button-elevation-hover);
  transform: translateY(var(--bw-component-button-hover-translate));
}
.bw-btn--ghost {
  background: transparent;
  /* Muted at rest so the tertiary action never ties the primary on ink
   * weight; steps up to full fg on hover. */
  color: var(--bw-color-fg-muted);
}
.bw-btn--ghost:not(.bw-btn--disabled):hover {
  background-color: var(--bw-state-hover-overlay);
  color: var(--bw-color-fg);
}
.bw-btn--danger {
  background: var(--bw-color-danger);
  color: var(--bw-color-fg-on-accent);
  /* Same 80%-toward-black edge as primary, in the danger hue (ratios above). */
  border-color: color-mix(in oklab, var(--bw-color-danger) 80%, black);
}
.bw-btn--danger:not(.bw-btn--disabled):hover {
  background-image: linear-gradient(var(--bw-state-hover-overlay), var(--bw-state-hover-overlay));
  box-shadow: var(--bw-component-button-elevation-hover), inset 0 1px 0 0 oklch(1 0 0 / 0.12);
  transform: translateY(var(--bw-component-button-hover-translate));
}
/* Filled action buttons carry physical weight: resting elevation plus a faint
 * top bevel; pressing swaps to an inset shadow so the button visibly sinks.
 * Elevation is the per-button token (#649); the inset highlight is craft. */
.bw-btn--primary:not(.bw-btn--disabled),
.bw-btn--danger:not(.bw-btn--disabled) {
  box-shadow: var(--bw-component-button-elevation), inset 0 1px 0 0 oklch(1 0 0 / 0.12);
}
.bw-btn--primary:not(.bw-btn--disabled):active,
.bw-btn--danger:not(.bw-btn--disabled):active {
  box-shadow: inset 0 1px 2px 0 oklch(0 0 0 / 0.2);
  transform: none;
}
/* Pressed feedback for every variant: the active overlay layers on top of
 * whichever background resolved (DESIGN.md 4.6). Declared after the variant
 * hovers so it wins the background-image cascade; instant so the press reads
 * immediately. Ghost is excluded (its states are solid overlay fills, so a
 * stacked image would double the tint) and gets its own solid active fill. */
.bw-btn:not(.bw-btn--disabled):not(.bw-btn--ghost):active {
  background-image: linear-gradient(var(--bw-state-active-overlay), var(--bw-state-active-overlay));
  transition-duration: var(--bw-duration-instant);
  transform: none;
}
.bw-btn--ghost:not(.bw-btn--disabled):active {
  background-color: var(--bw-state-active-overlay);
  background-image: none;
  transition-duration: var(--bw-duration-instant);
}
/* Hover-lift is opt-in via --bw-component-button-hover-translate (#649).
 * Prefer no transform when the user asks for reduced motion. */
@media (prefers-reduced-motion: reduce) {
  .bw-btn--primary:not(.bw-btn--disabled):hover,
  .bw-btn--secondary:not(.bw-btn--disabled):hover,
  .bw-btn--danger:not(.bw-btn--disabled):hover {
    transform: none;
  }
}
.bw-btn--icon-only {
  inline-size: var(--bw-density-control-height);
  padding-inline: 0;
}
/* Exact square at every size (0.7.0 craft): the sm/lg heights bring their own
 * widths, so an icon button's optical padding is identical on all four sides. */
.bw-btn--sm.bw-btn--icon-only {
  inline-size: var(--bw-size-control-height-sm);
}
.bw-btn--lg.bw-btn--icon-only {
  inline-size: var(--bw-size-control-height-lg);
}
/* Flat disabled colours (COL-018 / #282), not opacity: opacity failed
 * contrast on most surfaces and cannot be gated under brand overrides. */
.bw-btn:disabled,
.bw-btn--disabled {
  background: var(--bw-color-action-disabled-bg);
  background-image: none;
  color: var(--bw-color-action-disabled-text);
  border-color: var(--bw-color-action-disabled-border);
  box-shadow: none;
  cursor: not-allowed;
}

.bw-spinner {
  /* Sized via --bw-icon-size (set inline to a size token), the same mechanism
   * as .bw-icon: an SVG width/height attribute cannot take var() (issue #16). */
  inline-size: var(--bw-icon-size, var(--bw-component-icon-size-sm));
  block-size: var(--bw-icon-size, var(--bw-component-icon-size-sm));
  animation: bw-spin var(--bw-duration-spin) var(--bw-ease-linear) infinite;
}
@keyframes bw-spin {
  to {
    transform: rotate(360deg);
  }
}
/* reduced-motion: a frozen spinner is calmer than a stuck one; meaning is
 * carried by text/aria, not the motion. */
@media (prefers-reduced-motion: reduce) {
  .bw-spinner {
    animation: none;
  }
}

/* --- Badges ---------------------------------------------------------------- */

.bw-badge {
  display: inline-flex;
  align-items: center;
  gap: var(--bw-space-1);
  padding-block: var(--bw-space-0-5);
  padding-inline: var(--bw-space-2);
  /* transparent hairline so intent variants (tinted borders) keep the same
     geometry as the neutral badge */
  border: var(--bw-size-border-hairline) solid transparent;
  border-radius: var(--bw-radius-full);
  font-size: var(--bw-text-caption-size);
  font-weight: var(--bw-font-weight-medium);
  font-variant-numeric: tabular-nums;
  background: var(--bw-color-surface-sunken);
  color: var(--bw-color-fg-muted);
}
/* The documented default variant (#100): bw_badge's no-args default is
 * variant="neutral", so the contract needs a real .bw-badge--neutral rule,
 * not a look left implicit in the base class. The values restate the base's
 * neutral chip treatment (sunken surface, muted fg, the transparent hairline
 * that keeps every variant on the same geometry); fg-muted on surface-sunken
 * measures AA in both themes (the same pairing muted body text already
 * relies on). Neutral is the correct default per the badge-vs-alert rule
 * (STA-008): a badge carries neutral information, never error communication. */
.bw-badge--neutral {
  background: var(--bw-color-surface-sunken);
  color: var(--bw-color-fg-muted);
  border-color: transparent;
}
.bw-badge--success {
  background: var(--bw-color-success-subtle);
  color: var(--bw-color-success-fg);
  border-color: var(--bw-color-success-border);
}
.bw-badge--warning {
  background: var(--bw-color-warning-subtle);
  color: var(--bw-color-warning-fg);
  border-color: var(--bw-color-warning-border);
}
.bw-badge--danger {
  background: var(--bw-color-danger-subtle);
  color: var(--bw-color-danger-fg);
  border-color: var(--bw-color-danger-border);
}
.bw-badge--info {
  background: var(--bw-color-info-subtle);
  color: var(--bw-color-info-fg);
  border-color: var(--bw-color-info-border);
}

/* --- Alerts ---------------------------------------------------------------- */

.bw-alert {
  display: flex;
  gap: var(--bw-space-3);
  padding: var(--bw-space-4);
  border-radius: var(--bw-radius-lg); /* joins the card radius family */
  border: var(--bw-size-border-hairline) solid var(--bw-color-border);
  margin-block-end: var(--bw-density-stack-gap);
}
/* The text region between the icon and the optional close button
 * (icvoss/django-brickwork#137): without a flex-grow rule of its own it
 * collapses to content width instead of filling the row, matching
 * .bw-toast__message and .bw-modal__body's own flex: 1 1 auto. min-inline-size:
 * 0 lets it actually shrink below its content's natural width (the row is
 * horizontal, so the inline axis is what needs to give). */
.bw-alert__body {
  flex: 1 1 auto;
  min-inline-size: 0;
}
.bw-alert__title {
  margin: 0;
  font-family: var(--bw-text-heading-sm-family);
  font-size: var(--bw-text-heading-sm-size);
  font-weight: var(--bw-text-heading-sm-weight);
  line-height: var(--bw-text-heading-sm-line-height);
}
.bw-alert__message {
  margin: 0;
  margin-block-start: var(--bw-space-1); /* air between title and message */
  max-inline-size: var(--bw-size-max-width-prose);
  font-size: var(--bw-text-body-sm-size);
  font-weight: var(--bw-text-body-sm-weight);
  line-height: var(--bw-text-body-sm-line-height);
}
/* Variant borders use the per-intent tint-border tokens (decorative tint,
 * softer than the full-strength intent colour and not relied on for
 * contrast); icon carries the intent, title accents via X-fg (DESIGN.md 4.5). */
.bw-alert--info {
  background: var(--bw-color-info-subtle);
  border-color: var(--bw-color-info-border);
}
.bw-alert--info .bw-alert__icon {
  color: var(--bw-color-info);
}
.bw-alert--info .bw-alert__title {
  color: var(--bw-color-info-fg);
}
.bw-alert--success {
  background: var(--bw-color-success-subtle);
  border-color: var(--bw-color-success-border);
}
.bw-alert--success .bw-alert__icon {
  color: var(--bw-color-success);
}
.bw-alert--success .bw-alert__title {
  color: var(--bw-color-success-fg);
}
.bw-alert--warning {
  background: var(--bw-color-warning-subtle);
  border-color: var(--bw-color-warning-border);
}
.bw-alert--warning .bw-alert__icon {
  color: var(--bw-color-warning);
}
.bw-alert--warning .bw-alert__title {
  color: var(--bw-color-warning-fg);
}
.bw-alert--danger {
  background: var(--bw-color-danger-subtle);
  border-color: var(--bw-color-danger-border);
}
.bw-alert--danger .bw-alert__icon {
  color: var(--bw-color-danger);
}
.bw-alert--danger .bw-alert__title {
  color: var(--bw-color-danger-fg);
}
/* The dismiss control (icvoss/django-brickwork#137): flex-shrink: 0 so it
 * does not compress, matching .bw-modal__close/.bw-slide-over__close/
 * .bw-toast__close. No margin-inline-start: auto needed here: .bw-alert__body
 * already carries flex: 1 1 auto, so it fills the remaining row space and
 * pushes the close button to the trailing edge on its own. */
.bw-alert__close {
  flex-shrink: 0;
}

/* --- Form fields ----------------------------------------------------------- */

/* Forms hold the designed measure: the max-width-form token caps the field
 * column so controls never stretch the full workspace (the form screens'
 * strongest unstyled-scaffold signal). */
.bw-form {
  max-inline-size: var(--bw-size-max-width-form);
}
.bw-form__actions {
  display: flex;
  align-items: center;
  gap: var(--bw-space-3);
  margin-block-start: var(--bw-space-2);
}
.bw-field {
  display: flex;
  flex-direction: column;
  gap: var(--bw-space-1);
  margin-block-end: var(--bw-density-stack-gap);
  max-inline-size: var(--bw-size-max-width-form);
}
.bw-field__label {
  font-size: var(--bw-text-label-size);
  font-weight: var(--bw-text-label-weight);
  line-height: var(--bw-text-label-line-height);
}
.bw-field__required {
  /* danger-fg, not raw danger: the text-duty tier holds AA at caption size. */
  color: var(--bw-color-danger-fg);
}
/* The widget wrapper (icvoss/django-brickwork#130): every field renderer
 * (_field.html, _combobox.html, _dropzone.html, _tag_input.html) wraps its
 * widget in this div, but nothing named it in the stylesheet. It is a
 * genuine layout hook, not dead structure: .bw-field is a column flexbox, and
 * without min-inline-size: 0 a wide unbreakable child (a long select option,
 * an unbroken token in the tag-input floor) can force the field, and the
 * form around it, wider than its container instead of shrinking to fit. */
.bw-field__control {
  display: block;
  min-inline-size: 0;
}
.bw-input {
  inline-size: 100%;
  block-size: var(--bw-density-control-height);
  padding-inline: var(--bw-density-control-padding-inline);
  border: var(--bw-size-border-hairline) solid var(--bw-color-border-control);
  border-radius: var(--bw-radius-md);
  background: var(--bw-color-surface);
  color: var(--bw-color-fg);
  font: inherit;
  /* Inner top hairline (0.7.0 craft): black at 4% alpha, the faint recessed
   * top edge that makes the field read as a well rather than a flat rectangle
   * (the button bevel's counterpart, same black-literal shadow convention as
   * the elevation ramp; low enough alpha to sit quietly on both themes). */
  box-shadow: inset 0 1px 2px 0 oklch(0 0 0 / 0.04);
  transition: var(--bw-transition-colors), var(--bw-transition-shadow);
}
/* Textareas share .bw-input chrome but must honour rows= (and grow with
 * content). Fixed block-size on .bw-input collapsed every textarea to a
 * single-line control height (icvoss/django-brickwork#521). */
textarea.bw-input {
  block-size: auto;
  min-block-size: calc(var(--bw-density-control-height) * 3);
  padding-block: var(--bw-density-control-padding-inline);
  resize: vertical;
  field-sizing: content;
}
/* Hover pre-light: one perceptible step from the control border toward ink.
 * Mixing toward fg (82% border-control, 18% fg) is theme-correct in both
 * directions automatically, because fg flips with the theme: measured 4.47:1
 * against the light surface and 4.61:1 against the dark (the resting border's
 * 3.23/3.39 stays the 1.4.11 floor). :where() keeps the guards at zero
 * specificity so the later focus-visible and invalid border rules still win. */
.bw-input:hover:where(:not(:disabled):not([readonly]):not(:focus-visible)) {
  border-color: color-mix(in oklab, var(--bw-color-border-control) 82%, var(--bw-color-fg));
}
/* The focus ring itself comes from the global :focus-visible rule; the border
 * joining it is component-local. Invalid rules are declared later so the
 * danger border persists while an invalid input is focused. The soft halo
 * (18% accent over transparent, measured 1.28:1 light / 1.32:1 dark against
 * the surface, purely decorative) hugs the input's own radius beneath the
 * offset outline, so focus reads at the control edge as well as the ring;
 * the inner hairline is restated so the well doesn't pop flat on focus. */
.bw-input:focus-visible {
  border-color: var(--bw-color-accent);
  box-shadow:
    inset 0 1px 2px 0 oklch(0 0 0 / 0.04),
    0 0 0 3px color-mix(in oklab, var(--bw-color-accent) 18%, transparent);
}
/* Select (0.7.0 craft): the UA chrome goes, the input's own drawing stays, and
 * the affordance is an embedded chevron (--bw-component-select-indicator, a component
 * token carrying a data-URI SVG; its baked mid-grey and measured ratios are
 * documented on the token). Sized to the sm icon step, seated inside the
 * control padding; the end padding clears glyph plus a breathing space.
 * background-position has no logical form, so RTL swaps the corner via
 * [dir="rtl"], the same mechanism as .bw-icon-directional. */
select.bw-input {
  appearance: none;
  background-image: var(--bw-component-select-indicator);
  background-repeat: no-repeat;
  background-position: right var(--bw-density-control-padding-inline) center;
  background-size: var(--bw-component-icon-size-sm);
  padding-inline-end: calc(
    var(--bw-density-control-padding-inline) + var(--bw-component-icon-size-sm) + var(--bw-space-1)
  );
}
[dir="rtl"] select.bw-input {
  background-position: left var(--bw-density-control-padding-inline) center;
}
.bw-input[readonly] {
  background: var(--bw-color-surface-sunken);
}
.bw-input:disabled {
  background: var(--bw-color-action-disabled-bg);
  color: var(--bw-color-action-disabled-text);
  border-color: var(--bw-color-action-disabled-border);
  cursor: not-allowed;
}
.bw-field--invalid .bw-input,
.bw-input[aria-invalid="true"] {
  border-color: var(--bw-color-danger);
}

/* --- Checkbox + radio (drawn controls, 0.7.0 craft) ------------------------- */

/* appearance:none swaps the UA widget for a control drawn on our tokens (the
 * moderncss pattern: with appearance removed, every modern engine renders the
 * input as a plain box, including its ::before). inline-grid centres the
 * glyph; vertical-align + the end margin seat the control against label
 * text. The resting border is border-control (the WCAG 1.4.11 boundary,
 * measured 3.23:1 light / 3.39:1 dark, same contract as .bw-input); the focus
 * ring comes from the global :where(input):focus-visible rule. Every selector
 * is input-qualified: RadioSelect/CheckboxSelectMultiple rendering puts the
 * class on the group wrapper div as well as on each option input (the
 * option_inherits_attrs route), and the wrapper must stay unstyled.
 *
 * icvoss/django-brickwork#208: both controls render as bare native inputs
 * with no wrapping label (the data table's select-all/select-row cells, and
 * bw_field_widget's own .bw-field__control), so there is no clickable
 * wrapper to extend the hit area through; the box itself IS the whole
 * target. The box is 1.5rem (24px), the exact WCAG 2.5.8 floor, grown from
 * the previous 1.125rem (18px). This does change the control's visual
 * size (the issue's own "changing visual weight is a regression in its own
 * right" applies), but a clip/padding trick to keep an 18px-looking box
 * inside a 24px hit area needs the border moved off `border` onto a layered
 * box-shadow to stop it tracking the padding box, which is exactly the kind
 * of fragile, non-obvious CSS this codebase avoids elsewhere; growing the
 * real box is the KISS option and is verified not to unbalance the label
 * row (row height already had headroom: the data table's select column and
 * .bw-field__control both size to the row/control height, not to the
 * checkbox). */
input.bw-checkbox,
input.bw-radio {
  appearance: none;
  display: inline-grid;
  place-content: center;
  inline-size: 1.5rem;
  block-size: 1.5rem;
  flex-shrink: 0;
  margin: 0;
  margin-inline-end: var(--bw-space-2);
  vertical-align: middle;
  border: var(--bw-size-border-hairline) solid var(--bw-color-border-control);
  background: var(--bw-color-surface);
  transition: var(--bw-transition-colors);
}
input.bw-checkbox {
  border-radius: var(--bw-radius-sm);
}
input.bw-radio {
  border-radius: var(--bw-radius-full);
}
/* Shared hover pre-light, the input's own mix (constant documented there). */
input.bw-checkbox:hover:where(:not(:disabled):not(:checked)),
input.bw-radio:hover:where(:not(:disabled):not(:checked)) {
  border-color: color-mix(in oklab, var(--bw-color-border-control) 82%, var(--bw-color-fg));
}
/* Checked: solid accent fill (measured 5.11:1 light / 7.14:1 dark against the
 * surface, the 1.4.11 state boundary). */
input.bw-checkbox:checked,
input.bw-radio:checked {
  border-color: var(--bw-color-accent);
  background-color: var(--bw-color-accent);
}
/* The tick: --bw-component-checkbox-glyph is a colour-free alpha shape consumed as a
 * mask and PAINTED with fg-on-accent, so the glyph ink flips with the theme
 * like every other ink-on-accent (white on the light accent, near-black on
 * the lighter dark accent; measured 5.11:1 light / 7.52:1 dark against the
 * fill; a baked white tick would fail dark at 2.63:1, documented on the
 * token). No entrance animation: the fill change is feedback enough and the
 * reduced-motion floor stays trivially honoured. */
input.bw-checkbox:checked::before {
  content: "";
  inline-size: 1rem; /* 16px glyph centred in the 24px box, same ~66% fill ratio as the previous 12px-in-18px */
  block-size: 1rem;
  background-color: var(--bw-color-fg-on-accent);
  mask-image: var(--bw-component-checkbox-glyph);
  mask-repeat: no-repeat;
  mask-size: 100% 100%;
}
/* The radio dot uses the border-width trick, the cleanest cross-browser
 * drawing: the accent border thickens and the background becomes the dot
 * (the same theme-correct ink pair as the tick). No pseudo-element, no
 * image; box-sizing: border-box keeps the geometry fixed so nothing shifts
 * on check. Border width scaled with the 18px->24px box (#208) to keep the
 * same ~duplicate dot-to-box proportion as before. */
input.bw-radio:checked {
  border-width: 0.4167rem; /* ~6.7px: 5px was 5/18 of the old box; keeps the same ratio at 24px */
  background-color: var(--bw-color-fg-on-accent);
}
input.bw-checkbox:disabled,
input.bw-radio:disabled {
  border-color: var(--bw-color-action-disabled-border);
  background: var(--bw-color-action-disabled-bg);
  cursor: not-allowed;
}
input.bw-checkbox:disabled:checked {
  border-color: var(--bw-color-action-disabled-border);
  background-color: var(--bw-color-action-disabled-text);
}
input.bw-checkbox:disabled:checked::before {
  background-color: var(--bw-color-action-disabled-bg);
}
input.bw-radio:disabled:checked {
  border-color: var(--bw-color-action-disabled-text);
  background-color: var(--bw-color-action-disabled-bg);
}

/* --- Toggle switch (0.13.0, BR-BW-INPUT-001, brickwork#57) ------------------ */

/* Chrome over a native <input type="checkbox" role="switch">, never a bespoke
 * widget: appearance:none draws a track, the thumb is a ::before positioned
 * absolutely inside it and slid via a custom property so the checked-state
 * transform and the reduced-motion floor share one declaration. Track/thumb
 * sizes are component tokens (no primitive scale step fits a switch track).
 *
 * icvoss/django-brickwork#316: the input's own block-size is the WCAG 2.5.8
 * 24px hit-area floor, not the visible track. When used via the standalone
 * {% bw_toggle %} tag a wrapping <label class="bw-toggle-field"> already
 * gives a bigger clickable area, but when opted into via
 * CheckboxInput(attrs={"class": "bw-toggle"}) it renders bare through
 * bw_field_widget (forms/_field.html puts the field label in a SIBLING
 * <label>, not a wrapper), so the input alone is the whole target in that
 * context and must itself clear the floor. The track stays the fixed
 * 20px-tall shape (a deliberate design proportion per the switch's
 * near-2:1 track ratio, not incidental line-height, so growing it would
 * distort a recognisable switch shape) by moving track drawing off the
 * input's own border/background onto ::after, sized and centred
 * independently of the input's now-taller box. The thumb (::before) is
 * unaffected: its position is already 50% of the input's own box, so it
 * stays centred at any input height, and its slide distance is
 * track-width-derived only. */
input.bw-toggle {
  appearance: none;
  position: relative;
  display: inline-block;
  inline-size: var(--bw-component-toggle-track-width);
  block-size: 1.5rem; /* 24px hit-area floor; see comment above */
  flex-shrink: 0;
  margin: 0;
  margin-inline-end: var(--bw-space-2);
  vertical-align: middle;
  cursor: pointer;
}
input.bw-toggle::after {
  content: "";
  position: absolute;
  inset-block-start: 50%;
  inset-inline-start: 0;
  inline-size: 100%;
  block-size: var(--bw-component-toggle-track-height);
  border: var(--bw-size-border-hairline) solid var(--bw-color-border-control);
  border-radius: var(--bw-radius-full);
  background: var(--bw-color-surface-sunken);
  transform: translateY(-50%);
  transition: var(--bw-transition-colors);
}
input.bw-toggle::before {
  content: "";
  position: absolute;
  inset-block-start: 50%;
  inset-inline-start: var(--bw-component-toggle-thumb-inset);
  inline-size: var(--bw-component-toggle-thumb-size);
  block-size: var(--bw-component-toggle-thumb-size);
  border-radius: var(--bw-radius-full);
  background: var(--bw-color-surface);
  box-shadow: var(--bw-elevation-1);
  transform: translateY(-50%);
  transition: transform var(--bw-duration-normal) var(--bw-ease-standard);
  z-index: 1;
}
input.bw-toggle:hover:where(:not(:disabled))::after {
  border-color: color-mix(in oklab, var(--bw-color-border-control) 82%, var(--bw-color-fg));
}
/* Checked: the track fills accent (measured against the same 1.4.11 boundary
 * as the checkbox fill above) and the thumb slides to the trailing edge; the
 * slide distance is track width minus thumb size minus both edge insets, so
 * one custom property expression works for any future density-scaled track. */
input.bw-toggle:checked::after {
  border-color: var(--bw-color-accent);
  background: var(--bw-color-accent);
}
input.bw-toggle:checked::before {
  transform: translateY(-50%)
    translateX(
      calc(
        var(--bw-component-toggle-track-width) - var(--bw-component-toggle-thumb-size) -
          (var(--bw-component-toggle-thumb-inset) * 2)
      )
    );
}
[dir="rtl"] input.bw-toggle:checked::before {
  transform: translateY(-50%)
    translateX(
      calc(
        (
            var(--bw-component-toggle-track-width) - var(--bw-component-toggle-thumb-size) -
              (var(--bw-component-toggle-thumb-inset) * 2)
          ) * -1
      )
    );
}
input.bw-toggle:disabled {
  cursor: not-allowed;
}
input.bw-toggle:disabled::after {
  border-color: var(--bw-color-action-disabled-border);
  background: var(--bw-color-action-disabled-bg);
}
input.bw-toggle:disabled:checked::after {
  border-color: var(--bw-color-action-disabled-border);
  background: var(--bw-color-action-disabled-text);
}
input.bw-toggle:disabled::before {
  box-shadow: none;
}
@media (prefers-reduced-motion: reduce) {
  input.bw-toggle::before {
    transition: none;
  }
}
.bw-toggle-field {
  display: inline-flex;
  align-items: center;
  cursor: pointer;
}
.bw-toggle-field__label {
  font-size: var(--bw-text-label-size);
  font-weight: var(--bw-text-label-weight);
  line-height: var(--bw-text-label-line-height);
}

/* --- Theme switch (icvoss/django-brickwork#117) ----------------------------- */

/* Deliberately unstyled beyond a native fieldset of radios laid out with the
 * existing density/space tokens: no bespoke widget. axes=/locked_axes=
 * change WHICH fieldsets render, never their appearance. layout="compact"
 * (icvoss/django-brickwork#235, below) is the one option that does vary
 * appearance: it wraps the SAME fieldsets in a details/summary disclosure,
 * on the dropdown's own established panel tokens. */
.bw-theme-switch {
  display: flex;
  flex-wrap: wrap;
  gap: var(--bw-density-stack-gap);
}
/* Reserved pre-init state (icvoss/django-brickwork#272): visibility: hidden,
 * never display: none or the [hidden] attribute, so the control stays IN
 * FLOW and its real, label-dependent box is reserved from first paint.
 * bwThemeSwitch removes this class at init, changing appearance only, never
 * geometry, so the reveal produces no layout-shift entry.
 *
 * A class, not an attribute-based override: the honest reason is that
 * `visibility: hidden` keeps the box in flow, which the `hidden` ATTRIBUTE
 * cannot do (the UA sheet's `[hidden]` is `display: none`, out of flow, by
 * definition, so there is no attribute-only way to reserve space while
 * hiding). This is NOT a cascade-priority argument: `!important` here is the
 * same scoped-floor pattern index.css:42 already establishes for
 * `[class^="bw-"][hidden]` ("hidden always means hidden without a consumer
 * preflight"), applied to this class for the same reason, since brickwork's
 * own compiled output carries no @layer at all and an ordinary consumer
 * utility rule of equal or later specificity could otherwise override a
 * bare `visibility: hidden` (a plain descendant selector on
 * .bw-theme-switch__trigger, say) and leave part of the pre-init control
 * visible and focusable. Applies in BOTH layout="inline" and
 * layout="compact": the two share the same root element, this rule targets
 * neither layout's own modifier class.
 *
 * The descendant rule below is load-bearing, not belt-and-braces (icvoss/
 * django-brickwork#272 review): `visibility` INHERITS, but an inherited
 * value always loses to a value SPECIFIED on the element itself, `!important`
 * on the ancestor notwithstanding (`!important` only breaks ties between
 * rules matching the SAME element; it has no say over inheritance from a
 * different element at all). Verified directly: a plain `.child{visibility:
 * visible}` descendant of a `.root{visibility:hidden!important}` ancestor
 * computes to visible and is genuinely focusable. A single consumer rule as
 * ordinary as `.bw-theme-switch__trigger { visibility: visible; }` would
 * therefore paint and unhide the compact trigger while the rest of the
 * control stayed reserved-but-invisible, exactly the no-JS floor this class
 * exists to prevent. Forcing every descendant's OWN visibility here closes
 * that gap: it is no longer inheritance doing the work, so a competing
 * descendant rule has nothing weaker to beat. A consumer can still defeat
 * this deliberately (their own `!important` on the same selector, or a rule
 * later in a layer that outranks this unlayered one), which is the correct
 * shape: overriding this package's own floor should take a deliberate,
 * equally forceful rule, never an ordinary unqualified declaration. */
.bw-theme-switch--pre-init,
.bw-theme-switch--pre-init * {
  visibility: hidden !important;
}
.bw-theme-switch__group {
  display: flex;
  flex-direction: column;
  gap: var(--bw-space-2);
  margin: 0;
  padding: 0;
  border: 0;
}
.bw-theme-switch__legend {
  padding: 0;
  color: var(--bw-color-fg-muted);
  font-size: var(--bw-text-label-size);
  font-weight: var(--bw-text-label-weight);
  line-height: var(--bw-text-label-line-height);
}
.bw-theme-switch__option {
  display: inline-flex;
  align-items: center;
  gap: var(--bw-space-2);
  cursor: pointer;
}
/* 24x24 tap-target floor (icvoss/django-brickwork#272 gate run): a plain,
 * unstyled native radio has no chrome of its own to grow, so browsers render
 * it at its intrinsic ~13x13 box, under the WCAG 2.5.8 AA floor every OTHER
 * interactive control in this file already meets (input.bw-checkbox/
 * input.bw-radio grow their own box the same way, just with appearance:none
 * painted chrome on top since they are NOT plain). Sizing only, no
 * appearance/paint change: this control stays deliberately unstyled beyond
 * native fieldset-of-radios chrome (this file's own theme-switch doc
 * comment), so only the hit box grows, never the visible glyph. Previously
 * masked by the root's [hidden] attribute collapsing every descendant,
 * including this input, to 0x0 until JS ran; #272 replaced that with
 * visibility: hidden (an in-flow, real-geometry pre-init state), which
 * exposed the never-fixed 13x13 floor to axe's tap-target sweep for the
 * first time, in both the pre-init and post-reveal fixtures. */
.bw-theme-switch__input {
  inline-size: 1.5rem;
  block-size: 1.5rem;
}
.bw-theme-switch__input:disabled {
  cursor: not-allowed;
}
.bw-theme-switch__input:disabled ~ .bw-theme-switch__option-label {
  color: var(--bw-color-action-disabled-text);
}
.bw-theme-switch__option-label {
  font-size: var(--bw-text-body-sm-size);
  font-weight: var(--bw-text-body-sm-weight);
  line-height: var(--bw-text-body-sm-line-height);
}
.bw-theme-switch__locked-note {
  margin: 0;
  /* fg-muted, not fg-subtle: this is load-bearing explanatory text (why the
     axis is disabled), not decoration, so it must clear WCAG 2.2 AA body
     contrast, matching .bw-field__help immediately below (icvoss/django-brickwork#117). */
  color: var(--bw-color-fg-muted);
  font-size: var(--bw-text-caption-size);
  font-weight: var(--bw-text-caption-weight);
  line-height: var(--bw-text-caption-line-height);
}

/* layout="compact" (icvoss/django-brickwork#235): the same fieldsets above,
 * wrapped in a native details/summary disclosure (APG Disclosure, no ARIA
 * menu roles, the _account_menu.html/_dropdown.html doctrine). The trigger
 * is bw_button "secondary md" chrome on a summary (_dropdown.html's own
 * composition, mirrored here rather than duplicated as a new primitive);
 * the panel takes the SAME dropdown-shaped tokens _dropdown.html/
 * _account_menu.html already established: surface-raised, elevation-3 at
 * z-dropdown, radius-lg, absolute positioning anchored under the trigger by
 * placement=, deliberately WITHOUT the menu min-width token those two
 * borrow (a bug found verifying #235: that 12rem floor is sized for a
 * vertical list of text menu items, wider than this panel's own content,
 * three flex-wrapped fieldsets of radios; forcing it wider than the
 * trigger under placement="end" pushes the panel's start edge past the
 * trigger's own start edge, and past the viewport's edge whenever the
 * trigger sits near it, exactly the documented "header actions cluster"
 * placement this feature exists for). The panel sizes to its own content
 * instead, which already matches the trigger's width in practice. No
 * width-dependent rule anywhere in this block: layout is a consumer-chosen
 * option, not a
 * breakpoint. */
.bw-theme-switch--compact {
  display: inline-flex; /* the outer root still only ever holds the one disclosure */
}
.bw-theme-switch__disclosure {
  position: relative;
  display: inline-block;
}
.bw-theme-switch__trigger {
  cursor: pointer;
  list-style: none; /* hide the default disclosure triangle; the caret glyph is the affordance */
}
.bw-theme-switch__trigger::-webkit-details-marker {
  display: none;
}
.bw-theme-switch__caret {
  color: var(--bw-color-fg-muted);
  transition: var(--bw-transition-transform);
}
.bw-theme-switch__disclosure[open] .bw-theme-switch__caret {
  transform: rotate(180deg);
}
.bw-theme-switch__panel {
  position: absolute;
  inset-block-start: calc(100% + var(--bw-space-1));
  padding: var(--bw-space-3);
  display: flex;
  flex-wrap: wrap;
  gap: var(--bw-density-stack-gap);
  /* #484: compact panels with many axes overshoot short viewports under a
   * sticky host; page scroll cannot recover absolute overflow. Cap to the
   * lesser of a content-sized rem floor and ~half the viewport so the box
   * stays on-screen even when the disclosure opens mid-page (vh, not only
   * dvh: some engines resolve dvh late or not at all in max()). Scroll
   * inside, matching combobox listbox chrome. */
  max-block-size: min(18rem, 45vh);
  overflow-y: auto;
  scrollbar-width: thin;
  scrollbar-color: var(--bw-color-border) transparent;
  background: var(--bw-color-surface-raised);
  border: var(--bw-size-border-hairline) solid var(--bw-color-border);
  border-radius: var(--bw-radius-lg);
  box-shadow: var(--bw-elevation-3), inset 0 1px 0 0 oklch(1 0 0 / 0.6);
  z-index: var(--bw-z-dropdown);
}
.bw-theme-switch__disclosure[open] .bw-theme-switch__panel {
  animation: bw-fade-in-up var(--bw-duration-normal) var(--bw-ease-out);
}
.bw-theme-switch--start .bw-theme-switch__panel {
  inset-inline-start: 0;
}
.bw-theme-switch--end .bw-theme-switch__panel {
  inset-inline-end: 0;
}
/* The 44px touch-target floor on compact options only (#212's label-extension
 * route): the clickable label grows, the visible radio and text keep their
 * ordinary size, and align-items: center (above) keeps the label vertically
 * centred inside the taller target. */
.bw-theme-switch--compact .bw-theme-switch__option {
  min-block-size: var(--bw-size-touch-target-min);
}
/* fg-muted on the compact panel's legends (a bug found verifying #235): the
 * inline layout's legends sit against surface-sunken, where fg-muted clears
 * AA body contrast (proven by the inline axe pass above), but the compact
 * panel is surface-raised, measurably LIGHTER than sunken by design (the
 * elevation effect), and fg-muted against it lands at ~4.1:1 in both
 * themes, under the 4.5:1 floor. bw-dropdown__item/bw-account-menu__item
 * never hit this because they use full-strength ink for their raised-surface
 * text; the compact legend now matches that, inline is untouched. */
.bw-theme-switch--compact .bw-theme-switch__legend {
  color: var(--bw-color-fg);
}
@media (prefers-reduced-motion: reduce) {
  .bw-theme-switch__caret {
    transition: none;
  }
}

.bw-field__help {
  margin: 0;
  color: var(--bw-color-fg-muted);
  font-size: var(--bw-text-caption-size);
  font-weight: var(--bw-text-caption-weight);
  line-height: var(--bw-text-caption-line-height);
}
.bw-field__errors:empty {
  display: none;
}
.bw-field__error {
  margin: 0;
  /* danger-fg, not raw danger: the smallest text on the form carries the most
   * important message, so it gets the text-duty danger tier (AA at 12px). */
  color: var(--bw-color-danger-fg);
  font-size: var(--bw-text-caption-size);
  font-weight: var(--bw-text-caption-weight);
  line-height: var(--bw-text-caption-line-height);
}
/* The summary border is the decorative tint tier, matching .bw-alert--danger
 * (the tinted bg and danger-fg title identify the box; the invalid input keeps
 * its full danger border for the 1.4.11 control boundary). */
.bw-form-errors {
  padding: var(--bw-space-4);
  margin-block-end: var(--bw-density-stack-gap);
  border: var(--bw-size-border-hairline) solid var(--bw-color-danger-border);
  background: var(--bw-color-danger-subtle);
  border-radius: var(--bw-radius-lg);
  max-inline-size: var(--bw-size-max-width-form);
}
.bw-form-errors__title {
  margin: 0 0 var(--bw-space-2);
  font-family: var(--bw-text-heading-sm-family);
  font-size: var(--bw-text-heading-sm-size);
  font-weight: var(--bw-text-heading-sm-weight);
  line-height: var(--bw-text-heading-sm-line-height);
  color: var(--bw-color-danger-fg);
}
.bw-form-errors__list {
  margin: 0;
  padding-inline-start: var(--bw-space-5);
  font-size: var(--bw-text-body-sm-size);
  line-height: var(--bw-text-body-sm-line-height);
}
.bw-form-errors__list li {
  margin-block-end: var(--bw-space-0-5);
}
.bw-form-errors__list li::marker {
  color: var(--bw-color-danger);
}

/* --- Whole-form renderer (brickwork#53, FRM-002/003/019) -------------------- */

/* .bw-form-fields wraps a run of .bw-field-row, each holding one or more
 * fields (a plain stacked row always holds exactly one). .bw-field already
 * owns its OWN bottom margin and a max-inline-size cap (a single hand-
 * included field outside any form-renderer stays exactly as it always has);
 * inside a row wrapper both would double up with (stacked) or actively
 * fight (grid: capping a field's width mid-column) the row's own layout, so
 * they are neutralised here and the row takes over spacing/sizing. */
.bw-form-fields--stacked .bw-field-row {
  margin-block-end: var(--bw-density-stack-gap);
}
.bw-form-fields--stacked .bw-field-row:last-child {
  margin-block-end: 0;
}
.bw-form-fields--stacked .bw-field-row .bw-field {
  margin-block-end: 0;
}
/* A grouped row (rows= put >1 field on one line): a flex row so the fields
 * sit side by side regardless of the surrounding layout mode, each still
 * capped at the form's max width divided across the group so the pair does
 * not blow past a single field's usual measure. */
.bw-field-row--grouped {
  display: flex;
  flex-wrap: wrap;
  gap: var(--bw-space-3);
}
.bw-field-row--grouped .bw-field {
  flex: 1 1 0;
  min-inline-size: 0;
  max-inline-size: none;
}

/* layout="grid": an N-column CSS grid (--bw-form-grid-columns, set inline by
 * {% bw_form %}); each row spans the grid via display:contents so a
 * single-field row's field becomes a direct grid child (one cell) and a
 * grouped row's flex wrapper becomes a direct grid child (one cell spanning
 * its own flex-laid-out fields), matching FRM-003's "fields flow into it"
 * wording without special-casing grouped rows. DOM order is UNCHANGED (only
 * the visual position moves), so tab order still follows source order. */
.bw-form-fields--grid {
  display: grid;
  grid-template-columns: repeat(var(--bw-form-grid-columns, 2), minmax(0, 1fr));
  gap: var(--bw-density-stack-gap) var(--bw-space-4);
  max-inline-size: none;
}
.bw-form-fields--grid .bw-field-row {
  display: contents;
}
.bw-form-fields--grid .bw-field-row .bw-field {
  margin-block-end: 0;
  max-inline-size: none;
}
.bw-form-fields--grid .bw-field-row--grouped {
  display: flex; /* a grouped row inside grid: one grid cell, fields flex within it */
}
/* Narrow viewports collapse the grid to one column regardless of
 * grid_columns: a form is read top-to-bottom on a phone, never squeezed. */
@media (width < 40rem) {
  .bw-form-fields--grid {
    grid-template-columns: 1fr;
  }
}

/* --- Page header ----------------------------------------------------------- */

/* Layout craft only (icvoss/django-brickwork#541): title cluster and actions
 * share one wrapping row with a clear gap; no surface tint here (the shell
 * region already seats the header on the page canvas). Breadcrumbs and tabs
 * take the full inline track when present so they stay above/below the row. */
.bw-page-header {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--bw-space-3) var(--bw-space-4);
}
/* surface="tint" (Beat Phase C): opt-in chrome band for breadcrumbs + title
 * + actions. Default stays plain on the page canvas (#541). */
.bw-page-header--surface-tint {
  padding: var(--bw-space-4) var(--bw-space-5);
  margin-block-end: var(--bw-space-4);
  background: var(--bw-color-surface-sunken);
  border: var(--bw-size-border-hairline) solid
    color-mix(in oklab, var(--bw-color-fg) 10%, var(--bw-color-surface-sunken));
  border-radius: var(--bw-radius-lg);
}
.bw-page-header__titles {
  flex: 1 1 12rem;
  min-inline-size: 0;
}
.bw-page-header > .bw-breadcrumbs,
.bw-page-header > .bw-tabs {
  flex: 1 1 100%;
}
.bw-page-header__title {
  margin: 0;
  /* heading-xl role: its family token resolves to the display font, so a
     consumer can give headings a distinct typeface (override
     --bw-font-family-display) without touching this class */
  font-family: var(--bw-text-heading-xl-family);
  font-size: var(--bw-text-heading-xl-size);
  font-weight: var(--bw-text-heading-xl-weight);
  line-height: var(--bw-text-heading-xl-line-height);
  letter-spacing: var(--bw-text-heading-xl-tracking);
}
.bw-page-header__description {
  margin: var(--bw-space-1) 0 0;
  color: var(--bw-color-fg-muted);
  font-size: var(--bw-text-body-md-size);
  font-weight: var(--bw-text-body-md-weight);
  line-height: var(--bw-text-body-md-line-height);
  max-inline-size: var(--bw-size-max-width-prose);
}

/* The band heading primitive (icvoss/django-brickwork#399, ruling on #371
 * gap 2): a standalone visible <h2> caption for one band in an app-surface
 * page, outside `bw-prose`. Measured consequence of the trap this replaces:
 * a band wrapped in `bw-prose` to get a heading rendered 655px against its
 * siblings' 976px, because `bw-prose`'s first declaration is
 * `max-inline-size: var(--bw-size-max-width-prose)`, a content-measure
 * contract, not a heading utility. Borrowing the class borrows its whole
 * contract, so this primitive carries no inline-size rule of its own.
 *
 * Sized at heading-lg: one step under the page title's heading-xl
 * (.bw-page-header__title above), matching .bw-empty-state__heading's own
 * precedent for a standalone in-page heading outside prose. Deliberately
 * NOT heading-md, which DESIGN.md's component map (7.4) reserves for the
 * card-title role (.bw-card__title): a band heading sits above a band, at
 * the page's own structural level, not inside a card region. */
.bw-band-heading {
  margin: 0;
  font-family: var(--bw-text-heading-lg-family);
  font-size: var(--bw-text-heading-lg-size);
  font-weight: var(--bw-text-heading-lg-weight);
  line-height: var(--bw-text-heading-lg-line-height);
}

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

/* Default / md: a framed surface so the empty region reads on a white page
 * (same fg-mix edge + soft ambient as zero-kwargs .bw-card). --size-sm stays
 * unframed below so a nested empty state inside a card does not double-frame. */
.bw-empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: var(--bw-space-3);
  padding-block: var(--bw-space-16);
  padding-inline: var(--bw-space-4);
  background: var(--bw-color-surface);
  border: var(--bw-size-border-hairline) solid
    color-mix(in oklab, var(--bw-color-fg) 12%, var(--bw-color-surface));
  border-radius: var(--bw-radius-lg);
  box-shadow:
    var(--bw-elevation-1),
    0 4px 14px -4px oklch(0 0 0 / 0.1);
}
.bw-empty-state__icon {
  color: var(--bw-color-icon-muted);
}
/* Hero sizing: the template emits an inline --bw-icon-size, which only feeds
 * the .bw-icon declaration; the compound selector outranks .bw-icon so the
 * 2xl size wins here. 0.7.0 craft: the icon sits on a tinted circular
 * backdrop, a 3.5rem disc (the 2.5rem glyph plus a space-2 ring of padding,
 * composed from tokens because the spacing ramp has no 3.5rem step) of
 * accent-subtle, with the glyph stepping up from icon-muted to accent. The
 * pairing is decorative (the heading and body carry the meaning, so no AA
 * duty), and still measures 4.66:1 light / 6.70:1 dark. */
.bw-icon.bw-empty-state__icon {
  inline-size: calc(var(--bw-component-icon-size-2xl) + 2 * var(--bw-space-2));
  block-size: calc(var(--bw-component-icon-size-2xl) + 2 * var(--bw-space-2));
  padding: var(--bw-space-2);
  background: var(--bw-color-accent-subtle);
  border-radius: var(--bw-radius-full);
  color: var(--bw-color-accent);
}
.bw-empty-state__heading {
  margin: 0;
  font-size: var(--bw-text-heading-lg-size);
  font-weight: var(--bw-text-heading-lg-weight);
  line-height: var(--bw-text-heading-lg-line-height);
}
.bw-empty-state__body {
  margin: 0;
  color: var(--bw-color-fg-muted);
  font-size: var(--bw-text-body-md-size);
  font-weight: var(--bw-text-body-md-weight);
  line-height: var(--bw-text-body-md-line-height);
  max-inline-size: var(--bw-size-max-width-form);
}
/* --size-sm (ADR-060, STA-019, #218): the in-panel scale, for an empty state
 * nested inside a card or other bounded container rather than filling a
 * page. Tighter block padding than the page-filling default; framing drops
 * so the nest does not paint a card inside a card. */
.bw-empty-state--size-sm {
  padding-block: var(--bw-space-6);
  background: transparent;
  border: 0;
  border-radius: 0;
  box-shadow: none;
}
/* surface="plain" (Beat Phase C): page-scale empty state without the framed
 * card chrome, for embedding beside other chrome that already frames. */
.bw-empty-state--surface-plain {
  background: transparent;
  border: 0;
  border-radius: 0;
  box-shadow: none;
}
/* The heading demotes to a <p> at this scale (optional, STA-019), so it takes
 * an emphasised-body treatment rather than the heading-lg role: same
 * body-md size/line-height as .bw-empty-state__body, semibold weight to
 * still read as the region's title. Matches the established
 * emphasised-body pattern elsewhere in this file (.bw-alert__title,
 * .bw-page-header__description). */
.bw-empty-state--size-sm .bw-empty-state__heading {
  font-size: var(--bw-text-body-md-size);
  line-height: var(--bw-text-body-md-line-height);
  font-weight: var(--bw-font-weight-semibold);
}
/* Plain-link action treatment (STA-019): no bw-btn chrome, medium weight,
 * a 44px touch target. No bespoke focus ring: the global :focus-visible
 * rule (index.css) already covers anchors. */
.bw-empty-state__action-link {
  display: inline-flex;
  align-items: center;
  min-block-size: 44px;
  color: var(--bw-color-accent);
  font-weight: var(--bw-font-weight-medium);
  text-decoration: none;
}
.bw-empty-state__action-link:hover {
  text-decoration: underline;
}

/* --- Data table ------------------------------------------------------------ */

/* The wrap is a card. overflow-x: auto both scrolls wide tables and clips the
   painted content to the radius (a scroll container clips to its padding box),
   so no extra overflow property is needed. */
.bw-data-table-wrap {
  overflow-x: auto;
  background: var(--bw-color-surface);
  /* Edge + ambient match zero-kwargs .bw-card so the table reads on white;
   * sunken header, zebra, hover and selected stay on the inner rows. */
  border: var(--bw-size-border-hairline) solid
    color-mix(in oklab, var(--bw-color-fg) 12%, var(--bw-color-surface));
  border-radius: var(--bw-radius-lg);
  box-shadow:
    var(--bw-elevation-1),
    0 4px 14px -4px oklch(0 0 0 / 0.1);
  /* Wide tables scroll inside the card: thin, border-toned scrollbar so the
   * apparatus stays quieter than the data (decorative chrome). */
  scrollbar-width: thin;
  scrollbar-color: var(--bw-color-border) transparent;
  /* Paint containment: without it, an ancestor that still blows out (a
   * consumer's own wrapper, not just the .bw-card/.bw-section-stack fixed
   * above) can let this wrap's overflow paint outside its own box instead of
   * clipping to it, which is what let the wide table drag the PAGE sideways
   * rather than scrolling within its own card (icvoss/django-brickwork#136).
   * contain: paint guarantees this element is the clipping boundary for its
   * own descendants regardless of what an ancestor does. */
  contain: paint;
}
/* density="compact" (Beat Phase C): ops/console tables with tighter rows. */
.bw-data-table-wrap--density-compact .bw-data-table__th,
.bw-data-table-wrap--density-compact .bw-data-table__td {
  padding-block: var(--bw-space-2);
  padding-inline: var(--bw-space-3);
  font-size: var(--bw-text-body-sm-size);
}
.bw-data-table {
  inline-size: 100%;
  border-collapse: collapse;
}
/* Column headers speak in the small tracked-uppercase voice so the sunken band
 * reads as column apparatus, not a first data row. */
.bw-data-table__th {
  text-align: start;
  padding: var(--bw-density-row-padding-block) var(--bw-density-row-padding-inline);
  background: var(--bw-color-surface-sunken);
  border-block-end: var(--bw-size-border-hairline) solid var(--bw-color-border-strong);
  color: var(--bw-color-fg-muted);
  font-size: var(--bw-font-size-xs);
  font-weight: var(--bw-font-weight-semibold);
  line-height: var(--bw-text-label-line-height);
  letter-spacing: var(--bw-font-tracking-wide);
  text-transform: uppercase;
}
/* icvoss/django-brickwork#208: inline-flex with no minimum sized the target
 * to whatever the label's line-height gave it (measured 74x16 at 375px),
 * under the WCAG 2.5.8 floor. min-block-size only: the label text already
 * centres via align-items, so this changes no visual weight, just the
 * clickable/tappable extent above and below the text. */
.bw-data-table__sort {
  display: inline-flex;
  align-items: center;
  gap: var(--bw-space-1);
  min-block-size: 1.5rem;
  color: inherit;
  text-decoration: none;
  transition: var(--bw-transition-colors);
}
.bw-data-table__sort:hover {
  color: var(--bw-color-accent);
}
/* Idle sort arrows are noise until pointed at: hidden at rest, revealed on
 * hover and (for keyboard users) focus-visible; the affordance also stays in
 * the accessible name. Opacity transition only, so the reduced-motion floor
 * caps it globally. */
.bw-data-table__sort-idle {
  opacity: 0;
  transition: var(--bw-transition-opacity);
}
.bw-data-table__sort:hover .bw-data-table__sort-idle,
.bw-data-table__sort:focus-visible .bw-data-table__sort-idle {
  opacity: var(--bw-opacity-sort-idle);
}
.bw-data-table__td {
  padding: var(--bw-density-row-padding-block) var(--bw-density-row-padding-inline);
  border-block-end: var(--bw-size-border-hairline) solid var(--bw-color-border);
  font-size: var(--bw-text-body-sm-size);
  line-height: var(--bw-text-body-sm-line-height);
  font-variant-numeric: tabular-nums; /* numeric columns align digit-for-digit */
}
/* The last row's bottom hairline would double against the card's own border. */
.bw-data-table__row:last-child > .bw-data-table__td,
.bw-data-table__row:last-child > .bw-data-table__th--label {
  border-block-end: 0;
}
/* Records-variant zebra + hover. Even rows stripe so row 1 stays on the plain
   surface next to the sunken header band. Hover is an overlay image so it
   composes with the stripe rather than replacing it (DESIGN.md 4.6 stacking
   order). The definition variant stays plain: key/value reads better without
   stripes. */
.bw-data-table:not(.bw-data-table--definition) .bw-data-table__row:nth-child(even) {
  background-color: var(--bw-color-surface-sunken);
}
/* Selected row (records rows carry the hook): equal specificity to the zebra
   rule, later in source order, so selection beats the stripe; hover still
   layers its overlay image on top. */
.bw-data-table:not(.bw-data-table--definition) .bw-data-table__row.bw-data-table__row--selected {
  background-color: var(--bw-state-selected-bg);
}
.bw-data-table:not(.bw-data-table--definition) .bw-data-table__row:hover {
  background-image: linear-gradient(var(--bw-state-hover-overlay), var(--bw-state-hover-overlay));
}

/* A clickable record row (row.url, #10): the row-link fills its cell and
   inherits colour, so the whole first cell is the target and the row reads as
   actionable. The hover already comes from the records-variant row hover.
   min-block-size (#208): the compact density's row padding leaves less than
   24px of line-height alone, so the row-link's own tap target fell under
   the WCAG 2.5.8 floor there; comfortable/spacious density already clear it
   without this, so the floor is a no-op visual change for them. */
.bw-data-table__row-link {
  display: block;
  min-block-size: 1.5rem;
  color: inherit;
  text-decoration: none;
}
.bw-data-table__row-link:hover {
  text-decoration: underline;
}

/* Definition-list mode (#18): a key/value fact table for one entity. The label
   is a row header; give it the muted body-sm treatment (no header background,
   the column is a key, not chrome). The table fills its card; the 1% nowrap
   label column keeps the key column tight so values line up. */
.bw-data-table--definition {
  inline-size: 100%;
}
/* A key/value fact card hugs its facts rather than stretching the workspace.
   Graceful no-op in engines without :has(). */
.bw-data-table-wrap:has(.bw-data-table--definition) {
  max-inline-size: var(--bw-size-max-width-prose);
}
/* The value is the payload: muted-regular key against medium value gives the
   quiet two-rank rhythm. */
.bw-data-table--definition .bw-data-table__td {
  font-weight: var(--bw-font-weight-medium);
}
.bw-data-table__th--label {
  inline-size: 1%;
  white-space: nowrap;
  background: transparent;
  border-block-end: var(--bw-size-border-hairline) solid var(--bw-color-border);
  padding-inline-end: var(--bw-space-6);
  vertical-align: baseline;
  color: var(--bw-color-fg-muted);
  font-size: var(--bw-text-body-sm-size);
  line-height: var(--bw-text-body-sm-line-height);
  /* Reset the column-header voice: this cell also carries __th, but it is a
     row key, not chrome, so it keeps the plain body-sm treatment. */
  font-weight: var(--bw-text-label-weight);
  letter-spacing: var(--bw-font-tracking-normal);
  text-transform: none;
}

/* --- Data table: bulk selection, sticky header, responsive (brickwork#54) -- */

/* The leading checkbox column: 1% width hugs the control (the __th--label
 * inline-size:1% trick reused), centred so it does not drag the header text
 * baseline off the other columns. */
.bw-data-table__th--select,
.bw-data-table__td--select {
  inline-size: 1%;
  white-space: nowrap;
  text-align: center;
  vertical-align: middle;
}
/* Column alignment vocabulary (icvoss/django-brickwork#522): closed set
 * start (default, no modifier) | center | end, applied to both header and
 * body cells from the column dict's align key. */
.bw-data-table__th--center,
.bw-data-table__td--center {
  text-align: center;
}
.bw-data-table__th--end,
.bw-data-table__td--end {
  text-align: end;
  font-variant-numeric: tabular-nums;
}

/* loading=True skeleton (icvoss/django-brickwork#137): without padding of its
 * own the placeholder bars sit flush to the wrap's card border, then jump
 * inward once real rows arrive with their __td/__th row-density padding. Uses
 * the same row-density tokens as the real cells so the loading and loaded
 * states share one inset, and .bw-skeleton-group's own stack-with-gap shape
 * (flex column, --bw-space-2 gap) so the bars read as the same house pattern
 * for stacked skeletons rather than a one-off. */
.bw-data-table__skeleton {
  display: flex;
  flex-direction: column;
  gap: var(--bw-space-2);
  padding-block: var(--bw-density-row-padding-block);
  padding-inline: var(--bw-density-row-padding-inline);
}

/* scroll_container / sticky_header: the wrap keeps its own scroll (unchanged
 * from the plain wrap), the <thead> pins to the top of that scroll region.
 * The header cells need an opaque background of their own (they already
 * carry --bw-color-surface-sunken) so scrolled body rows do not show through
 * underneath the pinned band; the z-index keeps it above the body but below
 * any overlay chrome (the shared --bw-z-sticky token, the same one the
 * shell topbar uses, so a table's sticky head never fights the app chrome
 * for stacking). */
.bw-data-table-wrap--sticky {
  max-block-size: 100%;
}
.bw-data-table-wrap--sticky .bw-data-table__th {
  position: sticky;
  inset-block-start: 0;
  z-index: var(--bw-z-sticky);
}

/* responsive="stack": below the breakpoint each row becomes a labelled card
 * (data-label per cell, stamped by the template from the column list) and
 * the header row is hidden (the labels replace it). Above the breakpoint
 * this is a no-op: the table renders exactly as the scroll default. The
 * breakpoint matches the shell's own mobile cut (DESIGN.md's sidebar
 * collapse breakpoint), so a table's card mode and the shell's mobile nav
 * change together. */
@media (width < 48rem) {
  .bw-data-table--stack thead {
    position: absolute;
    inline-size: 1px;
    block-size: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
  }
  .bw-data-table--stack,
  .bw-data-table--stack tbody,
  .bw-data-table--stack .bw-data-table__row {
    display: block;
    inline-size: 100%;
  }
  .bw-data-table--stack .bw-data-table__row {
    padding: var(--bw-density-row-padding-block) 0;
    border-block-end: var(--bw-size-border-hairline) solid var(--bw-color-border);
  }
  .bw-data-table--stack .bw-data-table__row:last-child {
    border-block-end: 0;
  }
  .bw-data-table--stack .bw-data-table__td {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--bw-space-3);
    padding: var(--bw-space-1) var(--bw-density-row-padding-inline);
    border-block-end: 0;
    text-align: end;
  }
  /* The stamped label reads as the row's own column key, in the same muted
   * voice _data_table.html--definition uses for its row headers, so a
   * stacked card and a definition table share one "key: value" language. */
  .bw-data-table--stack .bw-data-table__td[data-label]::before {
    content: attr(data-label);
    flex: 0 0 auto;
    color: var(--bw-color-fg-muted);
    font-size: var(--bw-text-body-sm-size);
    font-weight: var(--bw-font-weight-medium);
    text-align: start;
  }
  /* The select column carries no data-label (it is a control, not a fact);
   * it stays a plain leading row instead of a fake "key: value" pair. */
  .bw-data-table--stack .bw-data-table__td--select {
    justify-content: flex-start;
  }
}

/* --- Bulk-actions bar (brickwork#54) ---------------------------------------- */

/* Sits directly above a selectable table, inside the consumer's <form>
 * (see _bulk_actions_bar.html). Same control-deck treatment as the filter
 * bar (surface + fg-mix edge + elevation-1 ambient), so the two compose
 * visually when both are present above one table. Always rendered (the
 * no-JS floor); only the live count text changes under JS. */
.bw-bulk-actions-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--bw-space-3);
  padding: var(--bw-density-card-padding);
  background: var(--bw-color-surface);
  border: var(--bw-size-border-hairline) solid
    color-mix(in oklab, var(--bw-color-fg) 12%, var(--bw-color-surface));
  border-radius: var(--bw-radius-lg);
  box-shadow:
    var(--bw-elevation-1),
    0 4px 14px -4px oklch(0 0 0 / 0.1);
  margin-block-end: var(--bw-density-stack-gap);
}
.bw-bulk-actions-bar__count {
  color: var(--bw-color-fg-muted);
  font-size: var(--bw-text-body-sm-size);
  font-weight: var(--bw-font-weight-medium);
  /* Empty at rest (zero selected) and under the no-JS floor; only ever
   * carries text once bwTableSelection writes a live count into it, so an
   * empty region takes no visible space rather than a hollow gap. */
  min-inline-size: 0;
}
.bw-bulk-actions-bar__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--bw-space-2);
}
/* min-block-size (icvoss/django-brickwork#208, same shape swept in on the
 * fix for the three named components): an action control, not inline
 * prose, so it needs the WCAG 2.5.8 floor; it was sized to its own
 * line-height alone (21px at 375px). */
.bw-bulk-actions-bar__select-all {
  display: inline-flex;
  align-items: center;
  min-block-size: 1.5rem;
  color: var(--bw-color-accent);
  font-size: var(--bw-text-body-sm-size);
  text-decoration: none;
}
.bw-bulk-actions-bar__select-all:hover {
  text-decoration: underline;
}

/* --- Account menu (#25) ---------------------------------------------------- */

/* A native <details>/<summary> dropdown (no JS, no-JS floor). The panel is
   absolutely positioned under the trigger; open state is the browser's own. */
.bw-account-menu {
  position: relative;
  display: inline-block;
}
.bw-account-menu__trigger {
  display: inline-flex;
  align-items: center;
  gap: var(--bw-space-2);
  min-block-size: var(--bw-size-touch-target-min);
  padding: var(--bw-space-1) var(--bw-space-2);
  border-radius: var(--bw-radius-md);
  cursor: pointer;
  color: var(--bw-color-fg);
  list-style: none; /* hide the default disclosure triangle */
  transition: var(--bw-transition-colors);
}
.bw-account-menu__trigger::-webkit-details-marker {
  display: none;
}
.bw-account-menu__trigger:hover {
  background-color: var(--bw-state-hover-overlay);
}
.bw-account-menu__trigger-label {
  font-size: var(--bw-text-body-sm-size); /* chrome rhythm: matches nav + crumbs */
}
.bw-account-menu__caret {
  color: var(--bw-color-fg-muted);
  transition: var(--bw-transition-transform);
}
.bw-account-menu[open] .bw-account-menu__caret {
  transform: rotate(180deg);
}
.bw-account-menu__panel {
  position: absolute;
  inset-block-start: calc(100% + var(--bw-space-1));
  min-inline-size: var(--bw-component-menu-min-width);
  padding: var(--bw-space-1);
  background: var(--bw-color-surface-raised);
  border: var(--bw-size-border-hairline) solid var(--bw-color-border);
  border-radius: var(--bw-radius-lg); /* card radius family */
  /* Ring treatment (0.7.0 craft): the card family's light-theme top-light
   * (documented at .bw-card); dark reset at the end of this file. */
  box-shadow: var(--bw-elevation-3), inset 0 1px 0 0 oklch(1 0 0 / 0.6);
  z-index: var(--bw-z-dropdown);
}
.bw-account-menu[open] .bw-account-menu__panel {
  animation: bw-fade-in-up var(--bw-duration-normal) var(--bw-ease-out);
}
@keyframes bw-fade-in-up {
  from {
    opacity: 0;
    transform: translateY(-4px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}
.bw-account-menu--end .bw-account-menu__panel {
  inset-inline-end: 0;
}
.bw-account-menu--start .bw-account-menu__panel {
  inset-inline-start: 0;
}
.bw-account-menu__item {
  display: flex;
  align-items: center;
  gap: var(--bw-space-2);
  padding: var(--bw-space-2) var(--bw-space-3);
  border-radius: var(--bw-radius-md);
  color: var(--bw-color-fg);
  text-decoration: none;
  white-space: nowrap;
  font-size: var(--bw-text-body-sm-size);
  line-height: var(--bw-text-body-sm-line-height);
  transition: var(--bw-transition-colors);
}
/* The POST sign-out item (brickwork#73): method="post" renders a
 * <form class="bw-account-menu__item-form"> wrapping a <button>, so a
 * CSRF-safe POST reaches Django's POST-only LogoutView. The form is
 * display: contents so it introduces no box of its own and the button
 * sits in the panel's flow exactly where an <a> item would; the button
 * then needs the native-control chrome (background, border, native font,
 * centre-alignment, min-width) reset off so it renders IDENTICALLY to the
 * <a> items, including the --danger treatment which needs no extra rule
 * (the ::before divider and colour ride the shared modifier class). */
.bw-account-menu__item-form {
  display: contents;
}
button.bw-account-menu__item {
  appearance: none;
  inline-size: 100%;
  border: 0;
  background: none;
  font-family: inherit;
  text-align: start;
  cursor: pointer;
}
.bw-account-menu__item:hover {
  background-color: var(--bw-state-hover-overlay);
}
.bw-account-menu__item:active {
  background-color: var(--bw-state-active-overlay);
  transition-duration: var(--bw-duration-instant);
}
.bw-account-menu__item-icon {
  color: var(--bw-color-icon-muted);
}
.bw-account-menu__item:hover .bw-account-menu__item-icon {
  color: inherit;
}
/* The destructive action is grouped apart: an 8px gap with a full-width
 * hairline centred in it. The divider is a ::before positioned in the margin
 * gap above the item (extended to the panel's inner edges via the negative
 * inline inset), so the item's own hover pill keeps its shape. */
.bw-account-menu__item--danger {
  color: var(--bw-color-danger-fg);
  position: relative;
  margin-block-start: var(--bw-space-2);
}
.bw-account-menu__item--danger::before {
  content: "";
  position: absolute;
  inset-inline: calc(-1 * var(--bw-space-1));
  inset-block-start: calc(-1 * var(--bw-space-1));
  block-size: var(--bw-size-border-hairline);
  background: var(--bw-color-border);
}
/* reduced-motion: an explicit caret override on top of the global floor; a
 * half-rotated caret reads as broken (DESIGN.md 8.4). */
@media (prefers-reduced-motion: reduce) {
  .bw-account-menu__caret {
    transition: none;
  }
}

/* --- Filter bar (#15) ------------------------------------------------------ */

/* An inline bar of filter fields above a list/table. Fields wrap on a narrow
   viewport; the actions stay together at the end. Token-only, RTL via logical
   properties and flex. Styled as the table card's control deck (surface +
   fg-mix edge + elevation-1 ambient): sunken-on-sunken would vanish against
   the page background. */
.bw-filter-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--bw-space-3);
  padding: var(--bw-density-card-padding);
  background: var(--bw-color-surface);
  border: var(--bw-size-border-hairline) solid
    color-mix(in oklab, var(--bw-color-fg) 12%, var(--bw-color-surface));
  border-radius: var(--bw-radius-lg);
  box-shadow:
    var(--bw-elevation-1),
    0 4px 14px -4px oklch(0 0 0 / 0.1);
  margin-block-end: var(--bw-density-stack-gap);
}
.bw-filter-bar__fields {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--bw-space-3);
  flex: 1 1 auto;
}
.bw-filter-bar__actions {
  display: flex;
  align-items: center;
  gap: var(--bw-space-2);
}

/* A field keeps a trailing stack margin for an ordinary vertical form. The
 * filter bar already supplies its own flex gap between wrapped items, so that
 * margin makes a plain action align to the field's empty-error tail rather
 * than its visible control. Remove only the contextual trailing margin; the
 * action's one field-gap end margin preserves its control-edge alignment with
 * the zero-height error container that stays in the DOM for live-region and
 * aria-describedby wiring. Help text and non-empty errors retain their own
 * field flow and can extend the row as needed. */
.bw-filter-bar__fields .bw-field {
  margin-block-end: 0;
}

.bw-filter-bar__actions {
  margin-block-end: var(--bw-space-1);
}

/* --- Search (#155) --------------------------------------------------------- */

/* The form itself is the no-JS floor: its submit button and scope clear link
 * stay native controls. `min-inline-size: 0` lets the input contract inside a
 * narrow topbar instead of causing the surrounding shell to overflow. */
.bw-search {
  display: flex;
  align-items: center;
  gap: var(--bw-space-2);
  min-inline-size: 0;
}

/* Documentation version switcher (#414): disclosure of version links with
 * current-version state. Shares dropdown tokens (surface-raised, elevation-3,
 * menu min-width) but is navigation, not a menu button: no Alpine upgrade,
 * aria-current on the viewed version, status text for latest/deprecated. */
.bw-version-switch {
  position: relative;
  display: inline-block;
}
.bw-version-switch__trigger {
  cursor: pointer;
  list-style: none;
  gap: var(--bw-space-2);
}
.bw-version-switch__trigger::-webkit-details-marker {
  display: none;
}
.bw-version-switch__label {
  font-variant-numeric: tabular-nums;
}
.bw-version-switch__caret {
  color: var(--bw-color-fg-muted);
  transition: var(--bw-transition-transform);
}
.bw-version-switch[open] .bw-version-switch__caret {
  transform: rotate(180deg);
}
.bw-version-switch__panel {
  position: absolute;
  inset-block-start: calc(100% + var(--bw-space-1));
  inset-inline-start: 0;
  min-inline-size: var(--bw-component-menu-min-width);
  padding: var(--bw-space-1);
  background: var(--bw-color-surface-raised);
  border: var(--bw-size-border-hairline) solid
    color-mix(in oklab, var(--bw-color-fg) 12%, var(--bw-color-surface-raised));
  border-radius: var(--bw-radius-lg);
  box-shadow:
    var(--bw-elevation-3),
    0 12px 28px -8px oklch(0 0 0 / 0.14);
  z-index: var(--bw-z-dropdown);
}
.bw-version-switch[open] .bw-version-switch__panel {
  animation: bw-fade-in-up var(--bw-duration-normal) var(--bw-ease-out);
}
.bw-version-switch--end .bw-version-switch__panel {
  inset-inline-start: auto;
  inset-inline-end: 0;
}
.bw-version-switch__item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--bw-space-3);
  padding: var(--bw-space-2) var(--bw-space-3);
  border-radius: var(--bw-radius-md);
  color: var(--bw-color-fg);
  text-decoration: none;
  white-space: nowrap;
  font-size: var(--bw-text-body-sm-size);
  line-height: var(--bw-text-body-sm-line-height);
  font-variant-numeric: tabular-nums;
  transition: var(--bw-transition-colors);
}
.bw-version-switch__item:hover {
  background: color-mix(in oklab, var(--bw-color-fg) 6%, transparent);
}
.bw-version-switch__item--current {
  background: color-mix(in oklab, var(--bw-color-accent) 12%, transparent);
  font-weight: var(--bw-font-weight-semibold);
}
.bw-version-switch__item-label {
  min-inline-size: 0;
}
.bw-version-switch__status {
  color: var(--bw-color-fg-muted);
  font-size: var(--bw-text-caption-size);
  line-height: var(--bw-text-caption-line-height);
  font-weight: var(--bw-font-weight-normal);
}

.bw-search__label {
  display: block;
  flex: 1 1 12rem;
  min-inline-size: 0;
}

.bw-search__input {
  inline-size: 100%;
}

.bw-search__scope {
  display: inline-flex;
  flex: 0 1 auto;
  align-items: center;
  gap: var(--bw-space-1);
  min-inline-size: 0;
  padding-inline: var(--bw-space-2);
  border-radius: var(--bw-radius-md);
  background: var(--bw-color-accent-subtle);
  color: var(--bw-color-fg);
  font-size: var(--bw-text-caption-size);
  line-height: var(--bw-text-caption-line-height);
}

.bw-search__scope-label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.bw-search__scope-clear {
  display: inline-flex;
  flex: 0 0 auto;
  align-items: center;
  justify-content: center;
  min-block-size: var(--bw-size-touch-target-min);
  min-inline-size: var(--bw-size-touch-target-min);
  margin-block: calc(var(--bw-space-2) * -1);
  margin-inline-end: calc(var(--bw-space-2) * -1);
  border-radius: var(--bw-radius-md);
  color: inherit;
  font-size: var(--bw-text-body-lg-size);
  line-height: 1;
  text-decoration: none;
}

.bw-search__scope-clear:hover {
  background: var(--bw-state-hover-overlay);
}

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

.bw-pagination {
  display: flex;
  align-items: center;
  gap: var(--bw-space-2);
  margin-block-start: var(--bw-density-stack-gap);
}
.bw-pagination__link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-block-size: var(--bw-size-touch-target-min);
  min-inline-size: var(--bw-size-touch-target-min);
  padding: var(--bw-space-1);
  border: var(--bw-size-border-hairline) solid var(--bw-color-border);
  border-radius: var(--bw-radius-md);
  color: var(--bw-color-fg);
  transition: var(--bw-transition-colors);
}
.bw-pagination__link:not(.bw-pagination__link--disabled):hover {
  background-color: var(--bw-state-hover-overlay);
}
.bw-pagination__link:not(.bw-pagination__link--disabled):active {
  background-color: var(--bw-state-active-overlay);
  transition-duration: var(--bw-duration-instant);
}
.bw-pagination__link--disabled {
  background: var(--bw-color-action-disabled-bg);
  color: var(--bw-color-action-disabled-text);
  border-color: var(--bw-color-action-disabled-border);
  cursor: not-allowed;
}
.bw-pagination__status {
  color: var(--bw-color-fg-muted);
  font-size: var(--bw-text-caption-size);
  font-weight: var(--bw-text-caption-weight);
  line-height: var(--bw-text-caption-line-height);
}

/* --- Skeleton (loading, STA-004) ------------------------------------------- */

.bw-skeleton {
  background: var(--bw-component-skeleton-bg);
  border-radius: var(--bw-radius-sm);
  position: relative;
  overflow: hidden;
}
/* Sizing (0.12.0, #56): each variant rule sets its own natural box, wrapped
 * in var(--bw-skeleton-w/-h, <variant literal>) so {% bw_skeleton width=
 * height= %} (which sets the plain, un-suffixed custom property inline)
 * overrides just the caller-supplied axis and leaves the other at its
 * variant default. The fallback is the variant's own literal/token
 * reference directly (not a second intermediate custom property): unlike
 * --bw-icon-size, whose fallback points at a real emitted design token
 * (--bw-component-icon-size-md), a per-variant box has no such shared token
 * to point at, so the literal lives inline in the fallback slot. */
.bw-skeleton--title {
  inline-size: var(--bw-skeleton-w, 40%);
  block-size: var(--bw-skeleton-h, var(--bw-font-size-2xl));
  margin-block-end: var(--bw-space-2);
}
.bw-skeleton--text {
  inline-size: var(--bw-skeleton-w, 60%);
  block-size: var(--bw-skeleton-h, var(--bw-font-size-md));
}
.bw-skeleton--row {
  inline-size: var(--bw-skeleton-w, 100%);
  block-size: var(--bw-skeleton-h, var(--bw-density-control-height));
  margin-block-end: var(--bw-space-2);
}
/* Filled rectangle (0.12.0, #56): a chart/image placeholder with no natural
 * aspect ratio, so its default box is a plain full-width block; the caller
 * typically supplies width/height to size it precisely. */
.bw-skeleton--block {
  inline-size: var(--bw-skeleton-w, 100%);
  block-size: var(--bw-skeleton-h, var(--bw-space-24));
}
.bw-skeleton::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg, transparent, var(--bw-component-skeleton-shimmer), transparent);
  transform: translateX(-100%);
  animation: bw-shimmer var(--bw-duration-shimmer) var(--bw-ease-linear) infinite;
}
@keyframes bw-shimmer {
  100% {
    transform: translateX(100%);
  }
}
/* reduced-motion: static pulse, no shimmer sweep (STA-005) */
@media (prefers-reduced-motion: reduce) {
  .bw-skeleton::after {
    animation: none;
    background: none;
  }
}
/* Consumer-facing group wrapper (0.12.0, #56, {% bw_skeleton %}): stacks
 * repeated shapes with a consistent gap, so a multi-line placeholder needs
 * no per-consumer layout CSS. */
.bw-skeleton-group {
  display: flex;
  flex-direction: column;
  gap: var(--bw-space-2);
}

/* --- Progress (STA-007, 0.12.0) --------------------------------------------- */

.bw-progress {
  display: flex;
  align-items: center;
  gap: var(--bw-space-2);
}
.bw-progress__track {
  flex: 1 1 auto;
  block-size: var(--bw-component-progress-track-height);
  background: var(--bw-color-surface-sunken);
  border-radius: var(--bw-radius-full);
  overflow: hidden;
  /* The track's own visible contrast against the fill is the non-colour
   * signal STA-007 requires: a sunken trough is legible even if the fill
   * hue is indistinguishable to the viewer. */
}
.bw-progress__fill {
  block-size: 100%;
  background: var(--bw-color-accent);
  border-radius: var(--bw-radius-full);
  /* Determinate width: --bw-progress-value is an unqualified 0-100 number
   * (not a percentage string), turned into a length here, never a
   * hardcoded "N%" string built at the template layer. The 0 fallback
   * (mirrors the --bw-icon-size per-instance-hook pattern) means a fill
   * rendered without the inline property collapses to an empty bar rather
   * than an invalid calc(). */
  width: calc(var(--bw-progress-value, 0) * 1%);
  transition: width var(--bw-duration-normal) var(--bw-ease-standard);
}
.bw-progress__value {
  color: var(--bw-color-fg-muted);
  font-size: var(--bw-text-caption-size);
  font-weight: var(--bw-text-caption-weight);
  line-height: var(--bw-text-caption-line-height);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
/* Indeterminate: a fixed-width segment sweeps the track. Gated behind
 * no-preference (STA-005); reduced motion falls back to a static partial
 * fill (below) rather than a frozen mid-sweep segment, so "work is in
 * progress" still reads without relying on the animation. */
.bw-progress__fill--indeterminate {
  width: 40%;
}
@media (prefers-reduced-motion: no-preference) {
  .bw-progress__fill--indeterminate {
    animation: bw-progress-sweep var(--bw-duration-shimmer) var(--bw-ease-standard) infinite;
  }
}
@keyframes bw-progress-sweep {
  0% {
    margin-inline-start: -40%;
  }
  100% {
    margin-inline-start: 100%;
  }
}

/* --- Card (CMP-014 to CMP-017, 0.5.0) --------------------------------------- */

/* The composition surface: the same chrome family as the data-table wrap and
   filter bar (surface + hairline + radius-lg + resting elevation-1), with
   density-aware padding so the card composes with the density axis
   (AC-BW-072). An unfilled region emits no markup at the template, so a bare
   card is just this elevated surface (AC-BW-070). */
.bw-card {
  background: var(--bw-color-surface);
  /* Edge on white canvas: mix a little ink into the border so the card does
   * not dissolve into a package-default white page. Elevation modifiers and
   * recipes keep using --bw-color-border where they draw their own rules. */
  border: var(--bw-size-border-hairline) solid
    color-mix(in oklab, var(--bw-color-fg) 12%, var(--bw-color-surface));
  border-radius: var(--bw-radius-lg);
  /* A grid item's default min-inline-size is auto (its content's intrinsic
   * width), not 0: without this, a card holding a wide child (a data table
   * scroll wrap) refuses to shrink below that content width and blows out
   * its .bw-section-stack track, so the overflow-x: auto container inside
   * never gets a constrained width to scroll within and the whole page
   * drags sideways instead (icvoss/django-brickwork#136). */
  min-inline-size: 0;
  /* Beautiful-defaults craft: keep elevation-1 as the named resting step,
   * then add a soft ambient so the card reads on white without a paper
   * sheen (white-on-white inset catch-light was invisible). Dark theme
   * resets at the end of this file return modifiers to plain elevation. */
  box-shadow:
    var(--bw-elevation-1),
    0 4px 14px -4px oklch(0 0 0 / 0.1);
  padding: var(--bw-density-card-padding);
}
/* Flat-design variant (CMP-015): the hairline carries the boundary alone. */
.bw-card--bordered {
  box-shadow: var(--bw-elevation-0);
}
/* Appearance suite (docs/APPEARANCE.md, #535): CSS-only surface / elevation /
   radius. "default" / elevation "1" / radius "lg"|"default" emit no modifier.
   Bleed header/media/footer corners use border-*-radius: inherit so they
   follow these modifiers without a local --bw-* custom property. */
.bw-card--radius-sm {
  border-radius: var(--bw-radius-sm);
}
.bw-card--radius-xl {
  border-radius: var(--bw-radius-xl);
}
.bw-card--radius-none {
  border-radius: var(--bw-radius-none);
}
.bw-card--surface-raised {
  background: var(--bw-color-surface-raised);
}
.bw-card--surface-tint {
  background: var(--bw-color-surface-marketing-tint);
}
.bw-card--surface-inverse {
  background: var(--bw-color-surface-inverse);
  color: var(--bw-color-fg-on-inverse);
  border-color: transparent;
  /* Inverse fill sits under the top edge: plain elevation (no light ambient). */
  box-shadow: var(--bw-elevation-1);
}
.bw-card--surface-muted {
  background: var(--bw-color-surface-sunken);
}
.bw-card--elevation-0 {
  box-shadow: var(--bw-elevation-0);
}
.bw-card--elevation-2 {
  box-shadow:
    var(--bw-elevation-2),
    0 8px 20px -6px oklch(0 0 0 / 0.12);
}
.bw-card--elevation-3 {
  box-shadow:
    var(--bw-elevation-3),
    0 12px 28px -8px oklch(0 0 0 / 0.14);
}
/* Bleed header / media keep the ambient stack; surface-inverse alone drops it
   so a dark fill does not carry a light-theme ambient halo. */
.bw-card--header-inverse,
.bw-card--header-muted,
.bw-card--header-accent,
.bw-card--media-bleed {
  box-shadow:
    var(--bw-elevation-1),
    0 4px 14px -4px oklch(0 0 0 / 0.1);
}
.bw-card--surface-inverse.bw-card--elevation-0,
.bw-card--header-inverse.bw-card--elevation-0,
.bw-card--header-muted.bw-card--elevation-0,
.bw-card--header-accent.bw-card--elevation-0,
.bw-card--media-bleed.bw-card--elevation-0 {
  box-shadow: var(--bw-elevation-0);
}
.bw-card--surface-inverse.bw-card--elevation-2,
.bw-card--header-inverse.bw-card--elevation-2,
.bw-card--header-muted.bw-card--elevation-2,
.bw-card--header-accent.bw-card--elevation-2,
.bw-card--media-bleed.bw-card--elevation-2 {
  box-shadow: var(--bw-elevation-2);
}
.bw-card--surface-inverse.bw-card--elevation-3,
.bw-card--header-inverse.bw-card--elevation-3,
.bw-card--header-muted.bw-card--elevation-3,
.bw-card--header-accent.bw-card--elevation-3,
.bw-card--media-bleed.bw-card--elevation-3 {
  box-shadow: var(--bw-elevation-3);
}
/* padding arg (04-interfaces): md restates the density default so an explicit
   padding="md" and the bare card always agree. */
.bw-card--size-sm {
  padding: var(--bw-space-3);
}
.bw-card--size-md {
  padding: var(--bw-density-card-padding);
}
.bw-card--size-lg {
  padding: var(--bw-space-6);
}
/* Card-as-link (CMP-017): the whole card is an anchor; content keeps its own
   ink and no underline smears across the card. The focus ring comes from the
   global :focus-visible rule. */
a.bw-card {
  display: block;
  color: inherit;
  text-decoration: none;
}
/* Interactive variant (MOT-010, AC-BW-071): the shadow raises 1 to 2 on
   hover/focus-within, never a transform; elevation 2+ pairs with the raised
   surface in both themes (DESIGN.md 4.1: in light the raised surface equals
   surface, so only the shadow moves; in dark the surface lightens too). */
.bw-card--interactive {
  transition: var(--bw-transition-shadow), var(--bw-transition-colors);
}
.bw-card--interactive:hover,
.bw-card--interactive:focus-within {
  box-shadow:
    var(--bw-elevation-2),
    0 8px 20px -6px oklch(0 0 0 / 0.12);
  background: var(--bw-color-surface-raised);
}
.bw-card__header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--bw-space-3);
  margin-block-end: var(--bw-space-3);
}
/* Region recipes (#535): package-emitted and consumer-owned headers share
   these treatments via .bw-card__header--* and .bw-card--header-* . */
.bw-card__header--bordered,
.bw-card--header-bordered .bw-card__header {
  padding-block-end: var(--bw-space-3);
  border-block-end: var(--bw-size-border-hairline) solid var(--bw-color-border);
}
.bw-card__header--muted,
.bw-card--header-muted .bw-card__header {
  margin: calc(-1 * var(--bw-density-card-padding));
  margin-block-end: var(--bw-space-3);
  padding: var(--bw-density-card-padding);
  background: var(--bw-color-surface-sunken);
  border-start-start-radius: inherit;
  border-start-end-radius: inherit;
}
.bw-card__header--inverse,
.bw-card--header-inverse .bw-card__header {
  margin: calc(-1 * var(--bw-density-card-padding));
  margin-block-end: var(--bw-space-3);
  padding: var(--bw-density-card-padding);
  background: var(--bw-color-surface-inverse);
  color: var(--bw-color-fg-on-inverse);
  border-start-start-radius: inherit;
  border-start-end-radius: inherit;
}
.bw-card__header--accent,
.bw-card--header-accent .bw-card__header {
  margin: calc(-1 * var(--bw-density-card-padding));
  margin-block-end: var(--bw-space-3);
  padding: var(--bw-density-card-padding);
  background: var(--bw-color-accent-subtle);
  border-block-end: var(--bw-size-border-hairline) solid var(--bw-color-accent);
  border-start-start-radius: inherit;
  border-start-end-radius: inherit;
}
/* Card titles speak in the heading-md role (DESIGN.md 7.4 component map). */
.bw-card__title {
  margin: 0;
  color: var(--bw-color-fg);
  font-family: var(--bw-text-heading-md-family);
  font-size: var(--bw-text-heading-md-size);
  font-weight: var(--bw-text-heading-md-weight);
  line-height: var(--bw-text-heading-md-line-height);
  letter-spacing: var(--bw-text-heading-md-tracking, normal);
  text-wrap: balance;
}
.bw-card__header--inverse .bw-card__title,
.bw-card--header-inverse .bw-card__title {
  color: var(--bw-color-fg-on-inverse);
}
/* Include-path header actions are ghost sm buttons. Ghost defaults to
   fg-muted, which fails WCAG AA on inverse (and whole-card inverse) fills.
   Restyle to the checked on-inverse pair; hover keeps the overlay. */
.bw-card__header--inverse .bw-btn--ghost,
.bw-card--header-inverse .bw-card__header .bw-btn--ghost,
.bw-card--surface-inverse .bw-btn--ghost {
  color: var(--bw-color-fg-on-inverse);
}
.bw-card__header--inverse .bw-btn--ghost:not(.bw-btn--disabled):hover,
.bw-card--header-inverse .bw-card__header .bw-btn--ghost:not(.bw-btn--disabled):hover,
.bw-card--surface-inverse .bw-btn--ghost:not(.bw-btn--disabled):hover {
  color: var(--bw-color-fg-on-inverse);
  background-color: var(--bw-state-hover-overlay);
}
/* Plain include-path body string (APPEARANCE.md): escaped text, not rich HTML. */
.bw-card__body {
  margin: 0;
  color: var(--bw-color-fg);
  font-size: var(--bw-text-body-md-size);
  font-weight: var(--bw-text-body-md-weight);
  line-height: var(--bw-text-body-md-line-height);
  text-wrap: pretty;
}
.bw-card__title + .bw-card__body,
.bw-card__header + .bw-card__body,
.bw-card__media + .bw-card__body,
.bw-card__media + .bw-card__header,
.bw-card__media + .bw-card__title {
  margin-block-start: var(--bw-space-3);
}
.bw-card__actions {
  display: flex;
  align-items: center;
  gap: var(--bw-space-2);
  flex-shrink: 0;
}
/* Media recipes (#535): bleed covers the top edge; inset sits inside padding;
   icon is a quiet emblem band. */
.bw-card__media {
  overflow: hidden;
}
.bw-card__media img {
  display: block;
  inline-size: 100%;
  block-size: auto;
  object-fit: cover;
  aspect-ratio: 16 / 9;
}
.bw-card__media--bleed,
.bw-card--media-bleed .bw-card__media {
  margin: calc(-1 * var(--bw-density-card-padding));
  margin-block-end: 0;
  border-start-start-radius: inherit;
  border-start-end-radius: inherit;
}
.bw-card__media--inset,
.bw-card--media-inset .bw-card__media {
  border-radius: var(--bw-radius-md);
}
.bw-card__media--inset img,
.bw-card--media-inset .bw-card__media img {
  border-radius: var(--bw-radius-md);
}
.bw-card__media--icon,
.bw-card--media-icon .bw-card__media {
  display: flex;
  align-items: center;
  justify-content: center;
  inline-size: 3rem;
  block-size: 3rem;
  border-radius: var(--bw-radius-md);
  background: var(--bw-color-accent-subtle);
  color: var(--bw-color-accent);
}
/* The footer is a quiet caption band under a hairline, matching the sidebar
   footer's divider treatment. */
.bw-card__footer {
  margin-block-start: var(--bw-space-3);
  padding-block-start: var(--bw-space-3);
  border-block-start: var(--bw-size-border-hairline) solid var(--bw-color-border);
  color: var(--bw-color-fg-muted);
  font-size: var(--bw-text-caption-size);
  font-weight: var(--bw-text-caption-weight);
  line-height: var(--bw-text-caption-line-height);
}
.bw-card__footer--muted,
.bw-card--footer-muted .bw-card__footer {
  margin-inline: calc(-1 * var(--bw-density-card-padding));
  margin-block-end: calc(-1 * var(--bw-density-card-padding));
  padding: var(--bw-space-3) var(--bw-density-card-padding);
  background: var(--bw-color-surface-sunken);
  border-block-start: var(--bw-size-border-hairline) solid var(--bw-color-border);
  border-end-start-radius: inherit;
  border-end-end-radius: inherit;
}
.bw-card__footer--actions,
.bw-card--footer-actions .bw-card__footer {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: var(--bw-space-2);
  color: var(--bw-color-fg);
  font-size: var(--bw-text-body-md-size);
  font-weight: var(--bw-text-body-md-weight);
  line-height: var(--bw-text-body-md-line-height);
}

/* --- Stat tile (VIZ-001/002, 0.5.0) ----------------------------------------- */

/* KPI tile: the card chrome whether standalone or in a stat grid, stacking an
   overline label, a tabular-numeral value, and an optional trend caption.
   Edge + ambient match zero-kwargs .bw-card so tiles read on white. */
.bw-stat {
  display: flex;
  flex-direction: column;
  gap: var(--bw-space-1);
  background: var(--bw-color-surface);
  border: var(--bw-size-border-hairline) solid
    color-mix(in oklab, var(--bw-color-fg) 12%, var(--bw-color-surface));
  border-radius: var(--bw-radius-lg);
  box-shadow:
    var(--bw-elevation-1),
    0 4px 14px -4px oklch(0 0 0 / 0.1);
  padding: var(--bw-density-card-padding);
}
/* Clickable tile (VIZ-024): an anchor stat is definitionally hoverable, so it
   takes the interactive card's shadow raise; same no-transform rule. */
a.bw-stat {
  color: inherit;
  text-decoration: none;
  transition: var(--bw-transition-shadow), var(--bw-transition-colors);
}
a.bw-stat:hover {
  box-shadow:
    var(--bw-elevation-2),
    0 8px 20px -6px oklch(0 0 0 / 0.12);
  background: var(--bw-color-surface-raised);
}
/* Overline voice for the label (uppercase applied here at the component, per
   the role contract), on muted ink. */
.bw-stat__label {
  color: var(--bw-color-fg-muted);
  font-family: var(--bw-text-overline-family);
  font-size: var(--bw-text-overline-size);
  font-weight: var(--bw-text-overline-weight);
  line-height: var(--bw-text-overline-line-height);
  letter-spacing: var(--bw-text-overline-tracking);
  text-transform: uppercase;
}
.bw-stat__value {
  margin: 0;
  /* The component token scales the numeral (VIZ-001, AC-BW-075); until it is
     emitted by the token build it falls back to the 3xl step, the same
     inline-token-with-fallback mechanism as .bw-spinner's size. */
  font-size: var(--bw-component-stat-tile-value-size, var(--bw-font-size-3xl));
  font-weight: var(--bw-font-weight-semibold);
  line-height: var(--bw-font-line-height-tight);
  font-variant-numeric: tabular-nums; /* a column of tiles never jitters as digits change */
}
/* The trend row itself (icvoss/django-brickwork#334): _stat.html now
   {% include %}s _trend_indicator.html rather than rendering its own trend
   markup, so the rule set lives once, under "--- Trend indicator ---"
   below, targeted at both .bw-trend (the partial's own root class) and
   .bw-stat__trend (retained alongside it on the SAME element for a
   consumer stylesheet that selects .bw-stat__trend directly; #334). */
/* size arg (VIZ-027): modifiers re-point the value-size component token, so
   the numeral rule itself never forks. */
.bw-stat--sm {
  --bw-component-stat-tile-value-size: var(--bw-font-size-2xl);
  padding: var(--bw-space-3);
}
.bw-stat--lg {
  --bw-component-stat-tile-value-size: var(--bw-font-size-4xl);
  padding: var(--bw-space-6);
}
/* chrome="plain" (#694): proof figures without card chrome. Default remains
   the KPI card (border, elevation, card padding). Size modifiers' padding is
   zeroed so sm/lg do not reintroduce card padding on a plain tile. */
.bw-stat--chrome-plain {
  background: transparent;
  border: none;
  border-radius: 0;
  box-shadow: none;
  padding: 0;
}
.bw-stat--chrome-plain.bw-stat--sm,
.bw-stat--chrome-plain.bw-stat--lg,
.bw-stat--chrome-plain.bw-stat--md {
  padding: 0;
}
a.bw-stat--chrome-plain:hover {
  box-shadow: none;
  background: transparent;
}
/* Consumer sparkline slot (0.12.0, #60): a constrained-height row below the
 * value/trend, so a caller's own SVG/canvas sparkline never overruns the
 * tile. The caller's markup fills this box; brickwork ships no chart
 * renderer (structural-only, matching bw_data_table's no-virtualisation
 * doctrine). */
.bw-stat__sparkline {
  margin-block-start: var(--bw-space-2);
  block-size: var(--bw-component-stat-tile-sparkline-height);
  inline-size: 100%;
}
.bw-stat__sparkline > svg,
.bw-stat__sparkline > canvas {
  display: block;
  inline-size: 100%;
  block-size: 100%;
}

/* --- Trend indicator (VIZ-017/#334) ------------------------------------------ */

/* The trend caption's only rule set: extracted from the stat tile's former
   inline block (VIZ-017) and, since #334, the one _stat.html itself renders
   via {% include %}, so a table cell, a scorecard, or a stat tile all get
   this same rule set with no .bw-stat ancestor required. .bw-stat__trend is
   the pre-#334 name for the exact same element inside a stat tile
   (_stat.html passes it through _trend_indicator.html's css_class seam
   alongside .bw-trend), retained as a selector here rather than aliased in
   the template, so a consumer stylesheet selecting .bw-stat__trend directly
   still matches: one implementation, one rule set, two selectors. The
   direction modifier is only ever emitted as bw-trend--<state> (css_class
   carries the bare bw-stat__trend token, not a second modifier), so the
   colour rules below key off bw-trend--<state> alone; .bw-stat__trend
   inherits the colour because it shares the element with bw-trend--<state>,
   not because it has its own modifier class. */
.bw-trend,
.bw-stat__trend {
  display: inline-flex;
  align-items: center;
  gap: var(--bw-space-1);
  color: var(--bw-color-fg-muted);
  font-size: var(--bw-text-caption-size);
  font-weight: var(--bw-text-caption-weight);
  line-height: var(--bw-text-caption-line-height);
}
/* Direction is neutral (icvoss/django-brickwork#388): trend names the way a
   NUMBER moved, never whether that is good news (_trend_indicator.html's own
   contract; "judgement belongs in trend_label"). Colouring --up green and
   --down red hard-codes a judgement the component never establishes, so a
   falling cost (trend="down", "which is better") rendered danger-red while
   correctly announcing "decreased" to assistive tech: the two audiences got
   opposite meanings from the same element. All three direction states
   therefore share one neutral ink, --bw-color-fg-muted (the same token the
   unmodified .bw-trend/.bw-stat__trend/.bw-stat-comparison__trend rules
   above already use before any modifier applies, and the token this file's
   other non-judgemental captions use: .bw-stat-comparison__previous/__period
   above, .bw-badge's neutral default). It holds AA at caption size on the
   card surface, which is where this package renders it: 5.82:1, against
   4.5:1 required (docs/DESIGN.md). No shipped trend site sets a sunken
   background. The sunken pairing is cited only because this partial is
   include-only, so a consumer may place it on that canvas; DESIGN.md
   records 5.26:1 there, which also clears AA. Reinforcing DIRECTION only
   would be neutral; reinforcing a judgement the caller has not stated is
   not, so this is not
   "colour is reinforcement only" restated, it is colour removed from a slot
   that had no basis for it. Judgement colour is opt-in via .bw-trend--good/
   --bad, set deliberately by the caller through _trend_indicator.html's new
   sentiment argument (see that template's header comment); it is layered on
   top of, never instead of, the direction modifier below. */
.bw-trend--up,
.bw-trend--down,
.bw-trend--flat {
  color: var(--bw-color-fg-muted);
}
/* Opt-in judgement colour (#388): caller-set via sentiment, independent of
   direction. Matches BR-BW-TPL-007/AC-BW-074's colour-is-reinforcement-only
   rule because the glyph and visually-hidden text already carry direction,
   and trend_label already carries judgement in words; this only reinforces
   a judgement the caller has explicitly stated, never implies one. */
.bw-trend--good {
  color: var(--bw-color-success-fg);
}
.bw-trend--bad {
  color: var(--bw-color-danger-fg);
}

/* Retained aliases of .bw-trend* (icvoss/django-brickwork#334). _stat.html
   emits BOTH families on one element: there is one implementation, with two
   names. The modifiers are retained alongside the base deliberately, because
   the base carries layout and the MODIFIERS carry the colour, so a consumer
   overriding trend colour selects a modifier. Retaining only the base would
   break exactly the override these classes exist to enable. Direction is
   neutral and judgement is opt-in here too (#388), matching .bw-trend*
   above exactly: one implementation, so the alias family cannot disagree
   with the base family on the same element. */
.bw-stat__trend {
  display: inline-flex;
  align-items: center;
  gap: var(--bw-space-1);
  color: var(--bw-color-fg-muted);
  font-size: var(--bw-text-caption-size);
  font-weight: var(--bw-text-caption-weight);
  line-height: var(--bw-text-caption-line-height);
}
.bw-stat__trend--up,
.bw-stat__trend--down,
.bw-stat__trend--flat {
  color: var(--bw-color-fg-muted);
}
.bw-stat__trend--good {
  color: var(--bw-color-success-fg);
}
.bw-stat__trend--bad {
  color: var(--bw-color-danger-fg);
}

/* --- Stat comparison (VIZ-019/020) ------------------------------------------ */

/* This-vs-last-period tile: same tile chrome as .bw-stat (surface, fg-mix
   edge, ambient elevation, density padding), stacking a label, the current
   value, a visible previous-period caption, and a trend row delegated to
   _trend_indicator.html. Both current and previous arrive pre-formatted
   from the caller (VIZ-020); this rule set never sizes text based on
   content, only on the fixed sm/md/lg scale below. */
.bw-stat-comparison {
  display: flex;
  flex-direction: column;
  gap: var(--bw-space-1);
  background: var(--bw-color-surface);
  border: var(--bw-size-border-hairline) solid
    color-mix(in oklab, var(--bw-color-fg) 12%, var(--bw-color-surface));
  border-radius: var(--bw-radius-lg);
  box-shadow:
    var(--bw-elevation-1),
    0 4px 14px -4px oklch(0 0 0 / 0.1);
  padding: var(--bw-density-card-padding);
}
.bw-stat-comparison__label {
  color: var(--bw-color-fg-muted);
  font-family: var(--bw-text-overline-family);
  font-size: var(--bw-text-overline-size);
  font-weight: var(--bw-text-overline-weight);
  line-height: var(--bw-text-overline-line-height);
  letter-spacing: var(--bw-text-overline-tracking);
  text-transform: uppercase;
}
.bw-stat-comparison__current {
  margin: 0;
  font-size: var(--bw-component-stat-tile-value-size, var(--bw-font-size-3xl));
  font-weight: var(--bw-font-weight-semibold);
  line-height: var(--bw-font-line-height-tight);
  font-variant-numeric: tabular-nums;
}
/* The previous-period value and its period_label caption share one line:
   period_label qualifies previous by DOM adjacency (VIZ-019's "not left as
   floating text"), not by an aria-describedby id, matching this template's
   own header-comment reasoning. */
.bw-stat-comparison__previous {
  color: var(--bw-color-fg-muted);
  font-size: var(--bw-text-caption-size);
  font-weight: var(--bw-text-caption-weight);
  line-height: var(--bw-text-caption-line-height);
}
.bw-stat-comparison__period {
  color: var(--bw-color-fg-muted);
}
/* size arg (mirrors _stat.html's VIZ-027 seam): modifiers re-point the
   same value-size component token _stat.html uses, so a comparison tile
   sits at the same scale as a plain stat tile sharing its grid. */
.bw-stat-comparison--sm {
  --bw-component-stat-tile-value-size: var(--bw-font-size-2xl);
  padding: var(--bw-space-3);
}
.bw-stat-comparison--lg {
  --bw-component-stat-tile-value-size: var(--bw-font-size-4xl);
  padding: var(--bw-space-6);
}
/* The trend row (_trend_indicator.html's css_class seam, matching #334's own
   .bw-stat__trend alias above): this template passes
   css_class="bw-stat-comparison__trend", so the rendered element carries
   both .bw-trend and .bw-stat-comparison__trend (plus their matching
   modifiers). One implementation (.bw-trend, styled above), one more
   retained alias, so a consumer stylesheet selecting
   .bw-stat-comparison__trend directly still matches. Direction is neutral
   and judgement is opt-in here too (#388), matching .bw-trend* above
   exactly, for the same one-implementation reason. */
.bw-stat-comparison__trend {
  display: inline-flex;
  align-items: center;
  gap: var(--bw-space-1);
  color: var(--bw-color-fg-muted);
  font-size: var(--bw-text-caption-size);
  font-weight: var(--bw-text-caption-weight);
  line-height: var(--bw-text-caption-line-height);
}
.bw-stat-comparison__trend--up,
.bw-stat-comparison__trend--down,
.bw-stat-comparison__trend--flat {
  color: var(--bw-color-fg-muted);
}
.bw-stat-comparison__trend--good {
  color: var(--bw-color-success-fg);
}
.bw-stat-comparison__trend--bad {
  color: var(--bw-color-danger-fg);
}

/* --- Ranked list (icvoss/django-brickwork#183) ------------------------------ */

/* An ordered <ol>: no default list-style/padding of its own (the numbering
   is announced to assistive tech via list semantics, not rendered as a
   visible marker glyph). Row rhythm via the row-gap component token. */
.bw-ranked-list {
  display: flex;
  flex-direction: column;
  gap: var(--bw-component-ranked-list-row-gap);
  margin: 0;
  padding: 0;
  list-style: none;
}
/* Visual column header for the secondary numeric column
   (icvoss/django-brickwork#605): mirrors .bw-ranked-list__row's three-column
   grid so the caption aligns with the figures it labels. aria-hidden on the
   element itself; each secondary cell carries a visually-hidden copy of the
   caption for assistive tech. */
.bw-ranked-list__head {
  display: grid;
  grid-template-columns: 1fr auto auto;
  column-gap: var(--bw-space-3);
  margin-block-end: var(--bw-space-1);
  color: var(--bw-color-fg-muted);
  font-size: var(--bw-text-caption-size);
  line-height: var(--bw-text-caption-line-height);
  text-align: end;
}
.bw-ranked-list__row {
  display: grid;
  grid-template-columns: 1fr auto;
  column-gap: var(--bw-space-3);
  row-gap: var(--bw-space-1);
}
.bw-ranked-list--with-secondary .bw-ranked-list__row {
  grid-template-columns: 1fr auto auto;
}
/* A linked row (VIZ-024): the <li>'s only child IS the anchor (mirroring
   a.bw-stat, not a wrapper around already-laid-out children), so the whole
   row stays keyboard-reachable via one link and keeps its own native focus
   ring, which a box-less display: contents wrapper cannot guarantee across
   browsers. */
a.bw-ranked-list__row {
  color: inherit;
  text-decoration: none;
}
a.bw-ranked-list__row:hover .bw-ranked-list__label,
a.bw-ranked-list__row:focus-visible .bw-ranked-list__label {
  text-decoration: underline;
}
.bw-ranked-list__primary {
  display: flex;
  flex-direction: column;
  gap: var(--bw-space-0-5);
  min-inline-size: 0;
}
.bw-ranked-list__label {
  color: var(--bw-color-fg);
  font-size: var(--bw-text-body-md-size);
  line-height: var(--bw-text-body-md-line-height);
}
/* Plain already-formatted text under the label (icvoss/django-brickwork#606). */
.bw-ranked-list__secondary-text {
  color: var(--bw-color-fg-muted);
  font-size: var(--bw-text-caption-size);
  line-height: var(--bw-text-caption-line-height);
}
.bw-ranked-list__value {
  color: var(--bw-color-fg-muted);
  font-variant-numeric: tabular-nums;
  text-align: end;
}
.bw-ranked-list__secondary {
  color: var(--bw-color-fg-muted);
  font-variant-numeric: tabular-nums;
  text-align: end;
}
.bw-ranked-list__bar {
  grid-column: 1 / -1;
  display: block;
  block-size: var(--bw-component-ranked-list-bar-thickness);
  background: var(--bw-color-surface-sunken);
  border-radius: var(--bw-radius-full);
  overflow: hidden;
}
/* The proportional fill: an unqualified 0-100 custom property turned into a
   length here (mirrors .bw-progress__fill exactly), never a "N%" string
   built at the template layer. Painted with a pseudo-element so the track
   background above stays visible as the non-colour contrast signal. */
.bw-ranked-list__bar::before {
  content: "";
  display: block;
  block-size: 100%;
  background: var(--bw-color-accent);
  border-radius: var(--bw-radius-full);
  width: calc(var(--bw-ranked-list-value, 0) * 1%);
}
/* Top-N / truncation note below the list (icvoss/django-brickwork#604).
   fg-muted, not fg-subtle: caption-size subtle ink fails WCAG 1.4.3. */
.bw-ranked-list__caption {
  margin-block-start: var(--bw-space-3);
  margin-block-end: 0;
  color: var(--bw-color-fg-muted);
  font-size: var(--bw-text-caption-size);
  line-height: var(--bw-text-caption-line-height);
}

/* --- Sparkline (VIZ-003/004/005/006) ---------------------------------------- */

/* An inline single-series trend line: label/value as visible text (COL-030),
   the <svg> itself scaling to whatever box the caller's container gives it
   (no fixed size of its own, matching _stat.html's bw-stat__sparkline slot,
   which this component is designed to fill). Label/value stack above the
   line by default; a caller wanting them side by side does so from its own
   container, since this component owns no width-dependent layout of its
   own (Responsive, per the template header). */
.bw-sparkline {
  display: flex;
  flex-direction: column;
  gap: var(--bw-space-1);
}
.bw-sparkline__label {
  color: var(--bw-color-fg-muted);
  font-size: var(--bw-text-caption-size);
  line-height: var(--bw-text-caption-line-height);
}
.bw-sparkline__value {
  color: var(--bw-color-fg);
  font-variant-numeric: tabular-nums;
}
/* Mirrors .bw-stat__trend exactly (BR-BW-TPL-007): the glyph + hidden text
   pairing that keeps direction off colour alone. */
.bw-sparkline__trend {
  display: inline-flex;
  align-items: center;
  gap: var(--bw-space-1);
  color: var(--bw-color-fg-muted);
}
.bw-sparkline--up .bw-sparkline__trend {
  color: var(--bw-color-success-fg);
}
.bw-sparkline--down .bw-sparkline__trend {
  color: var(--bw-color-danger-fg);
}
.bw-sparkline__svg {
  display: block;
  inline-size: 100%;
  block-size: 100%;
}
/* Neutral reuses the shared chart palette's first colour (VIZ-026) rather
   than a hardcoded value: --bw-color-sparkline-stroke DEFAULTS to
   --bw-color-chart-1, so a lone sparkline matches the chart palette's first
   series and nothing changes visually, but the token exists so a consumer
   can override it. VIZ-026 lets any VIZ stroke resolve --bw-color-chart-1..8
   to colour-match a chart shown alongside; reading chart-1 directly here
   would leave that consumer nothing to override but this rule, and
   docs/BRANDING.md forbids reaching into component internals. The seam is
   the point, not the default value. */
.bw-sparkline--neutral .bw-sparkline__line {
  stroke: var(--bw-color-sparkline-stroke);
}
/* Trend reuses the SAME per-theme-authored, AA-verified ink _stat.html's own
   trend TEXT already uses (--bw-color-success-fg / --bw-color-danger-fg),
   rather than a duplicate --bw-sparkline-stroke-positive/-negative pair
   carrying identical values: one authored pairing, two consumers. */
.bw-sparkline--trend.bw-sparkline--up .bw-sparkline__line {
  stroke: var(--bw-color-success-fg);
}
.bw-sparkline--trend.bw-sparkline--down .bw-sparkline__line {
  stroke: var(--bw-color-danger-fg);
}
.bw-sparkline--trend.bw-sparkline--flat .bw-sparkline__line {
  stroke: var(--bw-color-fg-muted);
}
.bw-sparkline__line {
  stroke-width: var(--bw-component-sparkline-stroke-width);
  stroke-linecap: round;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}
/* The highlighted point (VIZ-005): filled with the brand accent, matching
   .bw-ranked-list__bar::before's own reuse of --bw-color-accent rather than
   a new marker-only token, since nothing about this dot needs a colour the
   accent does not already provide. */
.bw-sparkline__marker {
  fill: var(--bw-color-accent);
  r: var(--bw-component-sparkline-marker-radius);
}
/* --- Gauge (icvoss/django-brickwork VIZ-007 to VIZ-010) --------------------- */

/* A circular progress ring: two stacked SVG circles (track, then arc) inside
   a fixed 0-100 viewBox, overlaid with the visible label text via a grid
   that centres every child in the same cell. The diameter is the only thing
   that changes per size modifier; the viewBox and stroke maths are fixed at
   every size, so the ring never distorts. */
.bw-gauge {
  display: inline-grid;
  place-items: center;
  inline-size: var(--bw-component-gauge-diameter-md);
  block-size: var(--bw-component-gauge-diameter-md);
}
.bw-gauge__svg {
  grid-area: 1 / 1;
  inline-size: 100%;
  block-size: 100%;
  /* The dash geometry below is computed for a circle whose arc should start
     at 12 o'clock and fill clockwise; SVG's own 0deg is 3 o'clock, so the
     whole svg is rotated -90deg here (a fixed, non-inline transform, never
     a caller-influenced value) rather than adjusting the Python-computed
     dash-offset maths to compensate. */
  transform: rotate(-90deg);
}
.bw-gauge__track,
.bw-gauge__arc {
  fill: none;
  stroke-width: var(--bw-component-gauge-stroke-width);
}
/* The track is the same "sunken trough" role .bw-progress__track and
   .bw-ranked-list__bar already carry: visible contrast against the arc that
   holds up even where the arc's own hue is hard to distinguish. */
.bw-gauge__track {
  stroke: var(--bw-color-surface-sunken);
}
.bw-gauge__arc {
  /* --bw-gauge-dash-array/-offset are emitted inline as bare, fixed-format
     numbers (never a %/px-suffixed string): SVG's own stroke-dasharray/
     -dashoffset take unitless user-space units directly, so there is no
     calc() translation step the way .bw-progress__fill's width needs.
     Both carry a 0 fallback, which degrades to an empty arc rather than
     invalid geometry if the inline style is ever absent. The fallback also
     satisfies test_frontend_css_only_references_emitted_token_names, but
     note what that does and does not prove: the test exempts ANY var() with
     a fallback, so it cannot distinguish a genuine per-instance hook from a
     misspelled token name (icvoss/django-brickwork#340). These two are hooks
     because the template sets them inline per instance, not because the
     comma is there. */
  stroke-dasharray: var(--bw-gauge-dash-array, 0);
  stroke-dashoffset: var(--bw-gauge-dash-offset, 0);
  stroke-linecap: round;
  transition: stroke-dashoffset var(--bw-duration-normal) var(--bw-ease-standard);
}
/* threshold_bands token (VIZ-009): a closed set of four ALREADY-SHIPPED
   semantic colours, never a caller-chosen hue. No new colour token is
   authored for this component. */
.bw-gauge__arc--accent {
  stroke: var(--bw-color-accent);
}
.bw-gauge__arc--success {
  stroke: var(--bw-color-success);
}
.bw-gauge__arc--warning {
  stroke: var(--bw-color-warning);
}
.bw-gauge__arc--danger {
  stroke: var(--bw-color-danger);
}
/* The visible numeric label (COL-030, VIZ-008): stacked in the SAME grid
   cell as the svg via grid-area, so it sits centred inside the ring without
   absolute positioning. Never aria-hidden: this is the one channel the
   gauge's meaning is guaranteed to survive on with every colour and the arc
   itself stripped away. */
.bw-gauge__label {
  grid-area: 1 / 1;
  color: var(--bw-color-fg);
  font-size: var(--bw-text-body-md-size);
  font-weight: var(--bw-font-weight-semibold);
  font-variant-numeric: tabular-nums;
  text-align: center;
}
/* size arg (VIZ-010): modifiers re-point the diameter component token, the
   same mechanism .bw-stat--sm/--lg use for their own value-size token. */
.bw-gauge--sm {
  inline-size: var(--bw-component-gauge-diameter-sm);
  block-size: var(--bw-component-gauge-diameter-sm);
}
.bw-gauge--md {
  inline-size: var(--bw-component-gauge-diameter-md);
  block-size: var(--bw-component-gauge-diameter-md);
}
.bw-gauge--lg {
  inline-size: var(--bw-component-gauge-diameter-lg);
  block-size: var(--bw-component-gauge-diameter-lg);
}
/* Reduced motion: the arc's transition above is a state-change transition
   (a value changing on a later htmx swap, say), not a decorative looping
   animation, matching .bw-progress__fill's own ungated width transition; no
   prefers-reduced-motion gate is needed for the same reason that one has
   none. */

/* --- Chart card (icvoss/django-brickwork CHT-001/002/003/024) --------------- */

/* The chrome around a consumer's own charting engine: _chart_card.html
 * extends _card.html for elevation/radius/density padding, and this file
 * styles only the two regions it declares itself, chart_legend and the mount
 * box. Column layout is the shared shape for every legend_position: "top"
 * and "bottom" differ only in order (row-reverse below), and "side" switches
 * to a row so the legend sits beside the mount rather than above/below it. */
.bw-chart-card {
  display: flex;
  flex-direction: column;
  gap: var(--bw-space-4);
}
.bw-chart-card--legend-bottom {
  flex-direction: column-reverse;
}
.bw-chart-card--legend-side {
  flex-direction: row;
  align-items: flex-start;
}
.bw-chart-card--legend-side > .bw-chart-card__mount {
  flex: 1 1 auto;
  /* min-inline-size: 0 so a wide engine-rendered canvas/SVG can shrink inside
     the flex row instead of overflowing the card (the classic flex-item
     intrinsic-size trap; every other row layout in this file that shares
     space with a shrinkable sibling needs the same override). */
  min-inline-size: 0;
}

/* The mount box itself (CHT-001: "unstyled beyond padding + a fixed
 * aspect-ratio/min-height box"). bw_chart_mount emits this class on the
 * element the consumer's engine paints into; .bw-chart-card__mount above is
 * this file's own layout wrapper around it (present even before the mount
 * tag renders, so the loading/error/empty states share the same slot).
 *
 * CHT-024 reservation, read d3fa3db before changing this: the box must
 * occupy its final size from FIRST PAINT, before the engine's JS ever runs,
 * so mounting the engine in later causes no layout-shift entry. Unlike
 * d3fa3db's theme-switch fix, this box is never hidden-then-revealed, so
 * there is no visibility-vs-display distinction to make here: reservation is
 * simply giving the box real geometry (min-block-size and/or aspect-ratio)
 * unconditionally, from the first server-rendered byte. The two custom
 * properties are unset (not layout-affecting) unless bw_chart_mount's
 * min_height=/aspect_ratio= arguments set them inline, per component,
 * because there is no one right chart height to default to package-wide;
 * a consumer that reserves neither gets whatever size its container gives
 * the box, with no shift protection of its own. */
.bw-chart-mount {
  display: block;
  inline-size: 100%;
  min-block-size: var(--bw-chart-mount-min-height, auto);
  aspect-ratio: var(--bw-chart-mount-aspect-ratio, auto);
}
/* The consumer's engine draws a <canvas> or <svg> into the mount; either
 * fills the reserved box exactly rather than adding its own intrinsic
 * sizing on top of it (an unstyled <canvas> defaults to 300x150 and an
 * <svg> to its own viewBox dimensions, both of which would fight the
 * min-block-size/aspect-ratio reservation above rather than filling it). */
.bw-chart-mount > canvas,
.bw-chart-mount > svg {
  display: block;
  inline-size: 100%;
  block-size: 100%;
}

/* App Overview example: server-rendered trend SVG passed as chart_card mount=
 * (no JS engine). Fills the mount slot without fighting the card chrome. */
.bw-chart-card__mount > .bw-dashboard-trend {
  display: block;
  inline-size: 100%;
  block-size: auto;
  max-block-size: 14rem;
}

/* The legend region (CHT-003, CHT-014): brickwork styles the row/column
 * chrome only, the consumer supplies each swatch+label pair's markup.
 * Wrapping and gap match the density axis's stack rhythm used elsewhere
 * (e.g. .bw-theme-switch__group), so a legend reads as one coherent row
 * regardless of how many series the consumer's data has. */
.bw-chart-card__legend {
  display: flex;
  flex-wrap: wrap;
  gap: var(--bw-space-2) var(--bw-space-4);
}
.bw-chart-card--legend-side .bw-chart-card__legend {
  flex-direction: column;
  flex: 0 0 auto;
}

/* Loading skeleton (CHT-009): a single block shape stands in for the whole
 * mount while the engine has no data to paint yet, matching the reserved
 * box's own sizing so the skeleton occupies the same footprint the real
 * chart will, rather than a fixed guess that itself shifts on reveal. */
.bw-chart-card__skeleton {
  display: block;
  inline-size: 100%;
  min-block-size: var(--bw-chart-mount-min-height, auto);
  aspect-ratio: var(--bw-chart-mount-aspect-ratio, auto);
}

/* --- Chart data table (CHT-012's fallback, CHT-013's data_table_mode) ------- */

/* The accessible transcript of what the chart plots, rendered by
 * bw_chart_data_table as a SIBLING of the mount (never a descendant: the
 * mount's role="img" makes its descendants presentational, so a table inside
 * it reaches nobody). See _chart_data_table.html's own header for the full
 * reasoning; this file only styles it.
 *
 * A SEPARATE class family from .bw-data-table, matching the separate template:
 * that component's rules carry a sortable-header voice, zebra striping, a
 * selection column and a stack/scroll responsive contract, none of which an
 * inert fallback transcript wants. Reusing its classes here would inherit all
 * of that apparatus visually while the markup carries none of it.
 *
 * Scoped ELEMENT selectors under the root, not a BEM class per cell: the
 * markup is a fixed, closed shape (one caption, one thead row, N tbody rows)
 * that no consumer composes into, so a class on every <th> and <td> would be
 * naming things nothing can address separately. The three classes this
 * component does emit are the ones a consumer's own CSS or test selectors
 * reasonably target: the root, the table, and the caption. */
.bw-chart-data-table {
  /* Horizontal scroll rather than reflow: a many-series matrix keeps its
     column/row header alignment intact, which is the whole point of the
     table, whereas a stacked card layout would break the coordinate pairing
     a screen reader announces. */
  overflow-x: auto;
  margin-block-start: var(--bw-space-4);
}
/* In data_table_mode="hidden" the table is clipped to a 1px box, so the
   scroll container above would always overflow while being impossible to
   see, reach or scroll. axe flags that as a keyboard-inaccessible scrollable
   region (scrollable-region-focusable, WCAG 2.1.1), and it is right to: a
   scroll affordance nobody can operate is a defect, not a false positive.
   The honest fix is to stop claiming the region scrolls when there is no
   visual box to scroll, rather than bolting tabindex="0" onto an element
   that is deliberately not a stop on the visual tab order. The visible and
   toggle modes keep the scroll container, because there the box is real and
   a pointer or keyboard user can actually operate it. Targets the descendant
   explicitly: a hiding mechanism on the ancestor cannot override a value the
   descendant sets on itself. */
.bw-visually-hidden > .bw-chart-data-table {
  overflow-x: visible;
  margin-block-start: 0;
}
.bw-chart-data-table__table {
  inline-size: 100%;
  border-collapse: collapse;
  font-size: var(--bw-text-body-sm-size);
  line-height: var(--bw-text-body-sm-line-height);
}
/* The caption IS the table's accessible name, so it is never hidden: a
   consumer wanting it out of the visual render uses data_table_mode="hidden",
   which hides the whole table (name included) rather than orphaning the
   table from its own name. */
.bw-chart-data-table__caption {
  text-align: start;
  padding-block-end: var(--bw-space-2);
  color: var(--bw-color-fg-muted);
  font-size: var(--bw-text-caption-size);
  font-weight: var(--bw-text-caption-weight);
  line-height: var(--bw-text-caption-line-height);
}
.bw-chart-data-table__table th,
.bw-chart-data-table__table td {
  padding: var(--bw-density-row-padding-block) var(--bw-density-row-padding-inline);
  border-block-end: var(--bw-size-border-hairline) solid var(--bw-color-border);
  text-align: start;
}
/* Column headers take the muted apparatus voice .bw-data-table__th uses, so a
   fallback table reads as the same species of object as a real data table
   even though it shares none of its classes. Row headers stay in the body
   voice: they are the category axis (data), not apparatus. */
.bw-chart-data-table__table thead th {
  color: var(--bw-color-fg-muted);
  font-size: var(--bw-font-size-xs);
  font-weight: var(--bw-font-weight-semibold);
  letter-spacing: var(--bw-font-tracking-wide);
  text-transform: uppercase;
}
.bw-chart-data-table__table tbody th {
  font-weight: var(--bw-font-weight-medium);
}
/* Series values align digit-for-digit, the same reason .bw-data-table__td
   carries tabular-nums: a column of numbers a reader scans vertically. */
.bw-chart-data-table__table tbody td {
  font-variant-numeric: tabular-nums;
}
/* The last row's hairline would double against the card's own border, exactly
   as .bw-data-table__row:last-child already avoids. */
.bw-chart-data-table__table tbody tr:last-child th,
.bw-chart-data-table__table tbody tr:last-child td {
  border-block-end: 0;
}
.bw-chart-card__skeleton .bw-skeleton--block {
  inline-size: 100%;
  block-size: 100%;
}

/* Visually hidden, screen-reader-available text: the stat trend's directional
   fallback ("increased"/"decreased", AC-BW-074) and any other markup whose
   meaning must survive with CSS colour stripped. Standard clip pattern; the
   px token keeps it out of the literal ban. */
.bw-visually-hidden {
  position: absolute;
  inline-size: var(--bw-space-px);
  block-size: var(--bw-space-px);
  margin: calc(var(--bw-space-px) * -1);
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* --- Scorecard (VIZ-011/012, CHT-026) --------------------------------------- */

/* The shared dashboard grid for stat tiles AND chart cards (CHT-026: a card
   is a card). A named-step grid, not _stat_band.html's continuous auto-fit:
   --bw-component-scorecard-columns-* steps the column count at fixed
   breakpoints, so a consumer overriding the token controls exactly how many
   columns a dashboard shows at each width, which a continuous auto-fit grid
   cannot expose as a single overridable number. Gap reuses the shared
   density-aware stack-gap token (VIZ-025); no scorecard-specific gap token
   is authored. */
.bw-scorecard {
  display: grid;
  grid-template-columns: repeat(var(--bw-component-scorecard-columns-base), 1fr);
  gap: var(--bw-density-stack-gap);
}
@media (width >= 40rem) {
  .bw-scorecard {
    grid-template-columns: repeat(var(--bw-component-scorecard-columns-sm), 1fr);
  }
}
@media (width >= 64rem) {
  .bw-scorecard {
    grid-template-columns: repeat(var(--bw-component-scorecard-columns-lg), 1fr);
  }
}
/* Every item, span or not, always carries the bare bw-scorecard__item class
   (the template's own {% for %} loop): min-width: 0 is the one rule this
   base class needs, the standard CSS grid fix that lets a long unbroken
   value/label inside a card shrink with its column instead of forcing the
   grid to overflow (the same fix _data_table.html's own responsive="stack"
   rule applies at its own grid boundary). No other layout property belongs
   here: column placement is the grid's own doing (equal by default, spanned
   items via the modifiers below), and the item never has visual chrome of
   its own (a scorecard is a layout device, this file's own docstring). */
.bw-scorecard__item {
  min-width: 0;
}
/* span= (VIZ-012): a closed 2/3/4 modifier set, matching every other
   closed-vocabulary ARG in this package (_card.html size, _gauge.html size),
   never an arbitrary caller integer forwarded into an inline
   style="grid-column: span N" attribute. Equal (one column) by default,
   with no modifier class. A span wider than the CURRENT column count at a
   given breakpoint is simply clamped by the grid itself (CSS grid-column's
   own documented behaviour), not by this stylesheet. */
.bw-scorecard__item--span-2 {
  grid-column: span 2;
}
.bw-scorecard__item--span-3 {
  grid-column: span 3;
}
.bw-scorecard__item--span-4 {
  grid-column: span 4;
}
/* Below the app sidebar/drawer breakpoint, force a single column and
 * collapse spans so a span-2 tile cannot open an implicit second track
 * (icvoss/django-brickwork#525). At phone width that left supporting tiles
 * side by side in ~130px each. */
@media (width < 48rem) {
  .bw-scorecard {
    grid-template-columns: 1fr;
  }
  .bw-scorecard__item--span-2,
  .bw-scorecard__item--span-3,
  .bw-scorecard__item--span-4 {
    grid-column: auto;
  }
}

/* --- Disclosure (0.8.0 interaction set, CBH-016/017, CMP-026/027) ------------ */

/* Native <details>/<summary>: the no-JS floor holds by construction
 * (BR-BW-HTMX-001) and no Alpine component registers. Expected markup:
 *
 *   <details class="bw-disclosure bw-disclosure--divided" [name] [open]>
 *     <summary class="bw-disclosure__trigger">
 *       <span class="bw-disclosure__label">...</span>
 *       [<span class="bw-disclosure__meta">...</span>]
 *       <svg class="bw-icon bw-disclosure__caret">...</svg>
 *     </summary>
 *     <div class="bw-disclosure__panel">
 *       <div class="bw-disclosure__panel-inner">...</div>
 *     </div>
 *   </details>
 *
 * The panel/panel-inner pair is load-bearing: the expand animation is the
 * grid-template-rows 0fr -> 1fr technique (MOT-008, no JS height measure),
 * which needs exactly one track child carrying min-block-size: 0 and
 * overflow: hidden. Close is the browser's own instant hide (native
 * behaviour; the exit needs no choreography of its own). */
.bw-disclosure--bordered {
  border: var(--bw-size-border-hairline) solid var(--bw-color-border);
  border-radius: var(--bw-radius-lg);
}
.bw-disclosure--divided {
  border-block-end: var(--bw-size-border-hairline) solid var(--bw-color-border);
}
/* The trigger takes the drawn-control physics (hover overlay, radius-md,
 * fast colour transition), the same row treatment as the account-menu
 * trigger; radius-md stays concentric inside the bordered style's lg. */
.bw-disclosure__trigger {
  display: flex;
  align-items: center;
  gap: var(--bw-space-2);
  min-block-size: var(--bw-size-touch-target-min);
  padding-block: var(--bw-density-row-padding-block);
  padding-inline: var(--bw-density-row-padding-inline);
  border-radius: var(--bw-radius-md);
  cursor: pointer;
  color: var(--bw-color-fg);
  list-style: none; /* hide the default disclosure triangle; the caret glyph is the affordance */
  transition: var(--bw-transition-colors);
}
.bw-disclosure__trigger::-webkit-details-marker {
  display: none;
}
.bw-disclosure__trigger:hover {
  background-color: var(--bw-state-hover-overlay);
}
.bw-disclosure__trigger:active {
  background-color: var(--bw-state-active-overlay);
  transition-duration: var(--bw-duration-instant);
}
/* Label speaks in the label role: an interactive control, not body copy. */
.bw-disclosure__label {
  flex: 1 1 auto;
  min-width: 0;
  font-size: var(--bw-text-label-size);
  font-weight: var(--bw-text-label-weight);
  line-height: var(--bw-text-label-line-height);
}
/* Inline-end meta (an icon or badge, CMP-027). Muted at rest; steps up to
 * the row ink on hover because muted text must never sit dimmed inside a
 * hover-overlaid container (DESIGN.md 4.2 contrast note). */
.bw-disclosure__meta {
  flex-shrink: 0;
  color: var(--bw-color-fg-muted);
  font-size: var(--bw-text-caption-size);
  line-height: var(--bw-text-caption-line-height);
  transition: var(--bw-transition-colors);
}
.bw-disclosure__trigger:hover .bw-disclosure__meta {
  color: var(--bw-color-fg);
}
/* Caret rotation rides the shared transform transition (DESIGN.md 8.3
 * interaction map: disclosure and caret rotate normal); decorative glyph,
 * no AA duty. */
.bw-disclosure__caret {
  flex-shrink: 0;
  color: var(--bw-color-fg-muted);
  transition: var(--bw-transition-transform);
}
.bw-disclosure[open] .bw-disclosure__caret {
  transform: rotate(180deg);
}
/* The 0fr -> 1fr expand (MOT-008). At rest inside an open details the panel
 * is a one-track grid; opening replays the track growing from nothing at
 * normal/standard. Reduced motion: the global floor caps the keyframe to
 * an instant state change, never a frozen mid-height. */
.bw-disclosure__panel {
  display: grid;
  grid-template-rows: 1fr;
}
.bw-disclosure[open] .bw-disclosure__panel {
  animation: bw-disclosure-expand var(--bw-duration-normal) var(--bw-ease-standard);
}
@keyframes bw-disclosure-expand {
  from {
    grid-template-rows: 0fr;
  }
  to {
    grid-template-rows: 1fr;
  }
}
.bw-disclosure__panel-inner {
  min-block-size: 0;
  overflow: hidden;
  padding-block: var(--bw-space-1) var(--bw-space-3);
  /* Content aligns under the trigger label, inside the same row inset. */
  padding-inline: var(--bw-density-row-padding-inline);
  font-size: var(--bw-text-body-sm-size);
  line-height: var(--bw-text-body-sm-line-height);
}

/* --- Dropdown menu (0.8.0 interaction set, CBH-006/007/008) ------------------ */

/* The account-menu recipe generalised: the no-JS floor is a native
 * <details> disclosure of plain links (no ARIA menu roles until bwDropdown
 * upgrades the markup at init, BR-BW-HTMX-006). Expected markup:
 *
 *   <details class="bw-dropdown [bw-dropdown--end]">
 *     <summary class="bw-dropdown__trigger bw-btn bw-btn--secondary">
 *       label <svg class="bw-icon bw-dropdown__caret">...</svg>
 *     </summary>
 *     <div class="bw-dropdown__panel">
 *       <a class="bw-dropdown__item">
 *         [<svg class="bw-icon bw-dropdown__item-icon">...</svg>] label
 *       </a>
 *       <div class="bw-dropdown__divider"></div>
 *       <a class="bw-dropdown__item bw-dropdown__item--danger">...</a>
 *     </div>
 *   </details>
 *
 * Open/close state is carried by the details [open] attribute on BOTH legs
 * (bwDropdown syncs details.open alongside its own [data-bw-open] root
 * hook), so one entrance rule serves the floor and the enhanced mode.
 * Close is an instant state removal on both legs: the JS lane is
 * state-driven and never sequences animation (motion is CSS-owned), and a
 * native details hides its content the moment [open] goes, so the 04
 * exit choreography (fade at fast/in) has no element left to animate;
 * recorded as a deviation in the lane report. */
.bw-dropdown {
  position: relative;
  display: inline-block;
}
/* The trigger is bw_button chrome on a summary; only the summary-specific
 * resets live here (the disclosure marker goes, the caret glyph and
 * aria-expanded carry the affordance instead). */
.bw-dropdown__trigger {
  cursor: pointer;
  list-style: none;
}
.bw-dropdown__trigger::-webkit-details-marker {
  display: none;
}
.bw-dropdown__caret {
  color: var(--bw-color-fg-muted);
  transition: var(--bw-transition-transform);
}
.bw-dropdown[open] .bw-dropdown__caret {
  transform: rotate(180deg);
}
/* Dropdown-shaped panel per the DESIGN.md reserved rows: surface-raised,
 * elevation-3 at z-dropdown, radius-lg (concentric with the item radius),
 * the menu min-width token, and soft ambient under elevation (dark reset at
 * the end of this file). Entrance is bw-fade-in-up at normal/out
 * (04-interfaces 4b). */
.bw-dropdown__panel {
  position: absolute;
  inset-block-start: calc(100% + var(--bw-space-1));
  inset-inline-start: 0;
  min-inline-size: var(--bw-component-menu-min-width);
  padding: var(--bw-space-1);
  background: var(--bw-color-surface-raised);
  border: var(--bw-size-border-hairline) solid
    color-mix(in oklab, var(--bw-color-fg) 12%, var(--bw-color-surface-raised));
  border-radius: var(--bw-radius-lg);
  box-shadow:
    var(--bw-elevation-3),
    0 12px 28px -8px oklch(0 0 0 / 0.14);
  z-index: var(--bw-z-dropdown);
}
.bw-dropdown[open] .bw-dropdown__panel {
  animation: bw-fade-in-up var(--bw-duration-normal) var(--bw-ease-out);
}
.bw-dropdown--end .bw-dropdown__panel {
  inset-inline-start: auto;
  inset-inline-end: 0;
}
/* Menu items: body-sm rows, hover overlay, instant pressed, muted icons
 * inheriting the row ink on hover (the account-menu physics verbatim). */
.bw-dropdown__item {
  display: flex;
  align-items: center;
  gap: var(--bw-space-2);
  padding: var(--bw-space-2) var(--bw-space-3);
  border-radius: var(--bw-radius-md);
  color: var(--bw-color-fg);
  text-decoration: none;
  white-space: nowrap;
  font-size: var(--bw-text-body-sm-size);
  line-height: var(--bw-text-body-sm-line-height);
  transition: var(--bw-transition-colors);
}
.bw-dropdown__item:hover {
  background-color: var(--bw-state-hover-overlay);
}
.bw-dropdown__item:active {
  background-color: var(--bw-state-active-overlay);
  transition-duration: var(--bw-duration-instant);
}
.bw-dropdown__item-icon {
  color: var(--bw-color-icon-muted);
}
.bw-dropdown__item:hover .bw-dropdown__item-icon {
  color: inherit;
}
/* Danger intent takes the text-duty danger tier on the raised surface, the
 * verified account-menu pairing (DESIGN.md 4.5 branding note). */
.bw-dropdown__item--danger {
  color: var(--bw-color-danger-fg);
}
/* An explicit divider item: a full-width hairline extended to the panel's
 * inner edges (the panel padding is space-1), breathing space either side. */
.bw-dropdown__divider {
  block-size: var(--bw-size-border-hairline);
  margin-block: var(--bw-space-1);
  margin-inline: calc(-1 * var(--bw-space-1));
  background: var(--bw-color-border);
  border: 0;
}

/* --- Tabs (0.8.0 interaction set, CBH-013/014/015, CMP-024/025) -------------- */

/* The tag renders the tablist only; panels are consumer markup wrapped in
 * the shipped tab_panel partial. Markup (per _tabs.html; tab roles and
 * roving tabindex arrive only when bwTabs upgrades at init):
 *
 *   <div class="bw-tabs bw-tabs--underline|--pill">
 *     <nav class="bw-tabs__list">
 *       <a class="bw-tabs__tab [bw-tabs__tab--active]" href="?tab=key">
 *         <span class="bw-tabs__label">label</span>
 *         [<span class="bw-tabs__badge">3</span>]
 *       </a>
 *     </nav>
 *   </div>
 *   <div class="bw-tabs__panel" id="bw-tabpanel-<id>-<key>">...</div>
 *
 * No-JS floor: each tab is a real anchor and the server renders the active
 * panel full-page (BR-BW-HTMX-006); roving tabindex is JS-only and needs no
 * CSS beyond the global :focus-visible ring. The underline style is the
 * default rendering of the base classes; --underline itself adds nothing. */
.bw-tabs__list {
  display: flex;
  flex-wrap: wrap;
  gap: var(--bw-space-1);
  /* The hairline rail the active marker sits on (underline style). */
  border-block-end: var(--bw-size-border-hairline) solid var(--bw-color-border);
}
/* Tabs speak in the label role: chrome one step below content, matching the
 * nav band. Weight stays medium in the active state deliberately, so a tab
 * never changes width as selection moves (no layout jitter, BR-BW-HTMX-009
 * in spirit). font/background/border resets keep the rule correct whether
 * the template renders an anchor (the floor) or a button. */
.bw-tabs__tab {
  display: inline-flex;
  align-items: center;
  gap: var(--bw-space-2);
  min-block-size: var(--bw-size-touch-target-min);
  padding-block: var(--bw-density-row-padding-block);
  padding-inline: var(--bw-space-3);
  background: transparent;
  border: 0;
  /* The marker slot: transparent at rest so rows never shift on selection;
   * the negative block-end margin seats the marker ON the rail. The 04
   * contract binds the marker to border-thick in accent (the nav band
   * marker recipe at the tab scale). */
  border-block-end: var(--bw-size-border-thick) solid transparent;
  margin-block-end: calc(-1 * var(--bw-size-border-hairline));
  color: var(--bw-color-fg-muted);
  font: inherit;
  font-size: var(--bw-text-label-size);
  font-weight: var(--bw-text-label-weight);
  line-height: var(--bw-text-label-line-height);
  text-decoration: none;
  cursor: pointer;
  transition: var(--bw-transition-colors);
}
.bw-tabs__tab:hover {
  color: var(--bw-color-fg);
}
.bw-tabs__tab:active {
  transition-duration: var(--bw-duration-instant);
}
/* Active tab: the nav band recipe (accent-duty text tier + accent marker).
 * Two equivalent state carriers, one rule: the server-rendered floor marks
 * the active anchor with the --active class; bwTabs marks its JS-selected
 * tab with [data-bw-active] as selection moves (the class is the floor's,
 * the attribute the enhancement's). The :hover restatement outranks the
 * plain hover rule so the active tab never dims back to fg on
 * pointer-over. */
.bw-tabs__tab:is(.bw-tabs__tab--active, [data-bw-active]),
.bw-tabs__tab:is(.bw-tabs__tab--active, [data-bw-active]):hover {
  color: var(--bw-component-nav-item-active-text);
  border-block-end-color: var(--bw-component-nav-item-active-border);
}
/* Badge count: the nav badge's quiet bordered chip. */
.bw-tabs__badge {
  flex-shrink: 0;
  min-inline-size: var(--bw-space-5);
  padding-block: var(--bw-space-0-5);
  padding-inline: var(--bw-space-1);
  border-radius: var(--bw-radius-full);
  background: var(--bw-color-surface-sunken);
  color: var(--bw-color-fg);
  border: var(--bw-size-border-hairline) solid var(--bw-color-border);
  font-size: var(--bw-text-caption-size);
  font-weight: var(--bw-font-weight-medium);
  font-variant-numeric: tabular-nums;
  line-height: var(--bw-font-line-height-none);
  text-align: center;
}
/* Pill style (CMP-024): no rail, no marker; selection is a selected-bg fill
 * at radius-full. Hover/pressed take the overlay physics because a pill is
 * a shape, not a rail position. The active pill's hover layers the overlay
 * over the persistent fill (DESIGN.md 4.6 stacking). */
/* --underline is the DOCUMENTED DEFAULT. It previously had no rule at all, so
 * the default variant emitted a class the stylesheet never matched (#120 defect
 * class). The underline treatment is the base .bw-tabs__tab marker, so this
 * rule states that relationship rather than inventing a second look. */
.bw-tabs--underline .bw-tabs__list {
  gap: var(--bw-space-1);
}

.bw-tabs--pill .bw-tabs__list {
  border-block-end: 0;
}
.bw-tabs--pill .bw-tabs__tab {
  border-block-end: 0;
  margin-block-end: 0;
  border-radius: var(--bw-radius-full);
}
.bw-tabs--pill .bw-tabs__tab:hover {
  background-color: var(--bw-state-hover-overlay);
}
.bw-tabs--pill .bw-tabs__tab:active {
  background-color: var(--bw-state-active-overlay);
  transition-duration: var(--bw-duration-instant);
}
.bw-tabs--pill .bw-tabs__tab:is(.bw-tabs__tab--active, [data-bw-active]),
.bw-tabs--pill .bw-tabs__tab:is(.bw-tabs__tab--active, [data-bw-active]):hover {
  background-color: var(--bw-state-selected-bg);
  color: var(--bw-component-nav-item-active-text);
}
.bw-tabs--pill .bw-tabs__tab:is(.bw-tabs__tab--active, [data-bw-active]):hover {
  background-image: linear-gradient(var(--bw-state-hover-overlay), var(--bw-state-hover-overlay));
}
/* Panel spacing: the stack gap between the tablist and the panel. */
.bw-tabs__panel {
  margin-block-start: var(--bw-density-stack-gap);
}

/* --- Modal dialog (0.8.0 interaction set, CBH-001..005, CMP-020/021) --------- */

/* Consumed by extending _modal.html; two render paths, one partial
 * (BR-BW-HTMX-001). Markup (per _modal.html):
 *
 *   <div class="bw-modal bw-modal--sm|--md|--lg|--full">
 *     <div class="bw-modal__scrim" aria-hidden="true"></div>
 *     <div role="dialog" class="bw-modal__panel">
 *       <header class="bw-modal__header">
 *         <h2 class="bw-modal__title">...</h2>
 *         <button class="bw-modal__close bw-btn bw-btn--ghost bw-btn--sm bw-btn--icon-only">...</button>
 *       </header>
 *       <div class="bw-modal__body">...</div>
 *       [<footer class="bw-modal__footer">...</footer>]  (consumer-authored block)
 *     </div>
 *   </div>
 *
 * State model (the load-bearing part): AT REST this markup is the no-JS
 * floor, an ordinary in-flow panel on a full page, so nothing here hides
 * content or paints a scrim without JavaScript. The overlay physics apply
 * only under the JS-set open state, carried by two equivalent signals:
 * the .bw-modal--open class (the template's Alpine binding) and the
 * [data-bw-open] attribute (set by bwModal). Close is an instant
 * state removal (the JS lane never sequences animation), so the 04 exit
 * choreography (fast/in) is not wired; recorded in the lane report.
 * Body scroll locking is x-trap's noscroll modifier, not a CSS hook. */

/* Inside the stable #bw-modal-root swap container a modal exists only to
 * be opened (bwModal opens it on insertion); a closed one there is either
 * pre-init or post-dismissal chrome and must not render as an in-flow
 * panel at the end of the page. Anywhere else a closed modal IS the
 * floor page's visible panel, so this rule scopes to the root only. */
#bw-modal-root .bw-modal:not(.bw-modal--open):not([data-bw-open]) {
  display: none;
}
/* The resting (floor) panel: the alone-on-a-page altitude of the auth and
 * centred panels (elevation-2), raised surface, the modal radius row.
 * Flex column so the header and footer hold still while the body scrolls
 * (CMP-021) in both states. Fg-mix edge so the floor panel reads on white. */
.bw-modal__panel {
  display: flex;
  flex-direction: column;
  inline-size: 100%;
  max-inline-size: var(--bw-size-max-width-modal-md);
  margin-inline: auto;
  background: var(--bw-color-surface-raised);
  border: var(--bw-size-border-hairline) solid
    color-mix(in oklab, var(--bw-color-fg) 12%, var(--bw-color-surface-raised));
  border-radius: var(--bw-radius-xl);
  box-shadow: var(--bw-elevation-2);
}
/* The open state: a fixed, centred overlay layer at z-modal. */
.bw-modal:is(.bw-modal--open, [data-bw-open]) {
  position: fixed;
  inset: 0;
  z-index: var(--bw-z-modal);
  display: grid;
  place-items: center;
  padding: var(--bw-space-6);
}
/* The scrim exists only while open: the authored dark-over-content overlay
 * colour in both themes, at the overlay layer beneath the panel. Enter
 * fades at normal/out; a reduced-motion frozen scrim is fine (opacity
 * only, no misleading mid-state). At rest it is an unstyled, empty,
 * aria-hidden div with no box of its own. */
.bw-modal:is(.bw-modal--open, [data-bw-open]) .bw-modal__scrim {
  position: fixed;
  inset: 0;
  z-index: var(--bw-z-overlay);
  background: var(--bw-color-surface-overlay);
  animation: bw-fade-in var(--bw-duration-normal) var(--bw-ease-out);
}
@keyframes bw-fade-in {
  from {
    opacity: 0;
  }
}
/* The open panel rises to the reserved elevation-4 modal row and gains a
 * soft ambient under elevation (dark reset at the end of this file).
 * Entrance is the fade-in-up rise plus the 96% -> 100% scale at
 * normal/out (MOT-005); the rise distance is the space-1 token, the same
 * 4px as bw-fade-in-up. */
.bw-modal:is(.bw-modal--open, [data-bw-open]) .bw-modal__panel {
  position: relative;
  z-index: var(--bw-z-modal);
  max-block-size: 100%;
  box-shadow:
    var(--bw-elevation-4),
    0 16px 36px -10px oklch(0 0 0 / 0.16);
  animation: bw-modal-in var(--bw-duration-normal) var(--bw-ease-out);
}
@keyframes bw-modal-in {
  from {
    opacity: 0;
    transform: translateY(calc(-1 * var(--bw-space-1))) scale(0.96);
  }
  to {
    opacity: 1;
    transform: none;
  }
}
/* Size modifiers (CMP-020): sm/lg re-point the max-width token (the floor
 * panel honours them too); full is full-viewport, gutterless, radius
 * squared off, and only means anything as an overlay. */
.bw-modal--sm .bw-modal__panel {
  max-inline-size: var(--bw-size-max-width-modal-sm);
}
.bw-modal--lg .bw-modal__panel {
  max-inline-size: var(--bw-size-max-width-modal-lg);
}
.bw-modal--full:is(.bw-modal--open, [data-bw-open]) {
  padding: 0;
}
.bw-modal--full:is(.bw-modal--open, [data-bw-open]) .bw-modal__panel {
  max-inline-size: none;
  block-size: 100%;
  border-radius: var(--bw-radius-none);
}
/* Sticky header: title plus the always-present close control (BR-BW-JS-007),
 * divided from the scrolling body by a hairline. Density card padding scales
 * the chrome with the density axis. */
.bw-modal__header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--bw-space-3);
  padding: var(--bw-density-card-padding);
  border-block-end: var(--bw-size-border-hairline) solid var(--bw-color-border);
}
/* Dialog titles speak in the heading-sm role (DESIGN.md 7.4 dialog row). */
.bw-modal__title {
  margin: 0;
  font-family: var(--bw-text-heading-sm-family);
  font-size: var(--bw-text-heading-sm-size);
  font-weight: var(--bw-text-heading-sm-weight);
  line-height: var(--bw-text-heading-sm-line-height);
}
.bw-modal__close {
  flex-shrink: 0;
}
/* The body is the one scrolling region (min-block-size 0 lets the flex
 * column actually shrink it); thin border-toned scrollbar per the house
 * scroll chrome. */
.bw-modal__body {
  flex: 1 1 auto;
  min-block-size: 0;
  overflow-y: auto;
  padding: var(--bw-density-card-padding);
  scrollbar-width: thin;
  scrollbar-color: var(--bw-color-border) transparent;
}
/* Footer: the form actions recipe (flex row, space-3 gap) seated at the
 * inline end, the dialog convention, under its own hairline. Empty footers
 * render nothing at the template, so no :empty rule is needed. */
.bw-modal__footer {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--bw-space-3);
  padding: var(--bw-density-card-padding);
  border-block-start: var(--bw-size-border-hairline) solid var(--bw-color-border);
}
/* header_recipe / footer_recipe (Beat Phase C): match card region recipes. */
.bw-modal__header--muted,
.bw-modal--header-muted .bw-modal__header {
  background: var(--bw-color-surface-sunken);
}
.bw-modal__header--bordered,
.bw-modal--header-bordered .bw-modal__header {
  border-block-end-width: var(--bw-size-border-thick);
  border-block-end-color: var(--bw-color-border-strong);
}
.bw-modal--footer-muted .bw-modal__footer,
.bw-modal__footer--muted {
  background: var(--bw-color-surface-sunken);
}
.bw-modal--footer-actions .bw-modal__footer,
.bw-modal__footer--actions {
  justify-content: flex-end;
  gap: var(--bw-space-3);
}

/* --- Slide-over / side panel (0.14.0, brickwork#55) -------------------------- */

/* Consumed by extending _slide_over.html; two render paths, one partial
 * (BR-BW-HTMX-001), same shape as the modal above. Markup (per
 * _slide_over.html):
 *
 *   <div class="bw-slide-over bw-slide-over--sm|--md|--lg bw-slide-over--start|--end">
 *     <div class="bw-slide-over__scrim" aria-hidden="true"></div>
 *     <div role="dialog" class="bw-slide-over__panel">
 *       <header class="bw-slide-over__header">
 *         <h2 class="bw-slide-over__title">...</h2>
 *         <button class="bw-slide-over__close bw-btn bw-btn--ghost bw-btn--sm bw-btn--icon-only">...</button>
 *       </header>
 *       <div class="bw-slide-over__body">...</div>
 *       [<footer class="bw-slide-over__footer">...</footer>]  (consumer-authored block)
 *     </div>
 *   </div>
 *
 * State model: identical to the modal's (AT REST this is the no-JS floor,
 * an ordinary in-flow panel on a full page; the overlay physics apply only
 * under the JS-set [data-bw-open] state). Body scroll locking is x-trap's
 * noscroll modifier, not a CSS hook. */

/* Inside the dedicated #bw-slide-over-root swap container a panel exists
 * only to be opened (bwSlideOver opens it on insertion); a closed one
 * there must not render as an in-flow panel at the end of the page.
 * Anywhere else a closed panel IS the floor page's visible panel. */
#bw-slide-over-root .bw-slide-over:not([data-bw-open]) {
  display: none;
}
/* The resting (floor) panel: full block-size, edge-anchored width from the
 * modal's own size ramp (no separate width token: 28rem-48rem already
 * covers a side panel's usual range). Flex column so the header and
 * footer hold still while the body scrolls, matching the modal. Fg-mix
 * floor edge so the panel reads on white when rendered as a page. */
.bw-slide-over__panel {
  display: flex;
  flex-direction: column;
  inline-size: 100%;
  max-inline-size: var(--bw-size-max-width-modal-md);
  block-size: 100%;
  background: var(--bw-color-surface-raised);
  border: var(--bw-size-border-hairline) solid
    color-mix(in oklab, var(--bw-color-fg) 12%, var(--bw-color-surface-raised));
  box-shadow: var(--bw-elevation-2);
}
/* The open state: a fixed, edge-anchored overlay layer at z-modal, full
 * block-size, no padding (the panel meets the viewport edge flush). */
.bw-slide-over[data-bw-open] {
  position: fixed;
  inset-block: 0;
  inset-inline: 0;
  z-index: var(--bw-z-modal);
  display: flex;
}
.bw-slide-over--end[data-bw-open] {
  justify-content: flex-end;
}
.bw-slide-over--start[data-bw-open] {
  justify-content: flex-start;
}
/* The scrim: identical contract to the modal's. */
.bw-slide-over[data-bw-open] .bw-slide-over__scrim {
  position: fixed;
  inset: 0;
  z-index: var(--bw-z-overlay);
  background: var(--bw-color-surface-overlay);
  animation: bw-fade-in var(--bw-duration-normal) var(--bw-ease-out);
}
/* The open panel rises to the modal elevation row and slides in from its
 * anchored edge (translateX; logical properties mean --end is
 * inline-end-anchored, so the enter transform is a positive offset under
 * ltr and flips sign automatically under rtl via the direction-aware
 * transform: translateX in a dir-aware axis is NOT itself logical, so the
 * two placements author their own transform direction below). */
.bw-slide-over[data-bw-open] .bw-slide-over__panel {
  position: relative;
  z-index: var(--bw-z-modal);
  max-block-size: 100%;
  box-shadow:
    var(--bw-elevation-4),
    0 16px 36px -10px oklch(0 0 0 / 0.16);
  animation-duration: var(--bw-duration-normal);
  animation-timing-function: var(--bw-ease-out);
}
.bw-slide-over--end[data-bw-open] .bw-slide-over__panel {
  border-start-start-radius: var(--bw-radius-xl);
  border-end-start-radius: var(--bw-radius-xl);
  animation-name: bw-slide-over-in-end;
}
.bw-slide-over--start[data-bw-open] .bw-slide-over__panel {
  border-start-end-radius: var(--bw-radius-xl);
  border-end-end-radius: var(--bw-radius-xl);
  animation-name: bw-slide-over-in-start;
}
@keyframes bw-slide-over-in-end {
  from {
    transform: translateX(100%);
  }
  to {
    transform: none;
  }
}
[dir="rtl"] .bw-slide-over--end[data-bw-open] .bw-slide-over__panel {
  animation-name: bw-slide-over-in-end-rtl;
}
@keyframes bw-slide-over-in-end-rtl {
  from {
    transform: translateX(-100%);
  }
  to {
    transform: none;
  }
}
@keyframes bw-slide-over-in-start {
  from {
    transform: translateX(-100%);
  }
  to {
    transform: none;
  }
}
[dir="rtl"] .bw-slide-over--start[data-bw-open] .bw-slide-over__panel {
  animation-name: bw-slide-over-in-start-rtl;
}
@keyframes bw-slide-over-in-start-rtl {
  from {
    transform: translateX(100%);
  }
  to {
    transform: none;
  }
}
@media (prefers-reduced-motion: reduce) {
  .bw-slide-over[data-bw-open] .bw-slide-over__scrim,
  .bw-slide-over[data-bw-open] .bw-slide-over__panel {
    animation: none;
  }
}
/* Size modifiers: reuse the modal's own width ramp. */
.bw-slide-over--sm .bw-slide-over__panel {
  max-inline-size: var(--bw-size-max-width-modal-sm);
}
/* --md is the DOCUMENTED DEFAULT and the template emits it unconditionally, so
 * it needs a real rule: without one the default size resolved to the panel's
 * base width by accident rather than by declaration (the #120 defect class,
 * found in the 3.0.0 options audit). */
.bw-slide-over--md .bw-slide-over__panel {
  max-inline-size: var(--bw-size-max-width-modal-md);
}
.bw-slide-over--lg .bw-slide-over__panel {
  max-inline-size: var(--bw-size-max-width-modal-lg);
}
/* Sticky header: title plus the always-present close control, divided
 * from the scrolling body by a hairline. Density card padding scales the
 * chrome with the density axis, matching the modal. */
.bw-slide-over__header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--bw-space-3);
  padding: var(--bw-density-card-padding);
  border-block-end: var(--bw-size-border-hairline) solid var(--bw-color-border);
}
.bw-slide-over__title {
  margin: 0;
  font-family: var(--bw-text-heading-sm-family);
  font-size: var(--bw-text-heading-sm-size);
  font-weight: var(--bw-text-heading-sm-weight);
  line-height: var(--bw-text-heading-sm-line-height);
}
.bw-slide-over__close {
  flex-shrink: 0;
}
.bw-slide-over__body {
  flex: 1 1 auto;
  min-block-size: 0;
  overflow-y: auto;
  padding: var(--bw-density-card-padding);
  scrollbar-width: thin;
  scrollbar-color: var(--bw-color-border) transparent;
}
.bw-slide-over__footer {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--bw-space-3);
  padding: var(--bw-density-card-padding);
  border-block-start: var(--bw-size-border-hairline) solid var(--bw-color-border);
}
/* header_recipe / footer_recipe (Beat Phase E, #540): match modal region recipes. */
.bw-slide-over__header--muted,
.bw-slide-over--header-muted .bw-slide-over__header {
  background: var(--bw-color-surface-sunken);
}
.bw-slide-over__header--bordered,
.bw-slide-over--header-bordered .bw-slide-over__header {
  border-block-end-width: var(--bw-size-border-thick);
  border-block-end-color: var(--bw-color-border-strong);
}
.bw-slide-over--footer-muted .bw-slide-over__footer,
.bw-slide-over__footer--muted {
  background: var(--bw-color-surface-sunken);
}
.bw-slide-over--footer-actions .bw-slide-over__footer,
.bw-slide-over__footer--actions {
  justify-content: flex-end;
  gap: var(--bw-space-3);
}

/* --- Toast region + toast (0.9.0 interaction set, CBH-009..012, CMP-022/023) - */

/* Two pieces (per _toast_region.html / _toast.html). Expected markup:
 *
 *   <div id="bw-toast-region" class="bw-toast-region bw-toast-region--top-end"
 *        x-data="bwToastRegion()" aria-live="polite" data-bw-toast-region>
 *     <div id="..." class="bw-toast bw-toast--success"
 *          x-data='bwToast({ duration: "normal" })' data-bw-toast>
 *       <svg class="bw-icon bw-toast__icon" aria-hidden="true">...</svg>
 *       <p class="bw-toast__message">...</p>
 *       [<a class="bw-toast__action" href="...">label</a>]
 *       <button class="bw-toast__close bw-btn bw-btn--ghost bw-btn--sm bw-btn--icon-only">...</button>
 *     </div>
 *     <button class="bw-toast-region__more" hidden>+N more</button>
 *   </div>
 *
 * Toasts arrive server-rendered only (BR-BW-HTMX-007), via hx-swap-oob
 * afterbegin appends into the stable region id; the no-JS floor renders the
 * same feedback as an _alert.html banner (STA-008) and never a toast, so a
 * resting toast being invisible below breaks no floor: a toast only exists
 * on the JS path. Enter AND exit key on [data-bw-visible] (bwToast stamps
 * it a frame after insertion and drops it before removal), so the marker
 * carries both legs with the durations swapped per direction. */
.bw-toast-region {
  position: fixed;
  z-index: var(--bw-z-toast);
  display: flex;
  flex-direction: column;
  gap: var(--bw-space-2);
  inline-size: min(var(--bw-component-toast-max-width), calc(100vw - 2 * var(--bw-space-4)));
  /* Inert chrome: an empty region paints nothing and clicks fall through the
   * column; toasts and the collapse control restore their own hit-testing. */
  pointer-events: none;
}
/* Logical position variants (RTL mirrors for free, CBH-010). afterbegin
 * appends put the newest toast first in the DOM, so the bottom regions
 * reverse the column to keep the newest nearest the viewport edge. */
.bw-toast-region--top-end {
  inset-block-start: var(--bw-space-4);
  inset-inline-end: var(--bw-space-4);
}
.bw-toast-region--top-start {
  inset-block-start: var(--bw-space-4);
  inset-inline-start: var(--bw-space-4);
}
.bw-toast-region--bottom-end {
  inset-block-end: var(--bw-space-4);
  inset-inline-end: var(--bw-space-4);
  flex-direction: column-reverse;
}
.bw-toast-region--bottom-start {
  inset-block-end: var(--bw-space-4);
  inset-inline-start: var(--bw-space-4);
  flex-direction: column-reverse;
}
/* OOB delivery wrappers: hx-swap-oob="afterbegin:#bw-toast-region" inserts
 * the wrapper element itself, so any region child that is not a toast or
 * the collapse control is layout-transparent (the #bw-modal-root
 * display: contents idiom, BR-BW-HTMX-009). The toasts stay the flex items
 * and a wrapper whose toast has hidden (or has been dismissed) spaces
 * nothing. */
.bw-toast-region > :not([data-bw-toast]):not(.bw-toast-region__more) {
  display: contents;
}
/* The toast card: raised surface at the reserved elevation-5 toast row with
 * soft ambient under elevation (dark reset at the end of this file),
 * radius-xl, and an emphasis-width inline-start edge that the intent
 * variants recolour. Resting = pre-enter (or mid-exit): faded and pulled
 * toward the region's block edge; the base transition is the exit leg
 * (fast/in, MOT-007's clear-quickly tier). */
.bw-toast {
  display: flex;
  align-items: flex-start;
  gap: var(--bw-space-3);
  padding: var(--bw-space-3);
  pointer-events: auto;
  background: var(--bw-color-surface-raised);
  border: var(--bw-size-border-hairline) solid
    color-mix(in oklab, var(--bw-color-fg) 12%, var(--bw-color-surface-raised));
  border-inline-start: var(--bw-size-border-thick) solid var(--bw-color-border);
  border-radius: var(--bw-radius-xl);
  box-shadow:
    var(--bw-elevation-5),
    0 20px 44px -12px oklch(0 0 0 / 0.18);
  opacity: 0;
  transform: translateY(calc(-1 * var(--bw-space-2)));
  transition:
    opacity var(--bw-duration-fast) var(--bw-ease-in),
    transform var(--bw-duration-fast) var(--bw-ease-in);
}
/* The enter leg: settle into place at normal/out. */
.bw-toast[data-bw-visible] {
  opacity: 1;
  transform: none;
  transition:
    opacity var(--bw-duration-normal) var(--bw-ease-out),
    transform var(--bw-duration-normal) var(--bw-ease-out);
}
/* Bottom regions enter from the block-end edge instead; a vertical travel in
 * both cases because transforms are physical and an inline slide would
 * mirror wrongly under RTL. */
.bw-toast-region--bottom-end .bw-toast:not([data-bw-visible]),
.bw-toast-region--bottom-start .bw-toast:not([data-bw-visible]) {
  transform: translateY(var(--bw-space-2));
}
/* Intent edge and icon take the full-strength intent colour (the alert icon
 * tier); message ink stays fg, the accessible carrier (DESIGN.md 4.5). */
.bw-toast--success {
  border-inline-start-color: var(--bw-color-success);
}
.bw-toast--success .bw-toast__icon {
  color: var(--bw-color-success);
}
.bw-toast--warning {
  border-inline-start-color: var(--bw-color-warning);
}
.bw-toast--warning .bw-toast__icon {
  color: var(--bw-color-warning);
}
.bw-toast--danger {
  border-inline-start-color: var(--bw-color-danger);
}
.bw-toast--danger .bw-toast__icon {
  color: var(--bw-color-danger);
}
.bw-toast--info {
  border-inline-start-color: var(--bw-color-info);
}
.bw-toast--info .bw-toast__icon {
  color: var(--bw-color-info);
}
.bw-toast__icon {
  flex-shrink: 0;
}
.bw-toast__message {
  flex: 1 1 auto;
  margin: 0;
  font-size: var(--bw-text-body-sm-size);
  line-height: var(--bw-text-body-sm-line-height);
}
/* The one optional inline action (CMP-023): the skip-link's accent-on-surface
 * link voice, underlined so colour is not the only signal. */
.bw-toast__action {
  flex-shrink: 0;
  color: var(--bw-color-accent);
  font-size: var(--bw-text-body-sm-size);
  font-weight: var(--bw-font-weight-medium);
  line-height: var(--bw-text-body-sm-line-height);
  text-decoration: underline;
}
.bw-toast__close {
  flex-shrink: 0;
}
/* The stack-collapse control (CBH-011): a quiet pill in the caption voice,
 * floor-sized per the pagination-link pattern; toasts beyond the 3 newest
 * carry the hidden attribute and need no rule here. */
.bw-toast-region__more {
  align-self: center;
  pointer-events: auto;
  min-block-size: var(--bw-size-touch-target-min);
  min-inline-size: var(--bw-size-touch-target-min);
  padding-block: var(--bw-space-1);
  padding-inline: var(--bw-space-3);
  background: var(--bw-color-surface-raised);
  color: var(--bw-color-fg-muted);
  border: var(--bw-size-border-hairline) solid var(--bw-color-border);
  border-radius: var(--bw-radius-full);
  box-shadow: var(--bw-elevation-1);
  font: inherit;
  font-size: var(--bw-text-caption-size);
  font-weight: var(--bw-font-weight-medium);
  font-variant-numeric: tabular-nums;
  cursor: pointer;
  transition: var(--bw-transition-colors);
}
.bw-toast-region__more:hover {
  background-color: var(--bw-color-surface-sunken);
  color: var(--bw-color-fg);
}
/* reduced-motion: nothing explicit needed; the global floor collapses both
 * transition legs to an instant swap and a settled toast never misleads
 * (timers are JS-owned and unchanged, DESIGN.md 8.4). */

/* --- Combobox (0.9.0 interaction set, CBH-018..022, CMP-028/029) ------------- */

/* One markup, progressive enhancement (BR-BW-HTMX-008). Expected markup (per
 * _combobox.html, inside the forms/_field.html chrome):
 *
 *   <div class="bw-combobox" x-data='bwCombobox({...})' data-bw-combobox>
 *     <select class="bw-input bw-combobox__floor">...</select>
 *     <div class="bw-combobox__field" hidden>
 *       [<span class="bw-combobox__chip">label
 *          <button class="bw-combobox__chip-remove">...</button></span>]
 *       <input class="bw-input bw-combobox__input" role="combobox" ...>
 *       <ul class="bw-combobox__listbox" role="listbox" id="bw-listbox-...">
 *         <li class="bw-combobox__option" role="option" data-bw-value="...">...</li>
 *         [<li class="bw-combobox__empty">No matches</li>]
 *       </ul>
 *     </div>
 *   </div>
 *
 * The floor <select> IS the submitted control at all times; bwCombobox swaps
 * the hidden attributes at init and mirrors selection both ways, so with no
 * JS the native select simply works and nothing here styles it beyond its
 * own .bw-input class. The enhanced input composes .bw-input in its class
 * list too (compose, never duplicate); only combobox-specific physics live
 * here. */
.bw-combobox {
  position: relative;
}
/* Chips and the input share one wrapping row; the absolutely positioned
 * listbox anchors to it. The input trades .bw-input's full width for a
 * flexible run beside any chips. */
.bw-combobox__field {
  position: relative;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--bw-space-1);
}
.bw-combobox__input {
  flex: 1 1 var(--bw-component-menu-min-width);
  inline-size: auto;
  min-inline-size: 0;
}
/* The listbox mirrors the dropdown panel exactly (DESIGN.md reserved rows):
 * raised surface, elevation-3 at z-dropdown, radius-lg, menu min-width, the
 * light-theme top-light (dark reset at the end of this file), bw-fade-in-up
 * entrance. Exit is an instant state removal, the interaction set's recorded
 * deviation (the JS lane is state-driven and never sequences animation).
 * Width tracks the field so options align under the control. Closed is the
 * resting state: bwCombobox stamps [data-bw-open] on the root while the
 * popup is open (the modal's open-marker convention), and the display flip
 * restarts the entrance each open. */
.bw-combobox__listbox {
  display: none;
  position: absolute;
  inset-block-start: calc(100% + var(--bw-space-1));
  inset-inline-start: 0;
  inline-size: 100%;
  min-inline-size: var(--bw-component-menu-min-width);
  /* Scroll cap at roughly seven option rows; a length literal because the
   * sizing ramp has no step near it (the checkbox-size precedent). */
  max-block-size: 16rem;
  overflow-y: auto;
  scrollbar-width: thin;
  scrollbar-color: var(--bw-color-border) transparent;
  margin: 0;
  padding: var(--bw-space-1);
  list-style: none;
  background: var(--bw-color-surface-raised);
  border: var(--bw-size-border-hairline) solid var(--bw-color-border);
  border-radius: var(--bw-radius-lg);
  box-shadow: var(--bw-elevation-3), inset 0 1px 0 0 oklch(1 0 0 / 0.6);
  z-index: var(--bw-z-dropdown);
  animation: bw-fade-in-up var(--bw-duration-normal) var(--bw-ease-out);
  transition: var(--bw-transition-opacity);
}
.bw-combobox[data-bw-open] .bw-combobox__listbox {
  display: block;
}
/* STA-006: while the server-filter round trip is in flight the listbox dims
 * via htmx's own htmx-request class convention, whichever element htmx
 * stamps it on: the target listbox itself, or the requesting input beside
 * it. First consumer of --bw-component-htmx-indicator-opacity. */
.bw-combobox__listbox.htmx-request,
.bw-combobox__input.htmx-request ~ .bw-combobox__listbox {
  opacity: var(--bw-component-htmx-indicator-opacity);
}
/* Option rows mirror the dropdown items (body-sm, hover overlay, radius-md);
 * role="option" rows are not anchors, so the pointer cursor is explicit. */
.bw-combobox__option {
  display: flex;
  align-items: center;
  gap: var(--bw-space-2);
  padding: var(--bw-space-2) var(--bw-space-3);
  border-radius: var(--bw-radius-md);
  color: var(--bw-color-fg);
  font-size: var(--bw-text-body-sm-size);
  line-height: var(--bw-text-body-sm-line-height);
  cursor: pointer;
  transition: var(--bw-transition-colors);
}
.bw-combobox__option:hover {
  background-color: var(--bw-state-hover-overlay);
}
/* The aria-activedescendant row: focus never leaves the input, so the active
 * option is marked with [data-bw-active] (the tabs convention) and paints
 * the selected wash. A chosen option keeps its meaning when not active via
 * weight (its width cannot jitter: the panel width is fixed by the field). */
.bw-combobox__option[data-bw-active] {
  background-color: var(--bw-state-selected-bg);
}
.bw-combobox__option[aria-selected="true"] {
  font-weight: var(--bw-font-weight-medium);
}
.bw-combobox__empty {
  padding: var(--bw-space-2) var(--bw-space-3);
  color: var(--bw-color-fg-muted);
  font-size: var(--bw-text-body-sm-size);
  line-height: var(--bw-text-body-sm-line-height);
}
/* Selected-value chips (multiple mode, CMP-028): the selected wash at
 * radius-full, block-sized by the floor-height remove control (the
 * pagination-link touch pattern), so a chip row sits level with the
 * control beside it. */
.bw-combobox__chip {
  display: inline-flex;
  align-items: center;
  gap: var(--bw-space-1);
  padding-inline: var(--bw-space-3) var(--bw-space-1);
  background: var(--bw-state-selected-bg);
  border: var(--bw-size-border-hairline) solid var(--bw-color-border);
  border-radius: var(--bw-radius-full);
  font-size: var(--bw-text-body-sm-size);
  line-height: var(--bw-text-body-sm-line-height);
}
.bw-combobox__chip-remove {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-block-size: var(--bw-size-touch-target-min);
  min-inline-size: var(--bw-size-touch-target-min);
  padding: var(--bw-space-1);
  background: transparent;
  border: 0;
  border-radius: var(--bw-radius-full);
  color: var(--bw-color-icon-muted);
  cursor: pointer;
  transition: var(--bw-transition-colors);
}
.bw-combobox__chip-remove:hover {
  background-color: var(--bw-state-hover-overlay);
  color: var(--bw-color-fg);
}
.bw-combobox__chip-remove:active {
  background-color: var(--bw-state-active-overlay);
  transition-duration: var(--bw-duration-instant);
}

/* --- Dismissible exit (0.9.0 interaction set, CMP-010/012 adoption) ---------- */

/* bw_alert / bw_badge with dismissible=True: the close control ships with
 * the hidden attribute and bwDismissible reveals it at init (no dead
 * control on the floor), so there is no resting state to style here. The
 * exit keys on the [data-bw-dismissing] marker the component stamps as
 * dismiss() runs; DOM removal follows the fade (MOT-007's clear-quickly
 * tier). The 04 contract permits a slight block-size collapse; declined:
 * the markup has no wrapper for a jank-free grid-rows collapse, and
 * removal closes the gap in one reflow exactly as reduced motion does
 * (the global floor collapses this fade to an instant removal). */
.bw-alert[data-bw-dismissing],
.bw-badge[data-bw-dismissing] {
  opacity: 0;
  transition: opacity var(--bw-duration-fast) var(--bw-ease-in);
}
/* The badge's close control skips the bw-btn chrome (a control-height button
 * would dwarf the compact badge; the template notes the hit area is owned
 * here): an inline icon run with the overlay hover physics, inline content's
 * documented WCAG 2.5.8 carve-out. The alert's close IS a bw-btn and needs
 * nothing extra. */
.bw-badge__close {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: var(--bw-space-0-5);
  background: transparent;
  border: 0;
  border-radius: var(--bw-radius-full);
  color: inherit;
  cursor: pointer;
  transition: var(--bw-transition-colors);
}
.bw-badge__close:hover {
  background-color: var(--bw-state-hover-overlay);
}
.bw-badge__close:active {
  background-color: var(--bw-state-active-overlay);
  transition-duration: var(--bw-duration-instant);
}

/* --- Tooltip (0.12.0, #56, supersedes STA-015's title-only baseline) -------- */

/* The wrapper is an inline positioning context only: no visible chrome of
 * its own, so it never disturbs the trigger's own inline layout. */
.bw-tooltip {
  position: relative;
  display: inline-flex;
}
.bw-tooltip__bubble {
  position: absolute;
  z-index: var(--bw-z-tooltip);
  max-inline-size: var(--bw-component-tooltip-max-width);
  padding: var(--bw-space-1) var(--bw-space-2);
  background: var(--bw-color-surface-raised);
  color: var(--bw-color-fg);
  border: var(--bw-size-border-hairline) solid var(--bw-color-border);
  border-radius: var(--bw-radius-md);
  box-shadow: var(--bw-elevation-2);
  font-size: var(--bw-text-caption-size);
  font-weight: var(--bw-text-caption-weight);
  line-height: var(--bw-text-caption-line-height);
  white-space: normal;
  pointer-events: none;
}
/* [hidden] display floor is restated globally ([class^="bw-"][hidden] in
 * index.css); bwTooltip toggles the attribute, never a class, so the closed
 * state needs no rule here. */
.bw-tooltip--top .bw-tooltip__bubble {
  inset-block-end: calc(100% + var(--bw-space-1));
  inset-inline-start: 50%;
  transform: translateX(-50%);
}
.bw-tooltip--bottom .bw-tooltip__bubble {
  inset-block-start: calc(100% + var(--bw-space-1));
  inset-inline-start: 50%;
  transform: translateX(-50%);
}
/* Logical placements: inline-start/-end flip automatically under dir="rtl"
 * (BR-BW-TOK-005), unlike top/bottom which are block-axis and
 * direction-agnostic already. */
.bw-tooltip--start .bw-tooltip__bubble {
  inset-inline-end: calc(100% + var(--bw-space-1));
  inset-block-start: 50%;
  transform: translateY(-50%);
}
.bw-tooltip--end .bw-tooltip__bubble {
  inset-inline-start: calc(100% + var(--bw-space-1));
  inset-block-start: 50%;
  transform: translateY(-50%);
}
[data-theme="dark"] .bw-tooltip__bubble {
  box-shadow: var(--bw-elevation-3);
}

/* reduced-motion (interaction set): explicit caret overrides on top of the
 * global floor; a half-rotated caret reads as broken (DESIGN.md 8.4). The
 * entrance/exit keyframes need no explicit override: the global floor
 * collapses them to an instant state change, and neither a scrim nor a
 * settled panel misleads when frozen. */
@media (prefers-reduced-motion: reduce) {
  .bw-dropdown__caret,
  .bw-disclosure__caret,
  .bw-version-switch__caret {
    transition: none;
  }
}

/* --- Stepper (0.14.0, brickwork#59) ------------------------------------------ */

/* Purely structural (see _stepper.html): an <ol> of steps plus decorative
 * connector <li>s between them. Status is conveyed by the marker glyph AND
 * the state colour AND the visually-hidden text in the template, so colour
 * is never the only signal (WCAG 1.4.1). No JS, no open/close state, so no
 * reduced-motion override is needed here beyond the shared colour
 * transition token already gated at its own definition. */
.bw-stepper {
  display: flex;
  list-style: none;
  margin: 0;
  padding: 0;
}
.bw-stepper--horizontal {
  flex-direction: column;
}
.bw-stepper--vertical {
  flex-direction: column;
}
.bw-stepper__step {
  display: flex;
  align-items: center;
  gap: var(--bw-space-2);
}
.bw-stepper--vertical .bw-stepper__step {
  align-items: flex-start;
}
.bw-stepper--horizontal .bw-stepper__step {
  align-items: flex-start;
}
/* The connector: a decorative hairline between steps. Horizontal grows to
 * fill the row between markers; vertical is a short fixed rule aligned
 * under the marker column. */
.bw-stepper__connector {
  align-self: center;
  background-color: var(--bw-color-border);
}
.bw-stepper--horizontal .bw-stepper__connector {
  align-self: flex-start;
  inline-size: var(--bw-size-border-hairline);
  block-size: var(--bw-space-4);
  margin-inline-start: calc(var(--bw-size-control-height-sm) / 2 - var(--bw-size-border-hairline) / 2);
}
.bw-stepper--vertical .bw-stepper__connector {
  align-self: flex-start;
  inline-size: var(--bw-size-border-hairline);
  block-size: var(--bw-space-4);
  margin-inline-start: calc(var(--bw-size-control-height-sm) / 2 - var(--bw-size-border-hairline) / 2);
}
/* The marker: a numbered circle at rest, swapped for a check glyph once
 * complete (the template makes that swap, not this stylesheet). Colour
 * distinguishes complete/current/upcoming, but the glyph and the
 * visually-hidden text (in the template) carry the same information in a
 * non-colour form, so this is decoration on top of an already-accessible
 * signal, never the only one. */
.bw-stepper__marker {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  inline-size: var(--bw-size-control-height-sm);
  block-size: var(--bw-size-control-height-sm);
  border-radius: var(--bw-radius-full);
  border: var(--bw-size-border-thick) solid var(--bw-color-border);
  color: var(--bw-color-fg-muted);
  font-size: var(--bw-text-body-sm-size);
  font-weight: var(--bw-font-weight-medium);
  flex-shrink: 0;
}
.bw-stepper__step--complete .bw-stepper__marker {
  border-color: var(--bw-color-accent);
  background-color: var(--bw-color-accent);
  color: var(--bw-color-fg-on-accent);
}
.bw-stepper__step--current .bw-stepper__marker {
  border-color: var(--bw-color-accent);
  color: var(--bw-color-accent);
}
.bw-stepper__label {
  font-size: var(--bw-text-label-size);
  font-weight: var(--bw-text-label-weight);
  color: var(--bw-color-fg-muted);
}
.bw-stepper__step--current .bw-stepper__label {
  color: var(--bw-color-fg);
  font-weight: var(--bw-font-weight-semibold);
}
.bw-stepper__step--complete .bw-stepper__label {
  color: var(--bw-color-fg);
}

@media (width >= 48rem) {
  .bw-stepper--horizontal {
    flex-direction: row;
    align-items: flex-start;
  }

  .bw-stepper--horizontal .bw-stepper__step {
    align-items: center;
  }

  .bw-stepper--horizontal .bw-stepper__connector {
    align-self: center;
    flex: 1 1 auto;
    inline-size: auto;
    block-size: var(--bw-size-border-hairline);
    margin-inline: var(--bw-space-2);
  }
}

/* --- Wizard pattern (0.14.0, brickwork#59) ------------------------------------ */

/* patterns/wizard.html scaffolding only: the step content wrapper and the
 * back-link nav row. No form chrome of its own (the step's own <form>
 * lives inside wizard_step; its submit button is the step's own markup,
 * following the button-row recipe used elsewhere, e.g. _modal.html's
 * footer), matching the "structure only" doctrine of the other patterns. */
.bw-wizard__step {
  margin-block-start: var(--bw-density-section-gap);
}
.bw-wizard__nav {
  display: flex;
  align-items: center;
  gap: var(--bw-space-3);
  margin-block-start: var(--bw-density-section-gap);
}

/* --- Tag / chips input (0.13.0, BR-BW-INPUT-002, brickwork#57) -------------- */

/* One field, two visual rows: the chip run (built by bwTagInput; empty and
 * collapsed at rest, so an unenhanced or freshly-loaded field shows no empty
 * gap) above the floor text control. The floor keeps its own full .bw-input
 * chrome; this wrapper adds no border of its own so the no-JS floor renders
 * as a single ordinary text field. */
.bw-tag-input__chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--bw-space-1);
}
.bw-tag-input__chips:empty {
  display: none;
}
.bw-tag-input__chips:not(:empty) {
  margin-block-end: var(--bw-space-2);
}

/* --- File dropzone (0.13.0, BR-BW-INPUT-003, brickwork#57) ------------------ */

/* Chrome over a native <input type="file">, never replaced (accessibility +
 * no-JS floor): the input sits visually hidden but focusable/labelled inside
 * the styled dropzone label, so "click to browse" and keyboard activation
 * both work with zero JS. bwDropzone only ever ADDS presentation (drag-over
 * state, a selected-file-name list); it never swaps the control out. */
.bw-dropzone {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--bw-space-2);
  padding: var(--bw-density-section-gap);
  border: var(--bw-size-border-hairline) dashed var(--bw-color-border-control);
  border-radius: var(--bw-radius-lg);
  background: var(--bw-color-surface-sunken);
  color: var(--bw-color-fg-muted);
  text-align: center;
  cursor: pointer;
  transition: var(--bw-transition-colors);
}
.bw-dropzone:hover {
  border-color: color-mix(in oklab, var(--bw-color-border-control) 82%, var(--bw-color-fg));
}
/* Focus-visible on the wrapping label itself is meaningless (labels are not
 * focusable); the real target is the visually-hidden input inside it, so the
 * dropzone borrows its focus presentation via :has(), with the input's own
 * global :focus-visible ring still painting on the input's true (0-size)
 * box. Graceful no-op in engines without :has(): the input's own ring still
 * shows, just without this extra highlight. */
.bw-dropzone:has(.bw-dropzone__input:focus-visible) {
  border-color: var(--bw-color-accent);
  border-style: solid;
}
/* Drag-over state (bwDropzone stamps [data-bw-drag-over] on the root while a
 * file is dragged over it; CSS-owned, the module never animates). */
.bw-dropzone[data-bw-drag-over] {
  border-color: var(--bw-color-accent);
  border-style: solid;
  background: var(--bw-state-hover-overlay);
}
.bw-dropzone__icon {
  color: var(--bw-color-icon-muted);
}
.bw-dropzone__input {
  /* Visually hidden, NOT display:none / hidden: it must stay focusable and
   * keyboard-activatable (Space/Enter on a focused file input opens the
   * picker natively), which a display:none control cannot be. Same clip
   * pattern as .bw-visually-hidden, inlined here so the dropzone has no
   * dependency ordering on that utility class. */
  position: absolute;
  inline-size: var(--bw-space-px);
  block-size: var(--bw-space-px);
  margin: calc(var(--bw-space-px) * -1);
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}
.bw-dropzone__hint {
  font-size: var(--bw-text-caption-size);
  line-height: var(--bw-text-caption-line-height);
}
.bw-dropzone__files {
  margin: 0;
  padding: 0;
  list-style: none;
  text-align: start;
  inline-size: 100%;
}
.bw-dropzone__files:empty {
  display: none;
}
.bw-dropzone__file {
  display: flex;
  align-items: center;
  gap: var(--bw-space-2);
  padding-block: var(--bw-space-1);
  color: var(--bw-color-fg);
  font-size: var(--bw-text-body-sm-size);
  line-height: var(--bw-text-body-sm-line-height);
}

/* --- Styled native date/time inputs (0.13.0, BR-BW-INPUT-004, brickwork#57) - */

/* CSS-only: date/time widgets already carry .bw-input (_widget_css_class's
 * fallthrough branch), so height/border/focus ring match every other field
 * with no markup change. Only the browser-drawn calendar/clock picker
 * indicator needs its own rule, and only where the UA exposes one as a
 * styleable pseudo-element (Chromium/Blink; Firefox/Safari's native
 * indicator is not exposed for restyling, so this is a graceful enhancement,
 * never load-bearing: the unstyled UA indicator still works everywhere). */
input.bw-input[type="date"]::-webkit-calendar-picker-indicator,
input.bw-input[type="time"]::-webkit-calendar-picker-indicator,
input.bw-input[type="datetime-local"]::-webkit-calendar-picker-indicator {
  cursor: pointer;
  opacity: var(--bw-opacity-muted);
  transition: var(--bw-transition-opacity);
}
input.bw-input[type="date"]:hover::-webkit-calendar-picker-indicator,
input.bw-input[type="time"]:hover::-webkit-calendar-picker-indicator,
input.bw-input[type="datetime-local"]:hover::-webkit-calendar-picker-indicator {
  opacity: 1;
}
/* Firefox draws its own affordance INSIDE the field box; the field's own
 * padding-inline-end already gives it breathing room, no extra rule needed. */

/* --- Date picker chrome (icvoss/django-brickwork#628) ----------------------- */

/* Field binding and popover panel shell ONLY. No calendar engine, no
 * bw_date_picker Alpine behaviour (BR-BW-INPUT-004). The consumer fills
 * fields/panel; this file styles the labelled shell, range row, trigger
 * button and panel surface. Positioning context lives on the root so the
 * panel can sit under the field without the consumer inventing a wrapping
 * relative box. */
.bw-date-picker-chrome {
  position: relative;
  display: block;
}
.bw-date-picker-chrome__fields,
.bw-date-picker-chrome__range {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--bw-space-3);
}
.bw-date-picker-chrome__field {
  display: inline-flex;
  align-items: center;
  gap: var(--bw-space-2);
  position: relative;
}
.bw-date-picker-chrome__separator {
  color: var(--bw-color-fg-muted);
  user-select: none;
}
.bw-date-picker-chrome__trigger {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  inline-size: var(--bw-size-touch-target-min);
  block-size: var(--bw-size-touch-target-min);
  border: var(--bw-size-border-hairline) solid var(--bw-color-border-control);
  border-radius: var(--bw-radius-md);
  background: var(--bw-color-surface);
  color: var(--bw-color-fg-muted);
  cursor: pointer;
  flex: none;
}
.bw-date-picker-chrome__trigger:hover {
  background: var(--bw-state-hover-overlay);
}
.bw-date-picker-chrome__trigger:focus-visible {
  outline: var(--bw-focus-ring-width) var(--bw-focus-ring-style) var(--bw-color-focus-ring);
  outline-offset: var(--bw-focus-ring-offset);
}
.bw-date-picker-chrome__panel {
  position: absolute;
  z-index: var(--bw-z-dropdown);
  inset-block-start: calc(100% + var(--bw-space-2));
  inset-inline-start: 0;
  background: var(--bw-color-surface-raised);
  border: var(--bw-size-border-hairline) solid var(--bw-color-border);
  border-radius: var(--bw-radius-lg);
  box-shadow: var(--bw-elevation-3);
  padding: var(--bw-space-4);
  inline-size: max-content;
  max-inline-size: calc(100vw - var(--bw-space-8));
}
.bw-date-picker-chrome__panel[hidden] {
  display: none;
}

/* --- Sortable list (icvoss/django-brickwork#214) ----------------------------- */

/* Opt-in presentational hooks for bwSortable's two DOM-state attributes
 * (sortable.js owns setting/clearing them; this file owns what they look
 * like, matching the dropzone's drag-over precedent). Applying
 * .bw-sortable-item is optional: the behaviour itself has no required class,
 * only data-bw-sort-id, so a consumer who wants a different affordance can
 * skip this class entirely and style the two attributes themselves. */
.bw-sortable-item {
  cursor: grab;
  transition: var(--bw-transition-shadow), var(--bw-transition-opacity);
}
.bw-sortable-item[data-bw-dragging] {
  cursor: grabbing;
  opacity: var(--bw-opacity-muted);
  box-shadow: var(--bw-elevation-3);
}
/* The insertion indicator: a border on the side dragover's insertBefore/
 * insertAfter decision will actually land on, so the line always points at
 * where the drop will place the dragged item, not just "hovering here". */
.bw-sortable-item[data-bw-drag-over] {
  border-block-start: var(--bw-size-border-thick) solid var(--bw-color-accent);
}

/* --- Long-form prose floor (3.1.0, plan Phase 6 gate 2) --------------------- */

/* Blog posts and documentation pages are mostly text a CMS, a Markdown render,
 * or a rich-text field produced, so the markup arrives WITHOUT classes: bare
 * h2, ul, blockquote, code, table. Before this block the package styled none of
 * those (no element-level selector existed anywhere in frontend/src), so that
 * content fell back to UA defaults inside an otherwise themed page: Times-ish
 * headings, a 100%-wide table with no rules, and a blockquote with nothing but
 * a browser indent.
 *
 * .bw-prose is the opt-in wrapper that gives that content a floor. It is a
 * CLASS rather than element styling on the content region for two reasons: it
 * cannot bleed into a consumer's own markup, and a copied section (ADR-056)
 * carries its formatting with it. The shells apply it where long-form content
 * belongs, so the common case needs no opt-in at the call site.
 *
 * Everything below resolves through the text-role and colour tokens, so a brand
 * that retunes the type scale or palette retunes prose with it, and dark theme
 * needs no rules of its own. Descendants are matched with :where() so every rule
 * lands at zero specificity: a consumer's own class on any child always wins
 * without needing !important, which is the property that keeps this a FLOOR
 * rather than a style the consumer has to fight.
 */

.bw-prose {
  max-inline-size: var(--bw-size-max-width-prose);
  color: var(--bw-color-fg);
  font-family: var(--bw-text-body-md-family);
  font-size: var(--bw-text-body-md-size);
  line-height: var(--bw-text-body-lg-line-height);
}

/* Rhythm: space between flow siblings rather than margins on each element, so
 * the first and last child never introduce stray outer space the section band
 * then has to cancel. Every per-element reset below therefore leaves
 * margin-block-start alone and resets only margin-block-end and
 * margin-inline: it used to reset with the `margin` shorthand, which zeroes
 * margin-block-start too, and every reset ties this rule on specificity
 * ((0,1,0), :where() contributes none) while being authored later, so the
 * resets always won and the whole rhythm collapsed to 0 for any pair the
 * heading-specific rules below did not separately override
 * (icvoss/django-brickwork#461). */
.bw-prose > * + * {
  margin-block-start: var(--bw-space-5);
}
/* The first child still contributes no leading space of its own: the
 * comment above is the reason a reset can no longer supply this by zeroing
 * margin-block-start wholesale. */
.bw-prose > :first-child {
  margin-block-start: 0;
}

.bw-prose :where(h1, h2, h3, h4, h5, h6) {
  margin-block-end: 0;
  margin-inline: 0;
  color: var(--bw-color-fg);
  font-family: var(--bw-text-heading-lg-family);
  text-wrap: balance;
}

/* Headings pull toward the text they introduce: a heading belongs to what
 * follows it, so its leading space is larger than its trailing space. */
.bw-prose > :where(h2, h3, h4, h5, h6) + * {
  margin-block-start: var(--bw-space-3);
}
.bw-prose > * + :where(h2, h3, h4, h5, h6) {
  margin-block-start: var(--bw-space-10);
}

.bw-prose :where(h1) {
  font-family: var(--bw-text-heading-2xl-family);
  font-size: var(--bw-text-heading-2xl-size);
  font-weight: var(--bw-text-heading-2xl-weight);
  line-height: var(--bw-text-heading-2xl-line-height);
  letter-spacing: var(--bw-text-heading-2xl-tracking);
}
.bw-prose :where(h2) {
  font-family: var(--bw-text-heading-xl-family);
  font-size: var(--bw-text-heading-xl-size);
  font-weight: var(--bw-text-heading-xl-weight);
  line-height: var(--bw-text-heading-xl-line-height);
  letter-spacing: var(--bw-text-heading-xl-tracking);
}
.bw-prose :where(h3) {
  font-family: var(--bw-text-heading-lg-family);
  font-size: var(--bw-text-heading-lg-size);
  font-weight: var(--bw-text-heading-lg-weight);
  line-height: var(--bw-text-heading-lg-line-height);
}
.bw-prose :where(h4, h5, h6) {
  font-family: var(--bw-text-heading-md-family);
  font-size: var(--bw-text-heading-md-size);
  font-weight: var(--bw-text-heading-md-weight);
  line-height: var(--bw-text-heading-md-line-height);
}

.bw-prose :where(p) {
  margin-block-end: 0;
  margin-inline: 0;
  text-wrap: pretty;
}

/* A lede: the standfirst paragraph a post opens with. */
.bw-prose :where(.bw-prose__lede) {
  color: var(--bw-color-fg-muted);
  font-size: var(--bw-text-body-lg-size);
  line-height: var(--bw-text-body-lg-line-height);
}

.bw-prose :where(a) {
  color: var(--bw-color-accent);
  /* Underlined, not colour-only: WCAG 1.4.1 does not accept hue alone as the
   * signal that a run of text inside a paragraph is a link. */
  text-decoration: underline;
  text-underline-offset: 0.15em;
}
.bw-prose :where(a):hover {
  color: var(--bw-color-accent-hover);
}

.bw-prose :where(strong, b) {
  color: var(--bw-color-fg);
  font-weight: var(--bw-font-weight-semibold);
}

.bw-prose :where(ul, ol) {
  margin-block-end: 0;
  margin-inline: 0;
  padding-inline-start: var(--bw-space-6);
}
.bw-prose :where(li)::marker {
  color: var(--bw-color-fg-subtle);
}
.bw-prose :where(li) + :where(li) {
  margin-block-start: var(--bw-space-2);
}
/* A nested list belongs to its parent item, so it sits tighter than a sibling. */
.bw-prose :where(li) > :where(ul, ol) {
  margin-block-start: var(--bw-space-2);
}

.bw-prose :where(blockquote) {
  margin-block-end: 0;
  margin-inline: 0;
  padding-inline-start: var(--bw-space-5);
  border-inline-start: var(--bw-size-border-nav-marker) solid var(--bw-color-border-strong);
  color: var(--bw-color-fg-muted);
  font-style: italic;
}

.bw-prose :where(blockquote cite) {
  display: block;
  margin-block-start: var(--bw-space-2);
  font-style: normal;
  font-size: var(--bw-text-caption-size);
  line-height: var(--bw-text-caption-line-height);
  color: var(--bw-color-fg-muted);
}

.bw-prose :where(code) {
  padding: var(--bw-space-0-5) var(--bw-space-1-5);
  border-radius: var(--bw-radius-sm);
  background: var(--bw-color-surface-sunken);
  color: var(--bw-color-fg);
  font-family: var(--bw-text-code-family);
  /* Relative, so inline code tracks whichever role it is nested in (a heading,
   * a table cell, body text) rather than pinning to one absolute size. */
  font-size: 0.9em;
  /* An unbreakable token (a long identifier, a URL) has no wrap opportunity, so
   * without this it pushes its paragraph wider than a phone viewport. Applied
   * only to inline code: inside a <pre> the whole point is to preserve lines,
   * which the block's own scroll region handles instead. */
  overflow-wrap: break-word;
}

/* :not(.bw-code__pre) so a _code.html panel nested inside .bw-prose keeps ONE
 * scroll region rather than two (icvoss/django-brickwork#462). The panel's
 * scroller is .bw-code__body, which carries overflow-x AND the tabindex="0"
 * plus role="region" that make it reachable from a keyboard. Without this
 * exclusion the inner <pre> also matched the bare-element rule below, picked
 * up its own overflow-x, and became a second scrollable region with no
 * tabindex: axe's scrollable-region-focusable, failing at 375px on
 * examples/docs/article.html once a panel's content genuinely overflowed.
 * The comment on .bw-code__code below claimed both selectors "resolve to the
 * identical declarations" and that nesting "never doubles up"; that was true
 * of the code element and never of the pre, which the prose rule also gives a
 * border, background, padding and overflow the panel deliberately does not
 * want (its own chrome supplies them). Found by rendering, not by a suite:
 * the markup was correct and the gate only fires at a width where the sample
 * is actually wider than the column. */
.bw-prose :where(pre):not(.bw-code__pre) {
  margin-block-end: 0;
  margin-inline: 0;
  /* A grid/flex or block child sizes to its content by default, so a long code
   * line made the <pre> itself wider than the page instead of scrolling inside
   * it, and the whole document scrolled sideways on a phone. Capping the inline
   * size at the container makes overflow-x below actually take effect.
   * Found at 375px by the plan's Phase 6 gate 3. */
  max-inline-size: 100%;
  padding: var(--bw-space-4);
  border: var(--bw-size-border-hairline) solid var(--bw-color-border);
  border-radius: var(--bw-radius-lg);
  background: var(--bw-color-surface-sunken);
  /* A long code line scrolls its own block instead of widening the page: the
   * mobile-first floor ADR-057 section 1 requires. */
  overflow-x: auto;
  font-family: var(--bw-text-code-family);
  font-size: var(--bw-text-code-size);
  line-height: var(--bw-text-code-line-height);
}
/* A code element inside a pre is the block's own text: the inline chip
 * treatment (background, padding, radius) must not apply twice. */
.bw-prose :where(pre) :where(code) {
  padding: 0;
  border-radius: 0;
  background: none;
  font-size: inherit;
  /* display:block so the code is laid out as the pre's scrollable content
   * rather than as an inline box that paints past the scroll container and
   * drags the whole document wider. Without it the <pre> scrolls correctly in
   * isolation while the page still scrolls sideways on a phone: the pre
   * reported 327px wide with a 388px inline child spilling out of it.
   * The inline-code overflow-wrap above is deliberately NOT inherited here,
   * because preserving long lines is what a code block is for. */
  display: block;
  overflow-wrap: normal;
}

/* Tables scroll rather than overflow. .bw-data-table-wrap carries tabindex="0"
 * in _data_table.html so a keyboard user can reach the scroll region (axe
 * scrollable-region-focusable). Prose examples mirror the same pattern on
 * .bw-prose__table-wrap. */
.bw-prose :where(.bw-prose__table-wrap) {
  overflow-x: auto;
  /* contain: paint is load-bearing here, not an optimisation, and this is the
   * same lesson .bw-data-table-wrap learned on icvoss/django-brickwork#136.
   * overflow-x: auto alone does NOT stop a wide table dragging the whole
   * document sideways: an ancestor that still blows out (a consumer's own
   * wrapper, not only the .bw-card/.bw-section-stack cases already fixed) can
   * let this wrap's overflow paint outside its own box instead of clipping to
   * it. contain: paint guarantees this element is the clipping boundary for
   * its own descendants whatever an ancestor does. Measured at 300px and
   * 340px viewports on a five-column table: the document scrolled to 374px
   * without it and to the viewport width with it
   * (icvoss/django-brickwork#370). bw-prose is the general-purpose long-form
   * wrapper, so it is the surface most likely to hold a wide table. */
  contain: paint;
}

.bw-prose :where(table) {
  inline-size: 100%;
  border-collapse: collapse;
  font-size: var(--bw-text-body-sm-size);
  text-align: start;
}
.bw-prose :where(th, td) {
  padding: var(--bw-space-3) var(--bw-space-4);
  border-block-end: var(--bw-size-border-hairline) solid var(--bw-color-border);
  text-align: start;
  vertical-align: top;
}
.bw-prose :where(th) {
  color: var(--bw-color-fg);
  font-weight: var(--bw-font-weight-semibold);
}
.bw-prose :where(caption) {
  margin-block-end: var(--bw-space-2);
  color: var(--bw-color-fg-muted);
  font-size: var(--bw-text-caption-size);
  text-align: start;
}

.bw-prose :where(img, video) {
  display: block;
  max-inline-size: 100%;
  block-size: auto;
  border-radius: var(--bw-radius-lg);
}

.bw-prose :where(figure) {
  margin-block-end: 0;
  margin-inline: 0;
}
.bw-prose :where(figcaption) {
  margin-block-start: var(--bw-space-2);
  color: var(--bw-color-fg-muted);
  font-size: var(--bw-text-caption-size);
}

.bw-prose :where(hr) {
  margin-block: var(--bw-space-10);
  border: 0;
  border-block-start: var(--bw-size-border-hairline) solid var(--bw-color-border);
}

.bw-prose :where(dt) {
  font-weight: var(--bw-font-weight-semibold);
}
.bw-prose :where(dd) {
  margin-inline-start: 0;
  color: var(--bw-color-fg-muted);
}

/* Prose / heading anchor ---------------------------------------------------- */

/* A permalink affordance next to a heading that already carries an id, so a
 * reader can copy a link straight to that section. It lives here rather than
 * in marketing.css for the same reason as the callout and code blocks below:
 * it composes with the .bw-prose floor (a docs or blog heading), never
 * surface chrome, so ADR-090's amended rule (a class's family follows its
 * KIND, not the file its rules happen to sit in) makes it family-neutral and
 * legal on the docs shell as well as marketing. icvoss/django-brickwork#457.
 *
 * Contract: the package styles the affordance, the consumer supplies the
 * heading id and the accessible name. This deliberately does NOT generate
 * heading ids or parse content; that is the content pipeline's job, the same
 * boundary ADR-091 draws for the table of contents (icvoss/django-brickwork
 * #413, a separate, larger primitive that this is not). The markup shape:
 *
 *   <h2 id="installation">
 *     Installation
 *     <a class="bw-prose__anchor" href="#installation" aria-label="Link to
 *        the Installation section">#</a>
 *   </h2>
 *
 * The consumer writes aria-label from the heading's own text, because CSS has
 * no way to read it: a bare "Link" repeated at every heading fails WCAG 2.4.4
 * (link purpose) the moment a screen reader user tabs through two of them.
 * "Link to the X section" is the shape; the X is the one thing only the
 * consumer's template knows.
 *
 * Visibility state: opacity: 0 at rest, not display: none or
 * visibility: hidden. Both of those pull the element out of the accessibility
 * tree AND out of the tab order, so a keyboard user could never reach it at
 * all; opacity does neither; the anchor stays focusable and present, just
 * not painted. It is then revealed two ways, and both must be there:
 *   - on :hover of the whole heading, for a pointer user scanning the page;
 *   - on the anchor's own :focus-visible, for a keyboard user tabbing
 *     through. Without this second rule a keyboard user can Tab onto the
 *     link and land on a focus ring around nothing, which is worse than not
 *     having the affordance at all; this is the exact trap #457 names.
 *
 * Touch has no hover state, so gating reveal on hover alone would leave a
 * touch user with an affordance they can never see and therefore never
 * activate: nothing to press-and-hold on, nothing to discover by exploring
 * the page. @media (hover: hover) scopes the "hidden until hovered" rule to
 * devices that actually have a hover concept; everywhere else (the query's
 * negative case, touch included) the anchor stays permanently visible, which
 * is the only way a touch user can ever copy a section link.
 *
 * Opacity is the only property transitioning, and the global unconditional
 * prefers-reduced-motion floor (index.css) already caps every transition
 * duration to near-zero, so no separate media query is needed here. */

.bw-prose :where(h1, h2, h3, h4, h5, h6) :where(.bw-prose__anchor) {
  margin-inline-start: var(--bw-space-2);
  color: var(--bw-color-fg-subtle);
  font-family: var(--bw-text-body-md-family);
  font-weight: var(--bw-font-weight-normal);
  text-decoration: none;
  opacity: 1;
  transition: var(--bw-transition-opacity), var(--bw-transition-colors);
}

@media (hover: hover) {
  .bw-prose :where(h1, h2, h3, h4, h5, h6) :where(.bw-prose__anchor) {
    opacity: 0;
  }
  .bw-prose :where(h1, h2, h3, h4, h5, h6):hover :where(.bw-prose__anchor) {
    opacity: 1;
  }
}

.bw-prose :where(.bw-prose__anchor):focus-visible {
  opacity: 1;
}

.bw-prose :where(.bw-prose__anchor):hover {
  color: var(--bw-color-accent);
}

/* Content / callout -------------------------------------------------------- */

/* The callout composes with the .bw-prose floor above it (a note pulled out
 * of the flow of a docs or blog page), which is why it lives here rather
 * than in marketing.css: it is a content primitive, not surface chrome, and
 * ADR-090's 2026-08-31 amendment classes a class's family by its KIND, not
 * by the file its rules happened to sit in. Family-neutral, so legal on the
 * docs shell as well as marketing. icvoss/django-brickwork#452. */

.bw-callout {
  display: flex;
  flex-direction: column;
  gap: var(--bw-space-2);
  padding: var(--bw-space-5);
  border-inline-start: var(--bw-size-border-nav-marker) solid var(--bw-color-accent);
  border-radius: var(--bw-radius-md);
  background: var(--bw-color-accent-subtle);
  max-inline-size: var(--bw-size-max-width-prose);
}

.bw-callout + .bw-callout {
  margin-block-start: var(--bw-space-5);
}

.bw-callout__title {
  margin: 0;
  font-size: var(--bw-text-label-size);
  font-weight: var(--bw-font-weight-semibold);
  /* The kind is carried by this TEXT, not by the tint alone: colour-only
   * signalling fails WCAG 1.4.1. */
  color: var(--bw-color-fg);
}

/* The body opts out of .bw-prose's own measure: the callout already sets one,
 * and nesting two would narrow the text twice. */
.bw-callout__body {
  max-inline-size: none;
}

/* --note is the DEFAULT kind and the example emits it explicitly, so it needs a
 * real rule rather than relying on the base (the icvoss/django-brickwork#120
 * defect class: a documented option value whose CSS does not exist). It
 * restates the base treatment, which also gives the other kinds something
 * symmetrical to sit against. */
.bw-callout--note {
  border-inline-start-color: var(--bw-color-accent);
  background: var(--bw-color-accent-subtle);
}

.bw-callout--warning {
  border-inline-start-color: var(--bw-color-warning);
  background: var(--bw-color-warning-subtle);
}

.bw-callout--success {
  border-inline-start-color: var(--bw-color-success);
  background: var(--bw-color-success-subtle);
}

.bw-callout--info {
  border-inline-start-color: var(--bw-color-info);
  background: var(--bw-color-info-subtle);
}

.bw-callout--danger {
  border-inline-start-color: var(--bw-color-danger);
  background: var(--bw-color-danger-subtle);
}

.bw-callout--neutral {
  border-inline-start-color: var(--bw-color-border-strong);
  background: var(--bw-color-surface-sunken);
}

/* Content / code display ---------------------------------------------------- */

/* A content primitive, exactly like the callout above and for the same
 * reason: it composes with the .bw-prose floor (a labelled code panel
 * pulled out of the flow of a docs or blog page), never surface chrome, so
 * ADR-090's amended rule (a class's family follows its kind, not the file
 * its rules happen to sit in) puts it here rather than in marketing.css.
 * Family-neutral: legal on the docs shell as well as marketing.
 * icvoss/django-brickwork#259. */

.bw-code {
  display: flex;
  flex-direction: column;
  border: var(--bw-size-border-hairline) solid var(--bw-color-border);
  border-radius: var(--bw-radius-lg);
  background: var(--bw-color-surface-sunken);
  /* Same trap .bw-prose :where(pre) already solved (components.css:4212): a
   * block child sizes to its widest descendant by default, so an unbroken
   * code line pushed this panel itself wider than its container and the
   * whole document scrolled sideways on a phone. Capping the inline size at
   * the container is what makes __body's own overflow-x actually take
   * effect instead of being masked by an oversized parent. */
  max-inline-size: 100%;
}

/* The header bar is optional (filename and/or language label, plus the copy
 * control) and must survive being absent entirely: _code.html only emits
 * this wrapper when it has something to put in it, so an unfilled header
 * never leaves empty chrome sitting above the code body. */
.bw-code__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--bw-space-3);
  padding: var(--bw-space-2) var(--bw-space-4);
  border-block-end: var(--bw-size-border-hairline) solid var(--bw-color-border);
  background: var(--bw-color-surface);
}

/* Groups filename and language so the copy control sits at the header's
 * far end regardless of whether one or both labels are present; the
 * overflow/shrink behaviour belongs to __filename below, not this wrapper. */
.bw-code__meta {
  display: flex;
  align-items: baseline;
  gap: var(--bw-space-2);
  min-inline-size: 0;
}

.bw-code__filename {
  margin: 0;
  /* A flex item's default min-inline-size is auto (its content's own
   * width), which overrides overflow/text-overflow entirely; this is what
   * makes the ellipsis below actually trigger inside .bw-code__meta's
   * fixed-width row instead of pushing the header wider. */
  min-inline-size: 0;
  overflow: hidden;
  color: var(--bw-color-fg);
  font-family: var(--bw-text-code-family);
  font-size: var(--bw-text-caption-size);
  white-space: nowrap;
  text-overflow: ellipsis;
}

.bw-code__language {
  margin: 0;
  color: var(--bw-color-fg-muted);
  font-size: var(--bw-text-caption-size);
  font-weight: var(--bw-font-weight-medium);
  /* The language is conveyed as this TEXT, never colour alone (WCAG 1.4.1),
   * the same principle the callout's kind label states above: no rule here
   * carries a colour axis a consumer could plausibly mistake for the signal
   * itself. */
  white-space: nowrap;
}

/* Reuses .bw-btn's own ghost/icon-only/sm chrome (the _modal.html/
 * _slide_over.html/_toast.html close-button precedent, components.css:2996
 * region): a copy control is a button, not a bespoke widget, so it composes
 * the existing control tokens rather than re-deriving them. This class only
 * adds the layout the header bar needs, never new colour or sizing rules. */
.bw-code__copy {
  flex-shrink: 0;
}

/* The scroll region: a real overflow container independent of .bw-prose, so
 * the panel renders correctly standalone. contain: paint is load-bearing
 * here, not an optimisation (the same lesson .bw-prose__table-wrap and
 * .bw-data-table-wrap already learned on icvoss/django-brickwork#136/#370):
 * overflow-x: auto alone does not stop a wide ancestor's own overflow
 * painting past this box's edge, so an ancestor a consumer wraps this panel
 * in can still drag the document sideways without it. */
.bw-code__body {
  overflow-x: auto;
  contain: paint;
}

.bw-code__pre {
  margin: 0;
  padding: var(--bw-space-4);
  font-family: var(--bw-text-code-family);
  font-size: var(--bw-text-code-size);
  line-height: var(--bw-text-code-line-height);
}

/* Laid out as the pre's own scrollable content rather than as an inline box
 * that can paint past __body's scroll boundary: the exact double-measure
 * trap .bw-prose :where(pre) :where(code) already documents at
 * components.css:4231. For THIS element, the code, a consumer nesting the
 * panel inside .bw-prose gets the same declarations from either selector, so
 * nesting does not double up; standalone use is never dependent on .bw-prose
 * being present at all, which is the whole point of shipping __pre/__code
 * here rather than relying on the prose floor's own bare-element selectors.
 *
 * That equivalence held for the code element and NEVER for the pre, though
 * this comment used to claim it for both (icvoss/django-brickwork#462). The
 * prose rule gives a bare <pre> a border, background, padding and overflow-x
 * that a panel does not want, since .bw-code supplies its own chrome, and the
 * overflow-x in particular turned the nested <pre> into a second scrollable
 * region with no tabindex, next to the properly focusable .bw-code__body.
 * .bw-prose :where(pre) now excludes .bw-code__pre for that reason; see its
 * own comment above. */
.bw-code__code {
  display: block;
  overflow-wrap: normal;
  color: var(--bw-color-fg);
  background: none;
}

/* Content / pager (icvoss/django-brickwork#460) ----------------------------- */

/* A content primitive, exactly like the callout and code panel above and for
 * the same reason: its subject is the CONTENT's own sequence (this document's
 * predecessor and successor), not a surface, so ADR-090's amended rule puts
 * it here rather than in marketing.css. Family-neutral: legal on the docs
 * shell, the marketing shell, and anywhere else a sequential article lives.
 *
 * .bw-pager is a flex row that wraps: two slots side by side at wide
 * viewports, stacked at narrow ones, with no breakpoint of its own (the
 * wrap happens whenever the row runs out of inline space, matching
 * .bw-bulk-actions-bar's own wrap+space-between idiom at
 * components.css:1321-1323). justify-content: space-between only positions
 * the pair; the single-link degradation below is a separate, deliberate
 * rule, because space-between alone would leave a lone slot pinned to the
 * start regardless of which one it is.
 *
 * The first and last page of every sequence renders exactly one slot
 * (previous is absent on page 1, next is absent on the last page), so this
 * is the common case, not an edge case: a lone "Next" must still sit at the
 * inline end, where a reader expects the forward control, and a lone
 * "Previous" must still sit at the inline start. margin-inline-start: auto
 * on __next does this with no modifier class or JS: it pushes __next to the
 * end whether or not __previous is present, and a solitary __previous needs
 * no rule at all, since a flex row's default main-axis placement is already
 * flex-start. */
.bw-pager {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: var(--bw-space-3) var(--bw-space-6);
  margin-block-start: var(--bw-density-stack-gap);
}

/* The link itself: visibly a link at the docs footer's secondary tier,
 * which is the whole defect this fixes (icvoss/django-brickwork#460). The
 * footer's own rule (shell.css's
 * .bw-docs-layout__footer :where(a):not(.bw-btn)) sets fg-muted and
 * text-decoration: none at ZERO specificity via :where(), deliberately,
 * because a docs footer's links are secondary chrome. This class selector
 * is (0,1,0) and beats that cleanly with no !important, which is the
 * intended mechanism (icvoss/django-brickwork#460's own evidence section).
 *
 * The fix is deliberately NOT "restore the full prose link tier"
 * (.bw-prose :where(a), accent colour): that would stop this reading as
 * footer chrome at all. Instead it keeps fg-muted (the secondary tier) and
 * adds the underline .bw-prose :where(a) already uses for the same WCAG
 * 1.4.1 reason, colour alone is never the signal that text is a link, just
 * applied to the muted colour instead of the accent one. Muted-and-underlined
 * reads as "a link, but a quiet one", which is exactly the footer's tier. */
.bw-pager__link {
  display: inline-flex;
  align-items: center;
  gap: var(--bw-space-2);
  min-block-size: 1.5rem;
  color: var(--bw-color-fg-muted);
  text-decoration: none;
  transition: var(--bw-transition-colors);
}
.bw-pager__link:hover {
  color: var(--bw-color-fg);
}

/* Matches the docs footer's own coarse-pointer bump exactly (shell.css's
 * .bw-docs-layout__footer :where(a):not(.bw-btn), icvoss/django-brickwork#242):
 * a fine-pointer desktop render stays at the 1.5rem floor above, so the
 * pager's layout does not shift, and only a coarse-pointer device gets the
 * WCAG 2.5.5 44px hit area. */
@media (pointer: coarse) {
  .bw-pager__link {
    min-block-size: var(--bw-size-touch-target-min);
  }
}

/* margin-inline-start: auto is the single-link mechanism the block comment
 * above describes: it pushes __next to the inline end whether or not
 * __previous is present, so a lone "Next" (the last page of a sequence)
 * still sits where a reader expects the forward control, with no modifier
 * class or JS needed. row-reverse trails the chevron behind the text so it
 * still sits nearest the inline end it points toward, and text-align: end
 * right-aligns the two-line text block against that same edge. */
.bw-pager__next {
  margin-inline-start: auto;
  flex-direction: row-reverse;
  text-align: end;
}

.bw-pager__link > span {
  display: flex;
  flex-direction: column;
  gap: var(--bw-space-1);
}

/* The direction label ("Previous" / "Next"): a small caption sitting above
 * the destination title. This is presentational reinforcement only, never
 * the sole carrier of direction: the anchor's own visible text already
 * states "Previous: <title>" / "Next: <title>" (see _pager.html), so
 * direction survives being read out of context even with CSS disabled
 * (WCAG 2.4.4). fg-subtle at caption size failed WCAG 1.4.3 (light 4.09:1,
 * dark 2.36:1 against 4.5:1; caption size is below the large-text threshold
 * so no exemption applies, icvoss/django-brickwork#460 axe gate). fg-muted
 * is the same tier .bw-pagination__status uses at this size (this rule's
 * own precedent), and is contrast-safe there; this rule now matches it
 * exactly rather than a step further. */
.bw-pager__direction {
  color: var(--bw-color-fg-muted);
  font-size: var(--bw-text-caption-size);
  font-weight: var(--bw-text-caption-weight);
  line-height: var(--bw-text-caption-line-height);
}

/* The underline sits on the TITLE, not on the anchor. The anchor stacks a
 * caption above a title in a column, so an underline on the anchor paints
 * one run per line box: the small "Next:" caption gained its own short
 * underline and read as a second, truncated link. Underlining the title
 * alone still satisfies WCAG 1.4.1 (colour is never the sole signal that
 * this is a link) while letting the caption read as the label it is. */
.bw-pager__title {
  font-size: var(--bw-text-body-md-size);
  text-decoration: underline;
  text-underline-offset: 0.15em;
}

/* --- Divider (Beat Phase B, #542) ------------------------------------------ */

.bw-divider {
  border: 0;
  border-block-start: var(--bw-size-border-hairline) solid var(--bw-color-border);
  margin: 0;
  inline-size: 100%;
}

/* muted is the documented default tone (#120/#130): emit the class with a
 * real rule that restates the base border colour, matching .bw-btn--md. */
.bw-divider--tone-muted {
  border-block-start-color: var(--bw-color-border);
}

.bw-divider--tone-strong {
  border-block-start-color: var(--bw-color-border-strong);
}

.bw-divider--spacing-sm {
  margin-block: var(--bw-space-3);
}

.bw-divider--spacing-md {
  margin-block: var(--bw-space-5);
}

.bw-divider--spacing-lg {
  margin-block: var(--bw-space-8);
}

.bw-divider--labelled {
  display: flex;
  align-items: center;
  gap: var(--bw-space-3);
  border: 0;
}

.bw-divider--labelled .bw-divider__rule {
  flex: 1 1 auto;
  block-size: 0;
  border-block-start: var(--bw-size-border-hairline) solid var(--bw-color-border);
}

.bw-divider--labelled.bw-divider--tone-muted .bw-divider__rule {
  border-block-start-color: var(--bw-color-border);
}

.bw-divider--labelled.bw-divider--tone-strong .bw-divider__rule {
  border-block-start-color: var(--bw-color-border-strong);
}

.bw-divider__label {
  flex: 0 0 auto;
  color: var(--bw-color-fg-muted);
  font-size: var(--bw-text-caption-size);
  font-weight: var(--bw-font-weight-medium);
  line-height: var(--bw-text-caption-line-height);
  white-space: nowrap;
}

.bw-divider--align-start .bw-divider__rule:first-child {
  flex: 0 0 var(--bw-space-6);
}

/* --- Avatar (Beat Phase B, #542) ------------------------------------------- */
/* Box size reuses shipped control-height tokens (DESIGN.md §10 keeps
   --bw-avatar-size-* reserved until a consumer needs a distinct scale). */

.bw-avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  inline-size: var(--bw-size-control-height-md);
  block-size: var(--bw-size-control-height-md);
  overflow: hidden;
  border-radius: var(--bw-radius-full);
  background: var(--bw-color-surface-sunken);
  color: var(--bw-color-fg-muted);
  border: var(--bw-size-border-hairline) solid color-mix(in oklab, var(--bw-color-fg) 12%, var(--bw-color-surface));
  vertical-align: middle;
  flex-shrink: 0;
}

.bw-avatar--size-sm {
  inline-size: var(--bw-size-control-height-sm);
  block-size: var(--bw-size-control-height-sm);
}

.bw-avatar--size-md {
  inline-size: var(--bw-size-control-height-md);
  block-size: var(--bw-size-control-height-md);
}

.bw-avatar--size-lg {
  inline-size: var(--bw-size-control-height-lg);
  block-size: var(--bw-size-control-height-lg);
}

/* circle is the documented default shape (#120/#130): restates the base
 * full radius so the class is not a broken promise in markup. */
.bw-avatar--shape-circle {
  border-radius: var(--bw-radius-full);
}

.bw-avatar--shape-square {
  border-radius: var(--bw-radius-md);
}

.bw-avatar__image {
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
}

.bw-avatar__initials {
  font-size: calc(var(--bw-size-control-height-md) * 0.375);
  font-weight: var(--bw-font-weight-semibold);
  line-height: 1;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  max-inline-size: 100%;
  overflow: hidden;
}

.bw-avatar--size-sm .bw-avatar__initials {
  font-size: calc(var(--bw-size-control-height-sm) * 0.375);
}

.bw-avatar--size-lg .bw-avatar__initials {
  font-size: calc(var(--bw-size-control-height-lg) * 0.375);
}

.bw-avatar--overflow {
  background: var(--bw-color-surface-raised);
  color: var(--bw-color-fg);
  border-color: var(--bw-color-border);
}

.bw-avatar-group {
  display: inline-flex;
  align-items: center;
}

.bw-avatar-group .bw-avatar {
  box-shadow: 0 0 0 var(--bw-size-border-thick) var(--bw-color-surface);
}

.bw-avatar-group .bw-avatar + .bw-avatar {
  margin-inline-start: calc(var(--bw-space-2) * -1);
}

/* --- Chip / filter chip (Beat Phase B, #542) ------------------------------- */

.bw-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--bw-space-1);
  padding-block: var(--bw-space-1);
  padding-inline: var(--bw-space-3);
  border: var(--bw-size-border-hairline) solid transparent;
  border-radius: var(--bw-radius-full);
  font-size: var(--bw-text-caption-size);
  font-weight: var(--bw-font-weight-medium);
  line-height: var(--bw-text-caption-line-height);
  background: var(--bw-color-surface-sunken);
  color: var(--bw-color-fg-muted);
  text-decoration: none;
  cursor: default;
  transition: var(--bw-transition-colors);
}

button.bw-chip,
a.bw-chip {
  cursor: pointer;
  font: inherit;
}

.bw-chip--size-sm {
  padding-block: var(--bw-space-0-5);
  padding-inline: var(--bw-space-2);
}

/* md is the documented default chip size (#120/#130): restates the base
 * padding so the emitted class has a named rule. */
.bw-chip--size-md {
  padding-block: var(--bw-space-1);
  padding-inline: var(--bw-space-3);
}

.bw-chip--neutral {
  background: var(--bw-color-surface-sunken);
  color: var(--bw-color-fg-muted);
  border-color: transparent;
}

.bw-chip--success {
  background: var(--bw-color-success-subtle);
  color: var(--bw-color-success-fg);
  border-color: var(--bw-color-success-border);
}

.bw-chip--warning {
  background: var(--bw-color-warning-subtle);
  color: var(--bw-color-warning-fg);
  border-color: var(--bw-color-warning-border);
}

.bw-chip--danger {
  background: var(--bw-color-danger-subtle);
  color: var(--bw-color-danger-fg);
  border-color: var(--bw-color-danger-border);
}

.bw-chip--info {
  background: var(--bw-color-info-subtle);
  color: var(--bw-color-info-fg);
  border-color: var(--bw-color-info-border);
}

.bw-chip--selected {
  box-shadow: inset 0 0 0 var(--bw-size-border-thick) var(--bw-color-accent);
  color: var(--bw-color-fg);
}

.bw-chip__close {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  margin-inline-end: calc(var(--bw-space-1) * -1);
  padding: var(--bw-space-0-5);
  background: transparent;
  border: 0;
  border-radius: var(--bw-radius-full);
  color: inherit;
  cursor: pointer;
  opacity: 0.75;
}

.bw-chip__close:hover,
.bw-chip__close:focus-visible {
  opacity: 1;
  background: var(--bw-state-hover-overlay);
}

/* --- Button group (Beat Phase B, #542) ------------------------------------- */

.bw-button-group {
  display: inline-flex;
  flex-wrap: nowrap;
  align-items: stretch;
  vertical-align: middle;
}

/* Size modifiers are markers on the group root; the item buttons carry the
 * real sm/md/lg geometry via .bw-btn--*. Restate align-items so each
 * documented size still has a named rule (#120/#130). */
.bw-button-group--size-sm,
.bw-button-group--size-md,
.bw-button-group--size-lg {
  align-items: stretch;
}

.bw-button-group__item {
  position: relative;
}

.bw-button-group--attached .bw-button-group__item {
  border-radius: 0;
}

.bw-button-group--attached .bw-button-group__item:first-child {
  border-start-start-radius: var(--bw-radius-md);
  border-end-start-radius: var(--bw-radius-md);
}

.bw-button-group--attached .bw-button-group__item:last-child {
  border-start-end-radius: var(--bw-radius-md);
  border-end-end-radius: var(--bw-radius-md);
}

.bw-button-group--attached .bw-button-group__item:not(:first-child) {
  margin-inline-start: calc(var(--bw-size-border-hairline) * -1);
}

.bw-button-group--attached .bw-button-group__item:hover,
.bw-button-group--attached .bw-button-group__item:focus-visible,
.bw-button-group--attached .bw-button-group__item--selected {
  z-index: 1;
}

.bw-button-group--segmented {
  gap: var(--bw-space-0-5);
  padding: var(--bw-space-0-5);
  background: var(--bw-color-surface-sunken);
  border: var(--bw-size-border-hairline) solid color-mix(in oklab, var(--bw-color-fg) 12%, var(--bw-color-surface));
  border-radius: var(--bw-radius-lg);
  box-shadow: var(--bw-elevation-1), 0 1px 2px color-mix(in oklab, var(--bw-color-fg) 6%, transparent);
}

.bw-button-group--segmented .bw-button-group__item {
  border-color: transparent;
  background: transparent;
  box-shadow: none;
}

.bw-button-group--segmented .bw-button-group__item--selected,
.bw-button-group--segmented .bw-button-group__item[aria-checked="true"] {
  background: var(--bw-color-surface);
  color: var(--bw-color-fg);
  box-shadow: var(--bw-elevation-1);
}

/* --- List item / media row (Beat Phase B, #542) ---------------------------- */

.bw-list-item {
  display: grid;
  gap: var(--bw-space-4);
  align-items: start;
  padding-block: var(--bw-space-4);
  border-block-end: var(--bw-size-border-hairline) solid var(--bw-color-border);
}

.bw-list-item:last-child {
  border-block-end: 0;
}
/* density="compact" (Beat Phase C): denser ops / filter-result lists. */
.bw-list-item--density-compact {
  padding-block: var(--bw-space-2);
  gap: var(--bw-space-3);
}
@media (width >= 40rem) {
  .bw-list-item--density-compact {
    grid-template-columns: 8rem 1fr;
    gap: var(--bw-space-4);
  }
}

@media (width >= 40rem) {
  .bw-list-item {
    grid-template-columns: 12rem 1fr;
    gap: var(--bw-space-6);
  }
}

.bw-list-item__media {
  inline-size: 100%;
  block-size: auto;
  aspect-ratio: 3 / 2;
  object-fit: cover;
  border-radius: var(--bw-radius-lg);
  border: var(--bw-size-border-hairline) solid color-mix(in oklab, var(--bw-color-fg) 10%, var(--bw-color-surface));
}

.bw-list-item__body {
  display: flex;
  flex-direction: column;
  gap: var(--bw-space-2);
  align-items: flex-start;
}

.bw-list-item__title {
  margin: 0;
  font-family: var(--bw-text-heading-sm-family);
  font-size: var(--bw-text-heading-sm-size);
  font-weight: var(--bw-text-heading-sm-weight);
  line-height: var(--bw-text-heading-sm-line-height);
}

.bw-list-item__link {
  display: block;
  min-block-size: 1.5rem;
  color: inherit;
  text-decoration: none;
  transition: var(--bw-transition-colors);
}

.bw-list-item__link:hover,
.bw-list-item__link:focus-visible {
  color: var(--bw-color-accent);
  text-decoration: underline;
}

.bw-list-item__summary {
  margin: 0;
  color: var(--bw-color-fg-muted);
  font-size: var(--bw-text-body-sm-size);
  line-height: var(--bw-text-body-sm-line-height);
  text-wrap: pretty;
}

.bw-list-item__meta {
  color: var(--bw-color-fg-muted);
  font-size: var(--bw-text-caption-size);
  line-height: var(--bw-text-caption-line-height);
}

/* --- Dark-theme resets for light-only card ambient (beautiful defaults) ---- */

/* Soft ambient under elevation-* is a LIGHT-theme treatment so cards read on
 * white canvas; dark elevation tokens already carry their own depth, so dark
 * surfaces return to the plain elevation ramp here. Scoped exactly like
 * tokens.css's own [data-theme="dark"] block. The bordered-card and
 * interactive-hover restatements exist because these attribute-scoped rules
 * outrank the plain class rules they would otherwise shadow. */
[data-theme="dark"] .bw-card,
[data-theme="dark"] .bw-data-table-wrap,
[data-theme="dark"] .bw-filter-bar,
[data-theme="dark"] .bw-bulk-actions-bar,
[data-theme="dark"] .bw-stat,
[data-theme="dark"] .bw-stat-comparison,
[data-theme="dark"] .bw-empty-state {
  box-shadow: var(--bw-elevation-1);
}
[data-theme="dark"] .bw-stat--chrome-plain {
  box-shadow: none;
}
[data-theme="dark"] .bw-empty-state--size-sm {
  box-shadow: none;
}
[data-theme="dark"] .bw-card--bordered,
[data-theme="dark"] .bw-card--elevation-0 {
  box-shadow: var(--bw-elevation-0);
}
[data-theme="dark"] .bw-card--elevation-2 {
  box-shadow: var(--bw-elevation-2);
}
[data-theme="dark"] .bw-card--elevation-3 {
  box-shadow: var(--bw-elevation-3);
}
[data-theme="dark"] .bw-card--interactive:hover,
[data-theme="dark"] .bw-card--interactive:focus-within,
[data-theme="dark"] a.bw-stat:hover {
  box-shadow: var(--bw-elevation-2);
}
[data-theme="dark"] a.bw-stat--chrome-plain:hover {
  box-shadow: none;
}
[data-theme="dark"] .bw-account-menu__panel,
[data-theme="dark"] .bw-dropdown__panel,
[data-theme="dark"] .bw-version-switch__panel,
[data-theme="dark"] .bw-theme-switch__panel,
[data-theme="dark"] .bw-combobox__listbox {
  box-shadow: var(--bw-elevation-3);
}
[data-theme="dark"] .bw-toast {
  box-shadow: var(--bw-elevation-5);
}
[data-theme="dark"] .bw-modal:is(.bw-modal--open, [data-bw-open]) .bw-modal__panel {
  box-shadow: var(--bw-elevation-4);
}
[data-theme="dark"] .bw-slide-over[data-bw-open] .bw-slide-over__panel {
  box-shadow: var(--bw-elevation-4);
}

/* --- Token specimen (THM-016, icvoss/django-brickwork#268) ----------------
 * Live brand-token presentation: dual light/dark panes, swatch from the
 * cascade, optional contrast-pair chip. Resolved values are PE (token-
 * specimen.js). No Tailwind utilities; --bw-* only (BR-BW-TOK-001).
 */
.bw-token-specimen {
  display: flex;
  flex-direction: column;
  gap: var(--bw-space-4);
  margin-block: var(--bw-density-section-gap);
}

.bw-token-specimen__heading {
  margin: 0;
  font-family: var(--bw-font-family-sans);
  font-size: var(--bw-font-size-xl);
  font-weight: var(--bw-font-weight-semibold);
  color: var(--bw-color-fg);
  line-height: var(--bw-font-line-height-snug);
}

.bw-token-specimen__panes {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--bw-space-4);
}

@media (width < 48rem) {
  .bw-token-specimen__panes {
    grid-template-columns: 1fr;
  }
}

.bw-token-specimen__pane {
  display: flex;
  flex-direction: column;
  gap: var(--bw-space-3);
  padding: var(--bw-space-4);
  background: var(--bw-color-surface);
  color: var(--bw-color-fg);
  border: var(--bw-size-border-hairline) solid var(--bw-color-border);
  border-radius: var(--bw-radius-lg);
}

.bw-token-specimen__pane-label {
  margin: 0;
  font-family: var(--bw-font-family-sans);
  font-size: var(--bw-font-size-sm);
  font-weight: var(--bw-font-weight-semibold);
  color: var(--bw-color-fg-muted);
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

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

.bw-token-specimen__row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto minmax(6rem, 0.8fr);
  grid-template-areas:
    "meta swatch value"
    "pair pair pair";
  align-items: center;
  gap: var(--bw-space-2) var(--bw-space-3);
  padding-block: var(--bw-space-2);
  border-block-end: var(--bw-size-border-hairline) solid var(--bw-color-border);
}

.bw-token-specimen__row:last-child {
  border-block-end: none;
  padding-block-end: 0;
}

.bw-token-specimen__meta {
  grid-area: meta;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--bw-space-1) var(--bw-space-2);
  min-inline-size: 0;
}

.bw-token-specimen__name {
  font-family: var(--bw-font-family-mono);
  font-size: var(--bw-font-size-xs);
  color: var(--bw-color-fg);
  word-break: break-all;
}

.bw-token-specimen__flag {
  font-family: var(--bw-font-family-sans);
  font-size: var(--bw-font-size-2xs);
  color: var(--bw-color-fg-muted);
  background: var(--bw-color-surface-sunken);
  border-radius: var(--bw-radius-sm);
  padding-inline: var(--bw-space-1);
  padding-block: 0.1em;
}

.bw-token-specimen__swatch {
  grid-area: swatch;
  inline-size: 2rem;
  block-size: 2rem;
  border-radius: var(--bw-radius-md);
  border: var(--bw-size-border-hairline) solid var(--bw-color-border);
  flex-shrink: 0;
}

.bw-token-specimen__sample {
  grid-area: swatch;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  min-inline-size: 2.5rem;
  min-block-size: 2.5rem;
}

.bw-token-specimen__sample--radius {
  inline-size: 2.5rem;
  block-size: 2.5rem;
  background: var(--bw-color-accent);
  border: var(--bw-size-border-hairline) solid var(--bw-color-border);
}

.bw-token-specimen__sample--elevation {
  inline-size: 2.5rem;
  block-size: 2.5rem;
  background: var(--bw-color-surface-raised);
  border: var(--bw-size-border-hairline) solid var(--bw-color-border);
  border-radius: var(--bw-radius-md);
}

.bw-token-specimen__sample--type {
  font-size: var(--bw-font-size-lg);
  font-weight: var(--bw-font-weight-semibold);
  color: var(--bw-color-fg);
  line-height: 1;
  min-inline-size: 2.5rem;
}

.bw-token-specimen__sample--space {
  block-size: 0.75rem;
  min-inline-size: 0.5rem;
  max-inline-size: 8rem;
  background: var(--bw-color-accent);
  border-radius: var(--bw-radius-sm);
}

.bw-token-specimen__sample--other {
  inline-size: 2rem;
  block-size: 2rem;
  border: var(--bw-size-border-hairline) dashed var(--bw-color-border);
  border-radius: var(--bw-radius-sm);
}

.bw-token-specimen__value {
  grid-area: value;
  font-family: var(--bw-font-family-mono);
  font-size: var(--bw-font-size-2xs);
  color: var(--bw-color-fg-muted);
  min-block-size: 1em;
  word-break: break-all;
}

.bw-token-specimen__pair {
  grid-area: pair;
  display: inline-flex;
  align-items: center;
  gap: var(--bw-space-2);
  justify-self: start;
  padding-inline: var(--bw-space-3);
  padding-block: var(--bw-space-2);
  border-radius: var(--bw-radius-md);
  border: var(--bw-size-border-hairline) solid color-mix(in oklab, var(--bw-color-border) 70%, transparent);
  font-family: var(--bw-font-family-sans);
  font-size: var(--bw-font-size-xs);
}

.bw-token-specimen__pair-sample {
  font-weight: var(--bw-font-weight-semibold);
  font-size: var(--bw-font-size-md);
  line-height: 1;
}

.bw-token-specimen__pair-floor,
.bw-token-specimen__pair-measured {
  font-family: var(--bw-font-family-mono);
  font-size: var(--bw-font-size-2xs);
}

/* --- Preview frame (ILL-026, icvoss/django-brickwork#269) -----------------
 * Surface-guarded live-render container. Fill is ALWAYS --bw-color-surface
 * (verified-contrast trap): raised/tint fills are forbidden here. Delineate
 * with border + padding only. Distinct from .bw-product-chrome (decorative
 * marketing media chrome, Beat S5 / #571).
 */
.bw-preview-frame {
  display: flex;
  flex-direction: column;
  gap: var(--bw-space-2);
  margin: 0;
  min-inline-size: 0;
}

.bw-preview-frame__viewport {
  padding: var(--bw-space-3);
  border: var(--bw-size-border-hairline) solid var(--bw-color-border);
  border-radius: var(--bw-radius-lg);
  background-color: var(--bw-color-surface);
  contain: layout paint;
  isolation: isolate;
  min-inline-size: 0;
}

@media (width >= 48rem) {
  .bw-preview-frame__viewport {
    padding: var(--bw-space-5);
  }
}

.bw-preview-frame__viewport:focus-visible {
  outline: var(--bw-size-border-thick) solid var(--bw-color-accent);
  outline-offset: var(--bw-space-1);
}

.bw-preview-frame--scrollable .bw-preview-frame__viewport {
  max-block-size: 42rem;
  overflow: auto;
}

.bw-preview-frame__stage {
  min-inline-size: 0;
}

.bw-preview-frame--card .bw-preview-frame__viewport {
  overflow: hidden;
  max-block-size: 14rem;
  padding: var(--bw-space-2);
}

.bw-preview-frame--card .bw-preview-frame__stage {
  transform: scale(0.5);
  transform-origin: top left;
  inline-size: 200%;
}

.bw-preview-frame__caption {
  margin: 0;
  font-family: var(--bw-font-family-sans);
  font-size: var(--bw-font-size-sm);
  color: var(--bw-color-fg-muted);
  line-height: var(--bw-font-line-height-normal);
}

/* --- Article meta / editorial byline (#625) -------------------------------- */

.bw-article-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--bw-space-2);
  margin: 0;
  font-family: var(--bw-font-family-sans);
  font-size: var(--bw-font-size-sm);
  line-height: var(--bw-font-line-height-normal);
  color: var(--bw-color-fg-muted);
}

.bw-article-meta__avatar {
  display: inline-flex;
  flex-shrink: 0;
}

.bw-article-meta__author {
  display: inline-flex;
  align-items: center;
  /* WCAG 2.5.8 AA target size (#208): navigational byline link, not prose. */
  min-block-size: 1.5rem;
  color: var(--bw-color-fg);
  font-weight: var(--bw-font-weight-medium);
  text-decoration: none;
}

a.bw-article-meta__author:hover,
a.bw-article-meta__author:focus-visible {
  color: var(--bw-color-accent);
  text-decoration: underline;
  text-underline-offset: 0.15em;
}

.bw-article-meta__sep {
  color: var(--bw-color-fg-muted);
}

.bw-article-meta__date,
.bw-article-meta__reading-time {
  color: var(--bw-color-fg-muted);
}

.bw-article-meta__tags {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--bw-space-2);
  margin: 0;
  padding: 0;
  list-style: none;
  inline-size: 100%;
}

.bw-article-meta__tag-link,
.bw-article-meta__tag-label {
  display: inline-flex;
  align-items: center;
  /* WCAG 2.5.8 AA target size (#208): tag links are controls, not prose. */
  min-block-size: 1.5rem;
  font-size: var(--bw-font-size-xs);
  color: var(--bw-color-fg-muted);
  text-decoration: none;
}

a.bw-article-meta__tag-link:hover,
a.bw-article-meta__tag-link:focus-visible {
  color: var(--bw-color-accent);
  text-decoration: underline;
  text-underline-offset: 0.15em;
}

/* --- Comparison table (#626) ----------------------------------------------
 * First-class plan/capability matrix. Promoted from marketing
 * .bw-pricing-comparison*; the old class names remain as aliases so
 * hand-authored markup and the thin marketing wrapper stay pixel-identical.
 */

.bw-comparison-table,
.bw-pricing-comparison {
  display: flex;
  flex-direction: column;
}

.bw-comparison-table__heading,
.bw-pricing-comparison__heading {
  margin: 0;
  font-size: var(--bw-text-heading-section-size);
  font-weight: var(--bw-text-heading-section-weight);
  line-height: var(--bw-text-heading-section-line-height);
  letter-spacing: var(--bw-text-heading-section-tracking);
}

.bw-comparison-table__lede,
.bw-pricing-comparison__lede {
  margin: var(--bw-space-3) 0 var(--bw-space-8);
  max-inline-size: var(--bw-size-max-width-prose);
  color: var(--bw-color-fg-muted);
  text-wrap: pretty;
}

/* A comparison table cannot reflow to one column and stay a comparison, so it
 * scrolls inside its own container instead of pushing the page sideways on a
 * phone. The container is focusable in the markup so a keyboard can reach the
 * scroll; its focus ring comes free from the global :focus-visible rule, whose
 * selector already includes [tabindex]. */
.bw-comparison-table__scroll,
.bw-pricing-comparison__scroll {
  overflow-x: auto;
  /* contain: paint is load-bearing, not an optimisation. overflow-x alone does
   * NOT stop a wide <table> painting outside this container: the box scrolls
   * correctly (scrollWidth 687 against clientWidth 310 at 360px) while the
   * table still renders to its full intrinsic width, so the PAGE gains a
   * horizontal scrollbar even though the container has one. Containing paint
   * clips it to this box, which is the behaviour overflow-x is assumed to have.
   * Same class of defect as the 3.1.0 <pre>/<code> fix. Verified at 360, 375
   * and 414px in both themes; the table keeps its full width and scrolls. */
  contain: paint;
  border: var(--bw-size-border-hairline) solid var(--bw-color-border);
  border-radius: var(--bw-radius-lg);
}

.bw-comparison-table__table,
.bw-pricing-comparison__table {
  inline-size: 100%;
  border-collapse: collapse;
  font-size: var(--bw-text-body-md-size);
}

.bw-comparison-table__caption,
.bw-pricing-comparison__caption {
  padding: var(--bw-space-4) var(--bw-space-4) 0;
  color: var(--bw-color-fg-muted);
  font-size: var(--bw-text-body-sm-size);
  text-align: start;
  caption-side: top;
}

.bw-comparison-table__table th,
.bw-comparison-table__table td,
.bw-pricing-comparison__table th,
.bw-pricing-comparison__table td {
  padding: var(--bw-space-3) var(--bw-space-4);
  text-align: start;
  vertical-align: middle;
  border-block-end: var(--bw-size-border-hairline) solid var(--bw-color-border);
}

.bw-comparison-table__table thead th,
.bw-pricing-comparison__table thead th {
  color: var(--bw-color-fg);
  font-size: var(--bw-text-label-size);
  font-weight: var(--bw-text-label-weight);
  white-space: nowrap;
}

/* The capability column: it is the row's label, so it stays legible at body
 * weight and gets the width it needs rather than being squeezed by the plans. */
.bw-comparison-table__row-header,
.bw-pricing-comparison__row-header {
  font-weight: var(--bw-text-body-md-weight);
  min-inline-size: 14rem;
}

.bw-comparison-table__table tbody tr:last-child th,
.bw-comparison-table__table tbody tr:last-child td,
.bw-pricing-comparison__table tbody tr:last-child th,
.bw-pricing-comparison__table tbody tr:last-child td {
  border-block-end: none;
}

/* Colour is reinforcement only: every cell also carries a visually-hidden
 * "Included" or "Not included", so the meaning survives without it. */
.bw-comparison-table__yes,
.bw-pricing-comparison__yes {
  color: var(--bw-color-success);
}

.bw-comparison-table__no,
.bw-pricing-comparison__no {
  color: var(--bw-color-icon-muted);
}

.bw-comparison-table__note,
.bw-pricing-comparison__note {
  margin: var(--bw-space-6) 0 0;
  color: var(--bw-color-fg-muted);
  font-size: var(--bw-text-body-sm-size);
}

/* One recommended plan column. Accent wash + stronger header weight are
 * reinforcement; the header also emits visually-hidden "(Recommended)" (or
 * highlighted_label) so meaning does not ride on colour alone. */
.bw-comparison-table__col--highlighted,
.bw-pricing-comparison__col--highlighted {
  background-color: color-mix(in oklch, var(--bw-color-accent) 8%, transparent);
}

.bw-comparison-table__table thead th.bw-comparison-table__col--highlighted,
.bw-pricing-comparison__table thead th.bw-pricing-comparison__col--highlighted {
  box-shadow: inset 0 calc(var(--bw-size-border-thick) * -1) 0 0 var(--bw-color-accent);
  font-weight: var(--bw-text-heading-sm-weight);
}

[data-theme="dark"] .bw-comparison-table__col--highlighted,
[data-theme="dark"] .bw-pricing-comparison__col--highlighted {
  background-color: color-mix(in oklch, var(--bw-color-accent) 14%, transparent);
}

/* --- Input group (prefix/suffix addons, #624) ------------------------------ */

.bw-input-group {
  display: flex;
  align-items: stretch;
  inline-size: 100%;
  min-inline-size: 0;
}

.bw-input-group__field {
  flex: 1 1 auto;
  min-inline-size: 0;
  position: relative;
  z-index: 0;
}

.bw-input-group__field .bw-input {
  inline-size: 100%;
}

.bw-input-group__addon {
  display: inline-flex;
  flex: 0 0 auto;
  align-items: center;
  gap: var(--bw-space-1);
  padding-inline: var(--bw-density-control-padding-inline);
  border: var(--bw-size-border-hairline) solid var(--bw-color-border-control);
  background: var(--bw-color-surface-sunken);
  color: var(--bw-color-fg-muted);
  font-size: var(--bw-text-body-sm-size);
  line-height: var(--bw-text-body-sm-line-height);
  white-space: nowrap;
  position: relative;
  z-index: 0;
}

.bw-input-group__text {
  font-variant-numeric: tabular-nums;
}

.bw-input-group__icon {
  color: var(--bw-color-fg-muted);
}

/* Attached geometry: shared border, collapsed adjacent edges, end radii. */
.bw-input-group__addon--prefix {
  border-start-start-radius: var(--bw-radius-md);
  border-end-start-radius: var(--bw-radius-md);
  border-inline-end-width: 0;
}

.bw-input-group__addon--suffix {
  border-start-end-radius: var(--bw-radius-md);
  border-end-end-radius: var(--bw-radius-md);
  border-inline-start-width: 0;
}

.bw-input-group:has(.bw-input-group__addon--prefix) .bw-input-group__field .bw-input {
  border-start-start-radius: 0;
  border-end-start-radius: 0;
}

.bw-input-group:has(.bw-input-group__addon--suffix) .bw-input-group__field .bw-input {
  border-start-end-radius: 0;
  border-end-end-radius: 0;
}

/* Focus and invalid ride the slotted control; raise its edge above addons. */
.bw-input-group__field:focus-within {
  z-index: 1;
}

.bw-field--invalid .bw-input-group__addon,
.bw-input-group:has(.bw-input[aria-invalid="true"]) .bw-input-group__addon {
  border-color: var(--bw-color-danger);
}


/* ==== layer: marketing ==== */
/* brickwork.marketing kit CSS (1.2.0, ADR-055, BR-BW-MKT-*): the marketing
 * shell + the eight marketing components. Clean-room, hand-authored CSS over
 * the --bw-* token layer only (BR-BW-MKT-003): no Tailwind utility classes,
 * no reference-kit markup or structure. Logical CSS properties throughout
 * (BR-BW-TOK-005), so RTL is handled by the `dir` attribute alone. Nothing
 * here needs JavaScript (BR-BW-HTMX-001/BR-BW-MKT's no-JS floor): the
 * marketing shell's header nav is plain links, and every component is
 * static markup. Every animation composes the shared motion tokens and sits
 * under the global prefers-reduced-motion floor already declared in
 * index.css (BR-BW-TOK-009).
 *
 * Section rhythm: every direct section-level element uses
 * --bw-component-section-gap-marketing (6rem) for its own block spacing,
 * the marketing-scoped counterpart of the app shell's
 * --bw-density-section-gap, so marketing pages read as generous, distinct
 * blocks rather than a dense workspace (BR-BW-MKT-002 beautiful-by-default;
 * raised from 4rem in icvoss/django-brickwork#510).
 */

/* --- Marketing shell -------------------------------------------------------- */

.bw-marketing {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}

/* --- Overlay mode (ADR-105, icvoss/django-brickwork#565) --------------------
 * Opt-in via .bw-site-header--overlay on the marketing shell. Marketing-
 * shell-only: docs must not use overlay. Default sticky solid header above
 * is unchanged. Overlay is fixed so the first content band can paint under
 * the chrome; package-owned clearance padding (below) keeps copy readable.
 * No-JS floor: opaque surface + theme ink. Transparency / context ink only
 * after data-bw-overlay-ready (marketing-overlay.js).
 */
.bw-site-header--overlay {
  position: fixed;
  inset-inline: 0;
  inset-block-start: 0;
  inline-size: 100%;
}

/* Clearance on the marketing root (inherits to content), not on the header:
 * custom properties do not cross to siblings. Value = logo row + header
 * inner block padding so copy clears the fixed chrome. Local cascade vars
 * always carry a fallback so they are not mistaken for tokens.css emits
 * (test_frontend_css_only_references_emitted_token_names). */
.bw-marketing:has(> .bw-site-header--overlay) {
  --bw-marketing-header-clearance: calc(
    var(--bw-component-logo-height) + (var(--bw-space-3) * 2) + var(--bw-space-2)
  );
}

.bw-marketing:has(> .bw-site-header--overlay) .bw-marketing__content > .bw-hero:first-child {
  padding-block-start: calc(
    var(
        --bw-marketing-header-clearance,
        calc(var(--bw-component-logo-height) + (var(--bw-space-3) * 2) + var(--bw-space-2))
      ) + var(--bw-space-10)
  );
}

.bw-marketing:has(> .bw-site-header--overlay)
  .bw-marketing__content
  > *:first-child:not(.bw-hero) {
  /* Clearance replaces #111's margin for overlay: the header is out of flow,
   * so padding (not margin against a sticky sibling) clears the fixed chrome.
   * Selector mirrors #111 so the hero still opts out. */
  margin-block-start: 0;
  padding-block-start: calc(
    var(
        --bw-marketing-header-clearance,
        calc(var(--bw-component-logo-height) + (var(--bw-space-3) * 2) + var(--bw-space-2))
      ) + var(--bw-component-section-gap-marketing)
  );
}

/* Enhanced: transparent until scrolled; ink follows panel context. */
.bw-site-header--overlay[data-bw-overlay-ready]:not([data-bw-scrolled="true"]) {
  background: transparent;
  border-block-end-color: transparent;
  box-shadow: none;
}

.bw-site-header--overlay[data-bw-overlay-ready][data-bw-nav-context="dark"] {
  --bw-overlay-ink: oklch(0.98 0.002 265);
  --bw-overlay-ink-muted: color-mix(in oklab, oklch(0.98 0.002 265) 72%, transparent);
  --bw-overlay-frost: color-mix(in oklab, oklch(0.16 0.02 265) 78%, transparent);
  --bw-overlay-line: oklch(1 0 0 / 0.14);
}

.bw-site-header--overlay[data-bw-overlay-ready][data-bw-nav-context="light"] {
  --bw-overlay-ink: oklch(0.205 0.005 265);
  --bw-overlay-ink-muted: color-mix(in oklab, oklch(0.205 0.005 265) 68%, transparent);
  --bw-overlay-frost: color-mix(in oklab, oklch(1 0 0) 82%, transparent);
  --bw-overlay-line: oklch(0.205 0.005 265 / 0.14);
}

.bw-site-header--overlay[data-bw-overlay-ready][data-bw-scrolled="true"] {
  background: var(--bw-overlay-frost, color-mix(in oklab, oklch(1 0 0) 82%, transparent));
  border-block-end-color: var(--bw-overlay-line, oklch(0.205 0.005 265 / 0.14));
  box-shadow: var(--bw-elevation-1);
  -webkit-backdrop-filter: blur(12px) saturate(140%);
  backdrop-filter: blur(12px) saturate(140%);
}

@media (prefers-reduced-motion: reduce) {
  .bw-site-header--overlay[data-bw-overlay-ready] {
    transition: none;
  }
  .bw-site-header--overlay[data-bw-overlay-ready][data-bw-scrolled="true"] {
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
}

.bw-site-header--overlay[data-bw-overlay-ready] {
  color: var(--bw-overlay-ink, oklch(0.205 0.005 265));
  transition:
    background var(--bw-duration-normal) var(--bw-ease-standard),
    border-color var(--bw-duration-normal) var(--bw-ease-standard),
    box-shadow var(--bw-duration-normal) var(--bw-ease-standard),
    color var(--bw-duration-normal) var(--bw-ease-standard);
}

.bw-site-header--overlay[data-bw-overlay-ready] .bw-site-header__brand-wordmark,
.bw-site-header--overlay[data-bw-overlay-ready]
  .bw-site-header__nav
  :where(a):not(.bw-btn),
.bw-site-header--overlay[data-bw-overlay-ready]
  .bw-site-header__actions
  > :where(a):not(.bw-btn) {
  color: var(
    --bw-overlay-ink-muted,
    color-mix(in oklab, oklch(0.205 0.005 265) 68%, transparent)
  );
}

.bw-site-header--overlay[data-bw-overlay-ready]
  .bw-site-header__nav
  :where(a:hover):not(.bw-btn),
.bw-site-header--overlay[data-bw-overlay-ready]
  .bw-site-header__actions
  > :where(a:hover):not(.bw-btn),
.bw-site-header--overlay[data-bw-overlay-ready] .bw-site-header__brand-wordmark:hover {
  color: var(--bw-overlay-ink, oklch(0.205 0.005 265));
}

/* Open mobile disclosure under overlay: keep opaque panel readable. */
.bw-site-header--overlay[data-bw-overlay-ready]
  .bw-mobile-nav-toggle[open]
  ~ .bw-site-header__nav,
.bw-site-header--overlay[data-bw-overlay-ready]
  .bw-mobile-nav-toggle[open]
  ~ .bw-site-header__nav,
.bw-site-header--overlay[data-bw-overlay-ready]
  .bw-mobile-nav-toggle[open]
  ~ .bw-site-header__actions {
  background: var(--bw-color-surface);
  color: var(--bw-color-fg);
}

.bw-site-header--overlay[data-bw-overlay-ready]
  .bw-mobile-nav-toggle[open]
  ~ .bw-site-header__nav
  a,
.bw-site-header--overlay[data-bw-overlay-ready]
  .bw-mobile-nav-toggle[open]
  ~ .bw-site-header__actions
  > a:not(.bw-btn) {
  color: var(--bw-color-fg);
}

/* Wraps below the brand row at narrow widths (icvoss/django-brickwork#125):
 * at 320px the brand mark/wordmark,
nav and actions cluster together are
 * wider than the content box the gutter leaves,
and with no flex-wrap the
 * row simply overflows the viewport,
scrolling the whole PAGE sideways
 * (measured 326px scrollWidth against a 320px viewport,
actions/button right
 * edge both at 326). flex-wrap alone is not the whole fix: see the
 * min-inline-size: 0 note on __nav and __actions below,
the same
 * "flex child refuses to shrink" hazard contain: paint solves for the
 * pricing-comparison table,
but here the fix is letting the row itself wrap
 * rather than clipping paint,
since this is real inline content,
not a wide
 * table. Verify at 320px in both themes before calling this closed: a JS
 * test can assert the layout rule,
not the rendered result. */
.bw-site-header__brand {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--bw-space-2) var(--bw-space-6);
  position: relative;
}

/* Brand slot default sizing (#83,
ADR-054 beautiful-by-default): brand_logo
 * and brand_wordmark are wrapped in brickwork-owned elements (the app
 * shell's brickwork#93 precedent),
so a mark/lockup dropped into either slot
 * is constrained WITHOUT knowing the consumer's own inner markup or classes.
 * Any img/svg inside a wrapper caps at --bw-component-logo-height with width
 * following the intrinsic ratio,
so an unconstrained SVG can never render as
 * a banner and push the nav off-screen; the wrappers stay flex: 0 0 auto so
 * the nav and actions clusters keep the remaining width. The img/svg leg is
 * zero-specificity (:where),
so a one-class consumer rule overrides the cap.
 * An unfilled block leaves an :empty wrapper,
which takes no space (no
 * phantom header gap). */
.bw-site-header__brand-mark,
.bw-site-header__brand-wordmark {
  display: inline-flex;
  align-items: center;
  flex: 0 0 auto;
}

.bw-site-header__brand-mark :where(img, svg),
.bw-site-header__brand-wordmark :where(img, svg) {
  block-size: var(--bw-component-logo-height);
  inline-size: auto;
}

/* Two-tier target size (icvoss/django-brickwork#208,
then #242): the primary
 * nav row's links carried no minimum,
sized to their label's line-height
 * alone (16-18px at 375px). A nav link is a navigational control,
not
 * inline prose,
so WCAG 2.5.8's text-run exception does not apply; the AA
 * floor here is min-block-size: 1.5rem (24px),
unconditional,
grown via
 * min-block-size rather than padding,
so display: inline-flex +
 * align-items keeps the label centred on the larger box. #242 adds a
 * second,
coarse-pointer-only tier below: WCAG 2.5.5/platform-HIG's 44px
 * bar,
above what the package's AA claim requires,
applied only where the
 * primary input is imprecise (a fine-pointer desktop click never sees the
 * larger target,
so the row's rendered layout does not shift there).
 * :not(.bw-btn) excludes a bw_button a consumer composes into this slot
 * (buttons are #212's own tier,
sized by .bw-btn's own rule),
matching the
 * header actions rule below exactly: marketing_nav is documented as "a
 * plain list of links",
but the exclusion is defensive against a consumer
 * dropping a button in here anyway. */
.bw-site-header__nav :where(a):not(.bw-btn) {
  display: inline-flex;
  align-items: center;
  min-block-size: 1.5rem;
  color: var(--bw-color-fg-muted);
  font-size: var(--bw-text-label-size);
  font-weight: var(--bw-text-label-weight);
  text-decoration: none;
  transition: var(--bw-transition-colors);
}

@media (pointer: coarse) {
  .bw-site-header__nav :where(a):not(.bw-btn) {
    min-block-size: var(--bw-size-touch-target-min);
  }
}

/* --- Mobile nav toggle (#263) ----------------------------------------------
 * Package-owned collapse for the marketing header. Place
 * .bw-mobile-nav-toggle (see _mobile_nav_toggle.html) as a sibling of
 * .bw-site-header__nav / .bw-site-header__actions via
 * marketing_nav_region. Rules sit AFTER the base __nav/__actions display
 * declarations so sibling combinators win the cascade. Mobile-first; the
 * --breakpoint-lg query (64rem) restores the inline desktop bar and hides
 * the toggle. No JS.
 */

.bw-mobile-nav-toggle {
  order: 1;
  margin-inline-start: auto;
}

.bw-mobile-nav-toggle > summary {
  list-style: none;
}

.bw-mobile-nav-toggle > summary::-webkit-details-marker {
  display: none;
}

.bw-mobile-nav-toggle__trigger {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  inline-size: var(--bw-size-touch-target-min);
  block-size: var(--bw-size-touch-target-min);
  border-radius: var(--bw-radius-md);
  color: var(--bw-color-fg);
  cursor: pointer;
  transition: var(--bw-transition-colors);
}

.bw-mobile-nav-toggle__trigger:hover {
  background: var(--bw-color-surface-marketing-tint);
}

.bw-mobile-nav-toggle__trigger:focus-visible {
  outline: 2px solid var(--bw-color-accent);
  outline-offset: 2px;
}

.bw-mobile-nav-toggle__icon {
  display: block;
}

/* Two-class / compound selectors beat the single-class .bw-icon
 * {display:inline-block} base (icvoss/django-brickwork#519): without the
 * extra specificity,
the later icon rule restored the close glyph in the
 * closed state so the trigger painted hamburger and cross together. */
.bw-mobile-nav-toggle:not([open]) .bw-mobile-nav-toggle__icon--close {
  display: none;
}

.bw-mobile-nav-toggle[open] .bw-mobile-nav-toggle__icon--open {
  display: none;
}

.bw-mobile-nav-toggle[open] .bw-mobile-nav-toggle__icon--close {
  display: block;
}

.bw-mobile-nav-toggle ~ .bw-site-header__nav,
.bw-mobile-nav-toggle ~ .bw-site-header__actions {
  display: none;
  order: 2;
  inline-size: 100%;
}

.bw-mobile-nav-toggle[open] ~ .bw-site-header__nav,
.bw-mobile-nav-toggle[open] ~ .bw-site-header__actions {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: var(--bw-space-1);
}

.bw-mobile-nav-toggle[open] ~ .bw-site-header__nav {
  padding-block-start: var(--bw-space-4);
  border-block-start: var(--bw-size-border-hairline) solid var(--bw-color-border);
  margin-block-start: var(--bw-space-3);
}

.bw-mobile-nav-toggle[open] ~ .bw-site-header__actions {
  padding-block-end: var(--bw-space-2);
  flex-direction: column;
}

.bw-mobile-nav-toggle[open] ~ .bw-site-header__nav a {
  display: flex;
  align-items: center;
  min-block-size: var(--bw-size-touch-target-min);
  padding-inline: var(--bw-space-3);
  border-radius: var(--bw-radius-md);
  color: var(--bw-color-fg);
  font-size: var(--bw-font-size-sm);
  font-weight: var(--bw-font-weight-medium);
  text-decoration: none;
}

.bw-mobile-nav-toggle[open] ~ .bw-site-header__nav a:hover {
  background: var(--bw-color-surface-raised);
}

.bw-mobile-nav-toggle[open] ~ .bw-site-header__nav a:focus-visible,
.bw-mobile-nav-toggle[open] ~ .bw-site-header__actions a:focus-visible,
.bw-mobile-nav-toggle[open] ~ .bw-site-header__actions button:focus-visible {
  outline: 2px solid var(--bw-color-accent);
  outline-offset: 2px;
}

.bw-mobile-nav-toggle[open] ~ .bw-site-header__actions > a,
.bw-mobile-nav-toggle[open] ~ .bw-site-header__actions .bw-btn,
.bw-mobile-nav-toggle[open] ~ .bw-site-header__actions .bw-site-header__logout-form {
  inline-size: 100%;
}

.bw-mobile-nav-toggle[open] ~ .bw-site-header__actions .bw-btn {
  justify-content: center;
  min-block-size: var(--bw-size-touch-target-min);
}

.bw-mobile-nav-toggle[open] ~ .bw-site-header__actions > a:not(.bw-btn) {
  display: flex;
  align-items: center;
  min-block-size: var(--bw-size-touch-target-min);
  padding-inline: var(--bw-space-3);
  border-radius: var(--bw-radius-md);
  color: var(--bw-color-fg);
  font-size: var(--bw-font-size-sm);
  font-weight: var(--bw-font-weight-medium);
  text-decoration: none;
}

.bw-mobile-nav-toggle[open] ~ .bw-site-header__actions > a:not(.bw-btn):hover {
  background: var(--bw-color-surface-raised);
}

@media (width >= 64rem) {
  .bw-site-header__brand {
    flex-wrap: nowrap;
  }

  .bw-mobile-nav-toggle {
    display: none;
  }

  .bw-mobile-nav-toggle ~ .bw-site-header__nav,
  .bw-mobile-nav-toggle ~ .bw-site-header__actions {
    display: flex;
    flex-direction: row;
    align-items: center;
    order: initial;
    inline-size: auto;
    padding: 0;
    border-block-start: none;
    margin-block-start: 0;
  }
}

/* A plain link (e.g. "Sign in") alongside a bw_button in this slot
 * (04-interfaces.md 4d documents exactly this shape): styled the same as the
 * nav link colour,
never the UA default blue,
so it clears WCAG AA contrast
 * in both themes (BR-BW-MKT-002). :not(.bw-btn) excludes bw_button's own
 * markup: buttons are #212's own tier,
sized by .bw-btn's own rule; this
 * rule targets only a bare <a> the consumer places here.
 * Two-tier target size (icvoss/django-brickwork#208,
then #242): a consumer
 * following this exact documented composition got a sub-floor target
 * (measured 40x21 at 375px) because nothing here set a minimum; the AA
 * floor is min-block-size: 1.5rem (24px),
unconditional,
grown via
 * min-block-size rather than padding,
so inline-flex + centred content
 * keeps the label centred on the larger box. #242 adds a coarse-pointer-only
 * 44px tier below,
the WCAG 2.5.5/platform-HIG bar; a fine-pointer desktop
 * click never sees it,
so the slot's layout does not shift there. */
.bw-site-header__actions > :where(a):not(.bw-btn) {
  display: inline-flex;
  align-items: center;
  min-block-size: 1.5rem;
  color: var(--bw-color-fg-muted);
  font-size: var(--bw-text-label-size);
  font-weight: var(--bw-text-label-weight);
  text-decoration: none;
  transition: var(--bw-transition-colors);
}

@media (pointer: coarse) {
  .bw-site-header__actions > :where(a):not(.bw-btn) {
    min-block-size: var(--bw-size-touch-target-min);
  }
}

.bw-marketing__content {
  flex: 1 1 auto;
  outline: none; /* the skip-link target; focus ring handled by :focus-visible */
}

/* Footer link groups (#500): responsive columns without elevated card chrome.
 * Distinct from .bw-feature-grid > .bw-feature-card (those cards are for
 * feature bands). The shell still ships an empty marketing_footer slot;
 * these classes are the authored-example vocabulary consumers may copy. */
.bw-marketing-footer__groups {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--bw-space-6);
}

@media (width >= 40rem) {
  .bw-marketing-footer__groups {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

.bw-marketing-footer__group {
  display: flex;
  flex-direction: column;
  gap: var(--bw-space-2);
  align-items: flex-start;
}

.bw-marketing-footer__group-heading {
  margin: 0;
  font-family: var(--bw-text-heading-sm-family);
  font-size: var(--bw-text-heading-sm-size);
  font-weight: var(--bw-text-heading-sm-weight);
  line-height: var(--bw-text-heading-sm-line-height);
  color: var(--bw-color-fg);
}

/* A consumer's marketing_footer block content is arbitrary markup. The shell
 * supplies no default link groups or column arrangement,
but any plain <a>
 * a consumer authors in the slot is styled like a header-nav link rather than
 * left to the UA default blue (BR-BW-MKT-002).
 * Two-tier target size (icvoss/django-brickwork#208,
then #242) REVERSES
 * #208's own exemption: #208 explicitly declined to size these links,
* reasoning that "brickwork does not own the group's layout,
so a sizing
 * floor is not this package's call to make" (its comment on
 * TAP_TARGET_EXEMPT_SELECTORS in axe.spec.mjs). That rationale no longer
 * holds: this rule already styles colour/decoration for any <a> descendant
 * here regardless of the consumer's own markup,
which is the same claim of
 * ownership a sizing floor makes,
so #242 sizes them too rather than
 * leaving the inconsistency in place. min-block-size: 1.5rem (24px,
* unconditional) grows the box,
not padding; inline-flex + align-items
 * keeps the label centred on the larger box. #242 adds a coarse-pointer-only
 * 44px tier below,
the WCAG 2.5.5/platform-HIG bar; a fine-pointer desktop
 * click never sees it,
so the footer's layout does not shift there.
 * :not(.bw-btn) excludes a bw_button a consumer composes into this slot
 * (buttons are #212's own tier,
sized by .bw-btn's own rule),
matching the
 * header actions rule above. */
.bw-site-footer__inner :where(a):not(.bw-btn) {
  display: inline-flex;
  align-items: center;
  min-block-size: 1.5rem;
  color: var(--bw-color-fg-muted);
  text-decoration: none;
  transition: var(--bw-transition-colors);
}

@media (pointer: coarse) {
  .bw-site-footer__inner :where(a):not(.bw-btn) {
    min-block-size: var(--bw-size-touch-target-min);
  }
}

/* icvoss/django-brickwork#267: collapse the footer landmark when both content
 * slots are empty. The inner markup keeps block output flush so an unfilled
 * marketing_footer leaves the legal wrapper as the sole (empty) child. */
.bw-site-footer:has(.bw-site-footer__legal:only-child:empty) {
  display: none;
}

/* Legacy rail: every direct child without the section shell still wraps at
 * the marketing measure. Kept so existing component roots (hero,
pricing,
* cta,
…) stay byte-identical. New custom bands use .bw-section /
 * .bw-section__inner below instead of inventing a site *-inner class
 * (icvoss/django-brickwork#667,
ADR-057 Phase A). */
.bw-marketing__content > * {
  max-inline-size: var(--bw-component-content-max-width-marketing);
  margin-inline: auto;
  padding-inline: var(--bw-density-page-gutter-inline);
}

.bw-marketing__content > * + * {
  margin-block-start: var(--bw-component-section-gap-marketing);
}

/* --- Section shell (ADR-057 Phase A,
#667) ----------------------------------
 * Outer .bw-section is full-bleed capable (atmosphere,
tint,
inverse).
 * Inner .bw-section__inner is the shared marketing rail (measure + gutter).
 * Rhythm above still applies to .bw-section roots as direct children. */

.bw-section {
  display: block;
}

/* Opt out of the legacy per-child rail so the band can run edge to edge
 * of <main>; content measure moves to .bw-section__inner. */
.bw-marketing__content > .bw-section {
  max-inline-size: none;
  inline-size: 100%;
  padding-inline: 0;
}

.bw-section__inner {
  max-inline-size: var(--bw-component-content-max-width-marketing);
  margin-inline: auto;
  padding-inline: var(--bw-density-page-gutter-inline);
}

/* Shared section intro (#695): overline + heading + lede inside __inner.
 * Class contract: usable from named includes and plain .bw-section markup.
 * Type roles: overline / heading-section / body-lg. Prefer the __intro
 * wrapper when stacking more than one piece; a bare __heading as the first
 * child of __inner still gets bottom rhythm for named bands that use it. */
.bw-section__intro {
  display: flex;
  flex-direction: column;
  gap: var(--bw-space-3);
  margin-block-end: var(--bw-space-8);
}

.bw-section__overline {
  margin: 0;
  color: var(--bw-color-fg-muted);
  font-family: var(--bw-text-overline-family);
  font-size: var(--bw-text-overline-size);
  font-weight: var(--bw-text-overline-weight);
  line-height: var(--bw-text-overline-line-height);
  letter-spacing: var(--bw-text-overline-tracking);
  text-transform: uppercase;
}

.bw-section__heading {
  margin: 0;
  font-family: var(--bw-text-heading-section-family);
  font-size: var(--bw-text-heading-section-size);
  font-weight: var(--bw-text-heading-section-weight);
  line-height: var(--bw-text-heading-section-line-height);
  letter-spacing: var(--bw-text-heading-section-tracking);
  text-wrap: balance;
}

.bw-section__lede {
  margin: 0;
  color: var(--bw-color-fg-muted);
  font-size: var(--bw-text-body-lg-size);
  font-weight: var(--bw-text-body-lg-weight);
  line-height: var(--bw-text-body-lg-line-height);
  text-wrap: pretty;
  max-inline-size: var(--bw-size-max-width-prose);
}

.bw-section__inner > .bw-section__heading:first-child {
  margin-block-end: var(--bw-space-8);
}

/* width="bleed" (ADR-057 §1a): shared escape,
also used by .bw-cta--bleed
 * and .bw-cta-bleed so the axis is not CTA-only. Specificity matches the
 * shell child rules when composed under .bw-marketing__content. */
.bw-section--bleed,
.bw-marketing__content > .bw-section--bleed,
.bw-cta--bleed,
.bw-cta-bleed {
  inline-size: 100vw;
  max-inline-size: 100vw;
  margin-inline: calc(50% - 50vw);
}

.bw-marketing__content > .bw-section--bleed {
  padding-inline: 0;
}

/* band="tint" (ADR-057 §1a): background only; measure stays on __inner. */
.bw-section--tint {
  background: var(--bw-color-surface-marketing-tint);
}

/* The FIRST section needs its own block-start spacing (#111). The rule above
 * only spaces BETWEEN children,
so a page whose opening section is not a hero
 * (a page header,
a feature grid,
a docs index) rendered flush against the
 * header's hairline,
and consumers were wrapping their first child in a
 * padding div to compensate. The app shell has no equivalent problem because
 * it owns a page-header slot; the marketing shell has no such slot,
so the
 * spacing has to come from the content region itself.
 *
 * The hero opts out: it already owns its vertical rhythm via its own
 * padding-block,
so applying this to it as well would double the space on the
 * canonical landing page. Landed in 2.0.0 because it changes a visual default
 * for any consumer already compensating with their own top padding. */
.bw-marketing__content > *:first-child:not(.bw-hero) {
  margin-block-start: var(--bw-component-section-gap-marketing);
}

/* --- Hero -------------------------------------------------------------------- */

.bw-hero {
  display: flex;
  flex-direction: column;
  gap: var(--bw-space-8);
  /* Extra block-end air so the hero reads as its own act before the next
   * section (icvoss/django-brickwork#510), without fighting the shell's
   * section-gap token on siblings. */
  padding-block: var(--bw-space-10) var(--bw-space-4);
  text-align: start;
}

/* --start is the DOCUMENTED DEFAULT and the template emits it unconditionally
 * via {% firstof align 'start' %}so it needs a real rule (#120 defect class).
 * It restates the .bw-hero base alignment explicitly,
which also gives
 * align="end" below something symmetrical to sit against. */
.bw-hero--start {
  align-items: flex-start;
  text-align: start;
}

.bw-hero--end {
  align-items: flex-end;
  text-align: end;
}

.bw-hero--center {
  align-items: center;
  text-align: center;
}

/* width="bleed" (ADR-057 section 1a / #710): the hero has no separate
 * __inner wrapper the way .bw-section does (media_placement="behind" and
 * "beside" both need .bw-hero__copy and .bw-hero__media as DIRECT children
 * for their own display:grid tricks below, and the media_placement axis is
 * documented CSS-only, so this axis cannot add one either). Collapsing the
 * section shell's two-box escape (100vw outer, capped-and-gutter inner)
 * onto the one box .bw-hero already is: inline-size/margin-inline give the
 * same fluid viewport escape as .bw-section--bleed, and padding-inline
 * reproduces .bw-section__inner's cap in a single declaration (the larger
 * of the page gutter or the space needed to keep the content box at
 * --bw-component-content-max-width-marketing). .bw-hero__media already
 * caps at 100% of its container (max-inline-size: 100%, above), and
 * .bw-hero__copy already caps narrower still at the prose measure, so both
 * land on the marketing measure inside this padding without their own
 * change. .bw-stage (position: absolute; inset: 0 relative to the padding
 * edge) ignores this padding and keeps covering the full bleed box.
 * media_placement="behind" applies AFTER this in source order and sets its
 * own `padding` shorthand (not padding-inline alone), which resets this
 * rule's padding-inline back to its own space-16/space-8: bleed then makes
 * the inverse panel itself span the viewport, exactly per the issue, while
 * .bw-hero__copy keeps capping itself at the prose measure inside it. */
.bw-hero--bleed {
  inline-size: 100vw;
  max-inline-size: 100vw;
  margin-inline: calc(50% - 50vw);
  /* Cap plus gutter, not max(cap, gutter): the rail rule and
     .bw-section__inner are border-box, so their content edge sits at
     (100vw - measure) / 2 + gutter. max() dropped the gutter on wide
     viewports and the bleed hero's copy sat one gutter outside every
     other band (4.3.0 defect, found on the first consumer). */
  padding-inline: calc(
    max(0px, (100vw - var(--bw-component-content-max-width-marketing)) / 2) +
      var(--bw-density-page-gutter-inline)
  );
}

.bw-hero__copy {
  display: flex;
  flex-direction: column;
  gap: var(--bw-space-4);
  /* .bw-hero is a column flex with align-items other than stretch (--start and
   * --end both set it), which sizes this child to its CONTENT rather than to
   * the container. A lede longer than the viewport therefore pushed the whole
   * hero wider than the page and scrolled the document sideways on a phone.
   * 100% caps it at the container's content box, restoring the mobile-first
   * floor (ADR-057 section 1: a section that only works at desktop width is not
   * a shipped section).
   *
   * Found at 375px by the plan's Phase 6 gate 3 while stacking two heroes in
   * one fixture; the shipped marketing pages never tripped it because their
   * ledes happen to be shorter.
   *
   * The measure itself is --bw-component-hero-copy-max-width (defaults to the
   * prose measure). Heading and lede have their own max-width tokens so a
   * brand can widen the display heading without also widening the lede
   * (icvoss/django-brickwork#640): raise the copy (and optionally heading)
   * token, then set --bw-component-hero-lede-max-width to the prose measure
   * or 62ch. */
  max-inline-size: min(var(--bw-component-hero-copy-max-width), 100%);
}

.bw-hero--center .bw-hero__copy {
  margin-inline: auto;
}

.bw-hero__eyebrow {
  margin: 0;
  color: var(--bw-color-accent);
  font-family: var(--bw-text-overline-family);
  font-size: var(--bw-text-overline-size);
  font-weight: var(--bw-text-overline-weight);
  line-height: var(--bw-text-overline-line-height);
  letter-spacing: var(--bw-text-overline-tracking);
  text-transform: uppercase;
}

/* Eyebrow tone (#672): sentence-case muted kicker. Opt-in via
 * eyebrow_tone="sentence"; omitted/"accent" keeps the accent uppercase
 * overline byte-identical. Size and weight stay on the overline role so
 * hierarchy is unchanged; only colour and case shift. */
.bw-hero__eyebrow--sentence {
  color: var(--bw-color-fg-muted);
  letter-spacing: var(--bw-font-tracking-normal);
  text-transform: none;
}

/* Eyebrow marker (#659): leading accent rule before the overline. Opt-in
 * via eyebrow_marker="rule"; omitted/"none" is byte-identical. Flex + gap
 * only when the modifier is present so default eyebrows keep their
 * previous block layout. The mark colour is its own token (not
 * currentColor) so a brand can paint the rule differently from the
 * eyebrow text. */
.bw-hero__eyebrow--rule {
  display: flex;
  align-items: center;
  gap: var(--bw-space-3);
}

.bw-hero--center .bw-hero__eyebrow--rule {
  justify-content: center;
}

.bw-hero--end .bw-hero__eyebrow--rule {
  justify-content: flex-end;
}

.bw-hero__eyebrow--rule::before {
  content: "";
  flex: none;
  inline-size: var(--bw-component-hero-eyebrow-marker-inline-size);
  block-size: var(--bw-component-hero-eyebrow-marker-block-size);
  background-color: var(--bw-component-hero-eyebrow-marker-color);
}

.bw-hero__heading {
  margin: 0;
  font-family: var(--bw-text-heading-display-family);
  /* Fluid, capped at the display token. The token is a fixed 3.75rem (60px),
   * at which a single long word ("Documentation") measures 406px and cannot
   * wrap, so it overflowed a 375px viewport and scrolled the whole document
   * sideways. A multi-word heading hid the bug by wrapping between words.
   *
   * clamp() keeps the designed size from ~40rem up and scales down below it,
   * so the mobile-first floor holds for any heading (ADR-057 section 1). The
   * token still sets the ceiling, so a brand retuning the display scale
   * retunes this with it.
   *
   * Found at 375px by the plan's Phase 6 gate 3. */
  font-size: clamp(2.25rem, 8vw, var(--bw-text-heading-display-size));
  font-weight: var(--bw-text-heading-display-weight);
  line-height: var(--bw-text-heading-display-line-height);
  letter-spacing: var(--bw-text-heading-display-tracking);
  /* Last-resort safety net: a single word longer than the viewport (a URL, a
   * long compound) still has no wrap opportunity even at the small end. */
  overflow-wrap: break-word;
  text-wrap: balance;
  /* Fills the copy column at default (100%). Override with the copy column
   * widened when the brand wants a wider heading than the lede (#640). */
  max-inline-size: min(var(--bw-component-hero-heading-max-width), 100%);
}

/* Subheading / positioning line (#672): between heading and lede,
stronger
 * than the muted lede. Absent slot is byte-identical. */
.bw-hero__subheading {
  margin: 0;
  color: var(--bw-color-fg);
  font-family: var(--bw-text-heading-lg-family);
  font-size: var(--bw-text-heading-lg-size);
  font-weight: var(--bw-text-heading-lg-weight);
  line-height: var(--bw-text-heading-lg-line-height);
  letter-spacing: var(--bw-text-heading-lg-tracking);
  text-wrap: pretty;
  max-inline-size: min(var(--bw-component-hero-lede-max-width), 100%);
}

.bw-hero__lede {
  margin: 0;
  color: var(--bw-color-fg-muted);
  font-size: var(--bw-text-body-lg-size);
  line-height: var(--bw-text-body-lg-line-height);
  text-wrap: pretty;
  /* Fills the copy column at default (100%). When the copy column is widened
   * for the heading, set --bw-component-hero-lede-max-width to the prose
   * measure or 62ch (#640). */
  max-inline-size: min(var(--bw-component-hero-lede-max-width), 100%);
}

.bw-hero__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--bw-space-3);
}

/* Actions follow align (#672). --start is the documented default and must
 * left-align the row explicitly (flex default alone left the end case
 * ambiguous and matched a consumer report that start never moved CTAs). */
.bw-hero--start .bw-hero__actions {
  justify-content: flex-start;
}

.bw-hero--center .bw-hero__actions {
  justify-content: center;
}

.bw-hero--end .bw-hero__actions {
  justify-content: flex-end;
}

/* Quiet meta under the actions (#672). Caption muted; middle-dot seps
 * match article_meta. Absent slot is byte-identical. */
.bw-hero__meta {
  margin: 0;
  color: var(--bw-color-fg-muted);
  font-size: var(--bw-text-caption-size);
  line-height: var(--bw-text-caption-line-height);
}

.bw-hero__meta-sep {
  margin-inline: var(--bw-space-2);
}

.bw-hero__media {
  max-inline-size: 100%;
}

.bw-hero__media :where(img, svg){
  display: block;
  inline-size: 100%;
  block-size: auto;
}

/* Radius on img only,
never svg (icvoss/django-brickwork#118). A photograph
 * wants a frame; a diagram or logotype has no frame to round,
and clipping its
 * corners damages artwork the caller drew deliberately. Scoping here rather
 * than shipping a --bare opt-out modifier fixes every existing SVG caller with
 * no action on their part,
instead of making each one discover an override for
 * a .bw-* internal (the-wall EXT-013 discourages exactly that). */
.bw-hero__media img {
  border-radius: var(--bw-radius-lg);
  /* Soft frame so media reads on a white page without a separate card wrap.
   * Dark theme strips the ambient at the end of this file. */
  box-shadow:
    var(--bw-elevation-1),
    0 4px 14px -4px oklch(0 0 0 / 0.1);
}

/* Decoration (#645): a watermark/stamp behind the copy on the ordinary
 * surface. Present only when the decoration slot has content; absent is
 * byte-identical. Not media-behind: no inverse panel,
no scrim. */
.bw-hero:has(> .bw-hero__decoration) {
  position: relative;
  /* Clip a mark that hangs past the trailing edge via a negative inset. */
  overflow: clip;
}

.bw-hero:has(> .bw-hero__decoration) .bw-hero__copy {
  position: relative;
  z-index: 1;
}

.bw-hero__decoration {
  position: absolute;
  z-index: 0;
  inset-block-start: var(--bw-component-hero-decoration-inset);
  /* Hang slightly past the trailing edge (evidence: icvoss.com mark). The
   * inset token is the magnitude; the sign flips for the inline-end edge. */
  inset-inline-end: calc(var(--bw-component-hero-decoration-inset) * -1);
  inline-size: var(--bw-component-hero-decoration-size);
  max-inline-size: 50%;
  opacity: var(--bw-component-hero-decoration-opacity);
  pointer-events: none;
}

.bw-hero__decoration :where(img, svg){
  display: block;
  inline-size: 100%;
  block-size: auto;
}

/* Product chrome: framed product surface for marketing media slots (Beat S5).
 * Clean-room depth argument for hero / feature-row media: a raised panel with
 * an optional rear layer. Tokens and elevation only; not kit markup. */
.bw-product-chrome-stack {
  position: relative;
  isolation: isolate;
  inline-size: 100%;
  /* Room for the rear panel to peek without clipping the front. */
  padding-block-start: var(--bw-space-5);
  padding-inline-end: var(--bw-space-5);
}

.bw-product-chrome {
  position: relative;
  z-index: 1;
  overflow: hidden;
  inline-size: 100%;
  border-radius: var(--bw-radius-xl);
  border: var(--bw-size-border-hairline) solid
    color-mix(in oklab, var(--bw-color-fg) 12%, var(--bw-color-surface-raised));
  background: var(--bw-color-surface-raised);
  box-shadow:
    var(--bw-elevation-2),
    0 8px 28px -10px oklch(0 0 0 / 0.14);
}

.bw-product-chrome--rear {
  position: absolute;
  z-index: 0;
  inset-block-start: 0;
  inset-inline-end: 0;
  inline-size: 90%;
  block-size: 88%;
  background: var(--bw-color-surface-inverse);
  border-color: transparent;
  box-shadow: var(--bw-elevation-1);
  /* Offset only: no rotate. Decorative depth, not motion. */
  transform: translate(calc(var(--bw-space-3) * -1), var(--bw-space-3));
}

.bw-product-chrome__bar {
  display: flex;
  align-items: center;
  gap: var(--bw-space-2);
  min-block-size: var(--bw-size-control-height-sm);
  padding-inline: var(--bw-space-3);
  border-block-end: var(--bw-size-border-hairline) solid
    color-mix(in oklab, var(--bw-color-fg) 10%, transparent);
  background: var(--bw-color-surface-sunken);
}

.bw-product-chrome__title {
  margin: 0;
  color: var(--bw-color-fg-muted);
  font-family: var(--bw-text-caption-family);
  font-size: var(--bw-text-caption-size);
  font-weight: var(--bw-text-caption-weight);
  line-height: var(--bw-text-caption-line-height);
  letter-spacing: var(--bw-text-caption-tracking);
}

.bw-product-chrome__stage {
  padding: var(--bw-space-3);
}

.bw-product-chrome__stage :where(svg) {
  display: block;
  inline-size: 100%;
  block-size: auto;
}

.bw-feature-row__media .bw-product-chrome-stack,
.bw-feature-row__media .bw-product-chrome,
.bw-hero__media .bw-product-chrome-stack,
.bw-hero__media .bw-product-chrome {
  max-inline-size: 100%;
}

/* --- Hero media placement (ADR-057 section 1a,
#118) ------------------------- */

/* The media_placement axis. "below" is the default and deliberately has NO
 * modifier class: it is the shipped 2.0.0 layout (.bw-hero is a column flex,
* so media follows the copy in normal flow),
so an omitted or unrecognised
 * value renders byte-identically to before this option existed. */

/* "above": media stacks visually above the copy. Document / tab order stay
 * copy-then-media (CSS-only axis; ADR-057 section 1a binding rule). */
.bw-hero--media-above {
  flex-direction: column-reverse;
}

/* "behind": media and copy share one grid area,
copy on top. */
.bw-hero--media-behind {
  display: grid;
  position: relative;
  isolation: isolate;
  padding: var(--bw-space-16) var(--bw-space-8);
  border-radius: var(--bw-radius-2xl);
  overflow: hidden;
  background-color: var(--bw-color-surface-inverse);
  color: var(--bw-color-fg-on-inverse);
}

.bw-hero--media-behind > * {
  grid-area: 1 / 1;
}

.bw-hero--media-behind .bw-hero__copy {
  z-index: 1;
  max-inline-size: min(var(--bw-component-hero-copy-max-width), 100%);
}

/* The copy inherits the inverse foreground. The eyebrow keeps its accent role
 * elsewhere,
but accent on an inverse surface is not contrast-checked,
so it
 * takes the inverse foreground here too. */
.bw-hero--media-behind .bw-hero__eyebrow,
.bw-hero--media-behind .bw-hero__heading,
.bw-hero--media-behind .bw-hero__subheading,
.bw-hero--media-behind .bw-hero__lede,
.bw-hero--media-behind .bw-hero__meta {
  color: var(--bw-color-fg-on-inverse);
}

/* Sentence-tone eyebrow on inverse still needs on-inverse ink (#672);
 * the base sentence rule would otherwise reintroduce muted fg. */
.bw-hero--media-behind .bw-hero__eyebrow--sentence {
  color: var(--bw-color-fg-on-inverse);
}

.bw-hero--media-behind .bw-hero__media {
  position: relative;
  inline-size: 100%;
  block-size: 100%;
}

.bw-hero--media-behind .bw-hero__media :where(img, svg){
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
  border-radius: 0;
}

/* The scrim,
between the media and the copy.
 *
 * UNIFORM,
not a gradient. A gradient makes the contrast ratio depend on WHERE
 * a line of text happens to sit,
so the guarantee changes with copy length:
 * measured against a pale illustration,
a 45%-at-the-top gradient put the lede
 * at 4.25:1,
under the 4.5:1 floor of WCAG 1.4.3,
while the heading lower down
 * passed. A flat scrim makes the floor unconditional,
which is the only way
 * this band can promise contrast without knowing what media the caller passed.
 *
 * 75% is chosen with headroom,
not to the millimetre. Against pure white media
 * (the worst case) the arithmetic minimum that clears 4.5:1 is 60%,
but
 * surface-inverse resolves to var(--bw-color-fg),
which a tenant rebrands: a
 * lighter brand foreground needs a denser scrim to reach the same ratio,
and
 * 75% keeps the promise across plausible rebrands rather than only the shipped
 * palette. A uniform scrim also needs no [dir="rtl"] mirroring,
which a
 * directional gradient would. */
.bw-hero--media-behind .bw-hero__media::after {
  content: "";
  position: absolute;
  inset: 0;
  background: color-mix(in oklab, var(--bw-color-surface-inverse) 75%, transparent);
}

/* "beside": a true side-by-side row from 48rem up,
single column below it.
 * Mobile-first,
so the narrow case needs no rule (ADR-057 section 1: a section
 * that only works at desktop width is not a shipped section). */
@media (width >= 48rem) {
  .bw-hero--media-beside {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: var(--bw-space-8);
  }

  .bw-hero--media-beside .bw-hero__copy,
  .bw-hero--media-beside .bw-hero__media {
    min-inline-size: 0;
  }

  .bw-hero--media-beside .bw-hero__media {
    align-self: center;
  }
}

/* Media shape (#672): circular portrait. Opt-in via media_shape="circle";
 * omitted/"default" is byte-identical. Caps the media column so a 100%
 * width circle cannot dominate the band; square crop + full radius so
 * the portrait survives under media_placement="beside" without a
 * consumer override fighting the default img radius. SVG carve-out
 * matches the default media radius rule (img only). */
.bw-hero--media-circle .bw-hero__media {
  max-inline-size: var(--bw-component-hero-media-circle-size);
}

.bw-hero--media-circle .bw-hero__media img {
  aspect-ratio: 1;
  object-fit: cover;
  border-radius: var(--bw-radius-full);
}

.bw-hero--media-behind.bw-hero--media-circle .bw-hero__media img {
  /* Circle must win over the behind placement's border-radius: 0. */
  border-radius: var(--bw-radius-full);
}

/* --- Feature grid ------------------------------------------------------------ */

.bw-feature-grid-section__intro {
  max-inline-size: var(--bw-size-max-width-prose);
  margin-block-end: var(--bw-space-8);
}

.bw-feature-grid-section__heading {
  margin: 0 0 var(--bw-space-2);
  font-family: var(--bw-text-heading-section-family);
  font-size: var(--bw-text-heading-section-size);
  font-weight: var(--bw-text-heading-section-weight);
  line-height: var(--bw-text-heading-section-line-height);
}

.bw-feature-grid-section__lede {
  margin: 0;
  color: var(--bw-color-fg-muted);
  font-size: var(--bw-text-body-lg-size);
  line-height: var(--bw-text-body-lg-line-height);
}

.bw-feature-grid {
  display: grid;
  grid-template-columns: repeat(1, 1fr);
  gap: var(--bw-space-6);
}

/* Bordered gap-less variant (#646): cells share one rule (1px gap over the
 * border colour,
strong outer border,
no per-card radius or shadow). The
 * default gapped cards stay byte-identical when variant is omitted. */
.bw-feature-grid--bordered {
  gap: var(--bw-size-border-hairline);
  background: var(--bw-color-border);
  border: var(--bw-size-border-thick) solid var(--bw-color-border-strong);
}

@media (width >= 40rem) {
  .bw-feature-grid--2 {
    grid-template-columns: repeat(2, 1fr);
  }
  .bw-feature-grid--3 {
    grid-template-columns: repeat(2, 1fr);
  }
  .bw-feature-grid--4 {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (width >= 64rem) {
  .bw-feature-grid--3 {
    grid-template-columns: repeat(3, 1fr);
  }
  .bw-feature-grid--4 {
    grid-template-columns: repeat(4, 1fr);
  }
}

/* Feature cards inside a feature grid carry a quiet surface role
 * (icvoss/django-brickwork#510): hairline + elevation-1,
not bare
 * icon-plus-copy. Scoped to the grid so footer link groups that reuse
 * .bw-feature-card for stacking do not pick up card chrome. Edge and
 * ambient mirror the zero-kwargs .bw-card craft so tiles read on white. */
.bw-feature-grid > .bw-feature-card {
  display: flex;
  flex-direction: column;
  gap: var(--bw-space-3);
  padding: var(--bw-space-5);
  background: var(--bw-color-surface-raised);
  border: var(--bw-size-border-hairline) solid
    color-mix(in oklab, var(--bw-color-fg) 12%, var(--bw-color-surface-raised));
  border-radius: var(--bw-radius-lg);
  box-shadow:
    var(--bw-elevation-1),
    0 4px 14px -4px oklch(0 0 0 / 0.1);
  transition: var(--bw-transition-shadow);
}

/* Linked feature-grid cards raise one elevation step on hover/focus,
with
 * the same ambient stack as .bw-card--interactive. */
.bw-feature-grid > .bw-feature-card--link:hover,
.bw-feature-grid > .bw-feature-card--link:focus-visible {
  box-shadow:
    var(--bw-elevation-2),
    0 8px 20px -6px oklch(0 0 0 / 0.12);
}

/* Bordered cells sit on surface and drop per-card chrome so the shared
 * rule reads as one grid,
not a stack of cards. Hover shifts surface
 * rather than raising elevation. */
.bw-feature-grid--bordered > .bw-feature-card {
  background: var(--bw-color-surface);
  border: 0;
  border-radius: 0;
  box-shadow: none;
  transition: var(--bw-transition-colors);
}

.bw-feature-grid--bordered > .bw-feature-card--link:hover,
.bw-feature-grid--bordered > .bw-feature-card--link:focus-visible {
  background: var(--bw-color-surface-sunken);
  box-shadow: none;
}

.bw-feature-card {
  display: flex;
  flex-direction: column;
  gap: var(--bw-space-3);
}

.bw-feature-card__badge {
  align-self: flex-start;
  font-family: var(--bw-text-overline-family);
  font-size: var(--bw-text-overline-size);
  font-weight: var(--bw-text-overline-weight);
  line-height: var(--bw-text-overline-line-height);
  letter-spacing: var(--bw-text-overline-tracking);
  text-transform: uppercase;
  color: var(--bw-color-accent);
  border: var(--bw-size-border-hairline) solid var(--bw-color-accent);
  padding: var(--bw-space-1) var(--bw-space-2);
}

.bw-feature-card__eyebrow {
  margin: 0;
  color: var(--bw-color-fg-muted);
  font-family: var(--bw-text-overline-family);
  font-size: var(--bw-text-overline-size);
  font-weight: var(--bw-text-overline-weight);
  line-height: var(--bw-text-overline-line-height);
  letter-spacing: var(--bw-text-overline-tracking);
  text-transform: uppercase;
}

.bw-feature-card__icon {
  color: var(--bw-color-accent);
}

.bw-feature-card__heading {
  margin: 0;
  font-family: var(--bw-text-heading-sm-family);
  font-size: var(--bw-text-heading-sm-size);
  font-weight: var(--bw-text-heading-sm-weight);
  line-height: var(--bw-text-heading-sm-line-height);
}

.bw-feature-card__body {
  margin: 0;
  color: var(--bw-color-fg-muted);
  font-size: var(--bw-text-body-md-size);
  line-height: var(--bw-text-body-md-line-height);
}

/* Meta and CTA sit on a shared baseline at the foot of the card when the
 * body lengths differ across a row (#641). When both are present,
meta
 * takes the auto push and CTA follows under the card gap. */
.bw-feature-card__meta,
.bw-feature-card__cta {
  margin-block-start: auto;
}

.bw-feature-card__meta + .bw-feature-card__cta {
  margin-block-start: 0;
}

.bw-feature-card__meta {
  color: var(--bw-color-fg-muted);
  font-size: var(--bw-text-caption-size);
  line-height: var(--bw-text-caption-line-height);
}

.bw-feature-card__cta {
  color: var(--bw-color-accent);
  font-family: var(--bw-text-overline-family);
  font-size: var(--bw-text-overline-size);
  font-weight: var(--bw-text-overline-weight);
  line-height: var(--bw-text-overline-line-height);
  letter-spacing: var(--bw-text-overline-tracking);
  text-transform: uppercase;
}

/* Pending (#641): reduced emphasis via the muted opacity token. Without a
 * url the card is already non-interactive; with a url it stays a link. */
.bw-feature-card--pending {
  opacity: var(--bw-opacity-muted);
}

.bw-feature-grid--bordered > .bw-feature-card--pending:hover,
.bw-feature-grid--bordered > .bw-feature-card--pending:focus-visible {
  background: var(--bw-color-surface);
}

/* Linked feature card (#99): the whole card is an anchor (a.bw-card's
 * contract: content keeps its own ink,
no underline smears across the card;
 * the focus ring comes from the global :focus-visible rule). Inside a feature
 * grid the card also carries the quiet surface chrome above; the hover
 * affordance remains the marketing kit's link ink treatment on the heading
 * (accent ink with the standard colour transition). Colour is reinforcement
 * only; the pointer cursor and focus ring carry the interactivity signal. */
.bw-feature-card--link {
  color: inherit;
  text-decoration: none;
}

.bw-feature-card--link .bw-feature-card__heading {
  transition: var(--bw-transition-colors);
}

.bw-feature-card--link:hover .bw-feature-card__heading,
.bw-feature-card--link:focus-visible .bw-feature-card__heading {
  color: var(--bw-color-accent);
}

/* --- Pricing ------------------------------------------------------------------ */

.bw-pricing-table-section__intro {
  max-inline-size: var(--bw-size-max-width-prose);
  margin-block-end: var(--bw-space-6);
  text-align: center;
  margin-inline: auto;
}

.bw-pricing-table-section__heading {
  margin: 0 0 var(--bw-space-2);
  font-family: var(--bw-text-heading-section-family);
  font-size: var(--bw-text-heading-section-size);
  font-weight: var(--bw-text-heading-section-weight);
  line-height: var(--bw-text-heading-section-line-height);
}

.bw-pricing-table-section__lede {
  margin: 0;
  color: var(--bw-color-fg-muted);
  font-size: var(--bw-text-body-lg-size);
  line-height: var(--bw-text-body-lg-line-height);
}

.bw-pricing-table {
  display: grid;
  grid-template-columns: repeat(1, 1fr);
  gap: var(--bw-space-6);
  align-items: stretch;
}

@media (width >= 48rem) {
  .bw-pricing-table {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (width >= 64rem) {
  .bw-pricing-table {
    grid-template-columns: repeat(3, 1fr);
  }
}

.bw-pricing-table--single {
  max-inline-size: 24rem;
  margin-inline: auto;
}

.bw-pricing-table__note {
  margin: var(--bw-space-4) 0 0;
  text-align: center;
  color: var(--bw-color-fg-muted);
  font-size: var(--bw-text-caption-size);
  line-height: var(--bw-text-caption-line-height);
}

.bw-pricing-tier {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--bw-space-4);
  background: var(--bw-color-surface);
  /* Edge and ambient match zero-kwargs .bw-card so tiers read on white. */
  border: var(--bw-size-border-hairline) solid
    color-mix(in oklab, var(--bw-color-fg) 12%, var(--bw-color-surface));
  border-radius: var(--bw-radius-lg);
  box-shadow:
    var(--bw-elevation-1),
    0 4px 14px -4px oklch(0 0 0 / 0.1);
  padding: var(--bw-space-6);
}

/* The recommended tier (04-interfaces.md 4d): raised elevation plus an
 * accent border,
never colour alone to signal "recommended" (the badge
 * text itself carries that meaning; the border/elevation are reinforcement). */
.bw-pricing-tier--highlighted {
  border-color: var(--bw-color-accent);
  box-shadow:
    var(--bw-elevation-3),
    0 12px 28px -8px oklch(0 0 0 / 0.14);
}

.bw-pricing-tier__badge {
  position: absolute;
  inset-block-start: calc(var(--bw-space-4) * -1);
  inset-inline-start: var(--bw-space-6);
}

.bw-pricing-tier__name {
  margin: 0;
  font-family: var(--bw-text-heading-sm-family);
  font-size: var(--bw-text-heading-sm-size);
  font-weight: var(--bw-text-heading-sm-weight);
  line-height: var(--bw-text-heading-sm-line-height);
}

.bw-pricing-tier__description {
  margin: 0;
  color: var(--bw-color-fg-muted);
  font-size: var(--bw-text-body-sm-size);
  line-height: var(--bw-text-body-sm-line-height);
}

.bw-pricing-tier__price {
  display: flex;
  align-items: baseline;
  gap: var(--bw-space-1);
  margin: 0;
}

.bw-pricing-tier__price-value {
  font-size: var(--bw-font-size-3xl);
  font-weight: var(--bw-font-weight-bold);
  line-height: var(--bw-font-line-height-tight);
  font-variant-numeric: tabular-nums;
}

.bw-pricing-tier__price-period {
  color: var(--bw-color-fg-muted);
  font-size: var(--bw-text-body-sm-size);
}

.bw-pricing-tier__features {
  display: flex;
  flex-direction: column;
  gap: var(--bw-space-2);
  margin: 0;
  padding: 0;
  list-style: none;
  flex: 1 1 auto;
}

.bw-pricing-tier__feature {
  display: flex;
  align-items: flex-start;
  gap: var(--bw-space-2);
  font-size: var(--bw-text-body-sm-size);
  line-height: var(--bw-text-body-sm-line-height);
}

.bw-pricing-tier__feature-icon {
  color: var(--bw-color-success);
  flex-shrink: 0;
  margin-block-start: 0.15em;
}

.bw-pricing-tier__cta {
  margin-block-start: auto;
  padding-block-start: var(--bw-space-2);
}

.bw-pricing-tier__cta .bw-btn {
  inline-size: 100%;
}

/* --- CTA band ------------------------------------------------------------------ */

.bw-cta {
  padding-block: var(--bw-space-8);
  border-radius: var(--bw-radius-xl);
  text-align: center;
}

/* Tinted band background: the subtle marketing surface token,
contrast-checked
 * in both themes (BR-BW-MKT-002). Edge and ambient so the tint reads on white
 * without relying on fill alone; bleed keeps radius 0 via .bw-cta--bleed. */
.bw-cta--tint {
  background: var(--bw-color-surface-marketing-tint);
  border: var(--bw-size-border-hairline) solid
    color-mix(in oklab, var(--bw-color-fg) 12%, var(--bw-color-surface-marketing-tint));
  box-shadow:
    var(--bw-elevation-1),
    0 4px 14px -4px oklch(0 0 0 / 0.1);
  padding-inline: var(--bw-space-8);
}

/* width="bleed" CTA extras on top of the shared .bw-section--bleed /
 * .bw-cta--bleed escape (#667). Declared AFTER .bw-cta--tint so a tinted
 * full-bleed band takes THIS rule's inline gutter rather than tint's
 * --bw-space-8 (specificity-tie / source-order,
ADR-057 §1a). CTA keeps
 * gutter on the outer because its __inner is the prose measure,
not the
 * marketing rail; custom bands using .bw-section put gutter on __inner. */
.bw-cta--bleed {
  padding-inline: var(--bw-density-page-gutter-inline);
  border-radius: 0;
}

.bw-cta__inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--bw-space-4);
  max-inline-size: var(--bw-size-max-width-prose);
  margin-inline: auto;
}

.bw-cta__heading {
  margin: 0;
  font-family: var(--bw-text-heading-section-family);
  font-size: var(--bw-text-heading-section-size);
  font-weight: var(--bw-text-heading-section-weight);
  line-height: var(--bw-text-heading-section-line-height);
  text-wrap: balance;
}

.bw-cta__body {
  margin: 0;
  color: var(--bw-color-fg-muted);
  font-size: var(--bw-text-body-lg-size);
  line-height: var(--bw-text-body-lg-line-height);
  text-wrap: pretty;
}

.bw-cta__actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--bw-space-3);
}

/* align="start" (#697): quiet contact-foot stack. Centre remains the
 * default (no modifier). Pair with band="plain" for a soft left close. */
.bw-cta--align-start {
  text-align: start;
}

.bw-cta--align-start .bw-cta__inner {
  align-items: flex-start;
  margin-inline: 0;
}

.bw-cta--align-start .bw-cta__actions {
  justify-content: flex-start;
}

/* --- Testimonial ---------------------------------------------------------------- */

/* No blanket `margin: 0` on the root (#86): that reset ties with the shell's
 * `.bw-marketing__content > * + *` section-gap rule on specificity and,
being
 * later in source order,
wins the tie,
permanently zeroing the section gap
 * above any composed testimonial (the stat-band/testimonial collision). The
 * UA <figure> block margins are neutralised at ZERO specificity instead,
so
 * the shell's section rhythm always wins when the testimonial is composed as
 * a marketing section,
and a one-class consumer override still wins here. */
:where(.bw-testimonial) {
  margin-block: 0;
}

.bw-testimonial {
  display: flex;
  flex-direction: column;
  gap: var(--bw-space-6);
  background: var(--bw-color-surface);
  /* Edge and ambient match zero-kwargs .bw-card so quotes read on white. */
  border: var(--bw-size-border-hairline) solid
    color-mix(in oklab, var(--bw-color-fg) 12%, var(--bw-color-surface));
  border-radius: var(--bw-radius-lg);
  box-shadow:
    var(--bw-elevation-1),
    0 4px 14px -4px oklch(0 0 0 / 0.1);
  padding: var(--bw-space-8);
  max-inline-size: var(--bw-size-max-width-prose);
  margin-inline: auto;
}

.bw-testimonial__quote {
  margin: 0;
  font-family: var(--bw-text-heading-sm-family);
  font-size: var(--bw-text-heading-sm-size);
  font-weight: var(--bw-font-weight-medium);
  line-height: var(--bw-text-heading-lg-line-height);
  text-wrap: pretty;
}

.bw-testimonial__quote p {
  margin: 0;
}

.bw-testimonial__quote p::before {
  content: "\201C";
}

.bw-testimonial__quote p::after {
  content: "\201D";
}

.bw-testimonial__attribution {
  display: flex;
  align-items: center;
  gap: var(--bw-space-3);
}

.bw-testimonial__avatar {
  inline-size: var(--bw-space-10);
  block-size: var(--bw-space-10);
  border-radius: var(--bw-radius-full);
  object-fit: cover;
  flex-shrink: 0;
}

.bw-testimonial__attribution-text {
  display: flex;
  flex-direction: column;
}

.bw-testimonial__author {
  font-weight: var(--bw-font-weight-semibold);
}

.bw-testimonial__role {
  color: var(--bw-color-fg-muted);
  font-size: var(--bw-text-caption-size);
}

.bw-testimonial__logo {
  margin-inline-start: auto;
}

.bw-testimonial__logo :where(img, svg){
  block-size: var(--bw-space-6);
  inline-size: auto;
}

/* --- Logo cloud ------------------------------------------------------------------ */

.bw-logo-cloud {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--bw-space-6);
  text-align: center;
}

.bw-logo-cloud__heading {
  margin: 0;
  color: var(--bw-color-fg-muted);
  font-family: var(--bw-text-overline-family);
  font-size: var(--bw-text-caption-size);
  font-weight: var(--bw-text-overline-weight);
  letter-spacing: var(--bw-text-overline-tracking);
  text-transform: uppercase;
}

.bw-logo-cloud__grid {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  gap: var(--bw-space-8);
}

.bw-logo-cloud__logo {
  block-size: var(--bw-space-8);
  inline-size: auto;
  object-fit: contain;
}

/* Purely decorative (never the sole signal of anything); colour-on-hover,
so
 * a greyscale logo is still perfectly legible without the effect. */
.bw-logo-cloud--greyscale .bw-logo-cloud__logo {
  filter: grayscale(100%);
  opacity: var(--bw-opacity-muted);
  transition: var(--bw-transition-colors);
}

.bw-logo-cloud--greyscale .bw-logo-cloud__logo:hover {
  filter: grayscale(0%);
  opacity: 1;
}

@media (prefers-reduced-motion: reduce) {
  .bw-logo-cloud--greyscale .bw-logo-cloud__logo {
    transition: none;
  }
}

/* --- Stat band --------------------------------------------------------------------- */

/* Soft band surface so stats read as a distinct act between features and
 * testimonial (icvoss/django-brickwork#510),
using the marketing tint token
 * already contrast-checked for CTA bands. Edge and a light ambient keep the
 * band from dissolving into a white page. */
.bw-stat-band-section {
  text-align: center;
  padding-block: var(--bw-space-10);
  padding-inline: var(--bw-space-6);
  background: var(--bw-color-surface-marketing-tint);
  border: var(--bw-size-border-hairline) solid
    color-mix(in oklab, var(--bw-color-fg) 12%, var(--bw-color-surface-marketing-tint));
  border-radius: var(--bw-radius-xl);
  box-shadow:
    var(--bw-elevation-1),
    0 4px 14px -4px oklch(0 0 0 / 0.1);
}

/* align="start" (#673): airy proof-band layout. The default (omitted /
 * "center") emits no modifier and stays the centred KPI layout above. */
.bw-stat-band-section--align-start {
  text-align: start;
}

.bw-stat-band-section__heading {
  margin: 0 0 var(--bw-space-6);
  font-family: var(--bw-text-heading-section-family);
  font-size: var(--bw-text-heading-section-size);
  font-weight: var(--bw-text-heading-section-weight);
  line-height: var(--bw-text-heading-section-line-height);
}

/* Display-scale band,
distinct from patterns/dashboard.html's dense KPI grid
 * (bw-stat-grid): fewer,
larger tiles read as a marketing statement,
so the
 * band uses a wider auto-fit floor and centres its tiles (composes
 * _stat.html's own tile shape unchanged,
BR-BW-MKT-004). */
.bw-stat-band {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(16rem, 100%), 1fr));
  gap: var(--bw-space-6);
  justify-items: center;
}

.bw-stat-band-section--align-start .bw-stat-band {
  justify-items: start;
}

/* Tile craft is scoped to the marketing band only; global .bw-stat is
 * unchanged in this pass (Phase A.3). */
.bw-stat-band .bw-stat {
  text-align: center;
  align-items: center;
  inline-size: 100%;
  max-inline-size: 20rem;
  border: var(--bw-size-border-hairline) solid
    color-mix(in oklab, var(--bw-color-fg) 12%, var(--bw-color-surface));
  box-shadow:
    var(--bw-elevation-1),
    0 4px 14px -4px oklch(0 0 0 / 0.1);
}

.bw-stat-band-section--align-start .bw-stat-band .bw-stat {
  text-align: start;
  align-items: flex-start;
}

/* chrome="plain" (#694): strip the marketing band's re-applied card edge
 * so proof figures stay bare when the tile itself opts out of chrome. */
.bw-stat-band .bw-stat--chrome-plain {
  border: none;
  box-shadow: none;
  background: transparent;
  padding: 0;
  max-inline-size: none;
}

/* --- FAQ ------------------------------------------------------------------------------ */

.bw-faq-section {
  max-inline-size: var(--bw-size-max-width-prose);
  margin-inline: auto;
}

.bw-faq-section__heading {
  margin: 0 0 var(--bw-space-4);
  font-size: var(--bw-text-heading-section-size);
  font-weight: var(--bw-text-heading-section-weight);
  line-height: var(--bw-text-heading-section-line-height);
  text-align: center;
}

.bw-faq-section__list {
  display: flex;
  flex-direction: column;
}

/* Features / simple list --------------------------------------------------- */

.bw-feature-list-section {
  display: flex;
  flex-direction: column;
}

.bw-feature-list-section__heading {
  margin: 0 0 var(--bw-space-6);
  font-size: var(--bw-text-heading-section-size);
  font-weight: var(--bw-text-heading-section-weight);
  line-height: var(--bw-text-heading-section-line-height);
  letter-spacing: var(--bw-text-heading-section-tracking);
}

.bw-feature-list {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--bw-space-3);
  margin: 0;
  /* The list markup stays a <ul> for its semantics; the bullets go because the
   * tick icon is the marker. */
  padding: 0;
  list-style: none;
}

.bw-feature-list__item {
  display: flex;
  align-items: flex-start;
  gap: var(--bw-space-3);
  font-size: var(--bw-text-body-md-size);
  line-height: var(--bw-text-body-md-line-height);
}

.bw-feature-list__marker {
  color: var(--bw-color-success);
  /* Optical alignment: the tick centres on the first LINE of a wrapping item
   * rather than on the item box, which would drift down as the text wraps. */
  margin-block-start: 0.15em;
}

@media (width >= 40rem) {
  .bw-feature-list {
    grid-template-columns: 1fr 1fr;
    gap: var(--bw-space-4) var(--bw-space-8);
  }
}

/* Features / alternating rows ---------------------------------------------- */

.bw-feature-rows {
  display: flex;
  flex-direction: column;
  gap: var(--bw-space-12);
}

.bw-feature-rows__heading {
  margin: 0;
  font-size: var(--bw-text-heading-section-size);
  font-weight: var(--bw-text-heading-section-weight);
  line-height: var(--bw-text-heading-section-line-height);
  letter-spacing: var(--bw-text-heading-section-tracking);
  text-wrap: balance;
}

.bw-feature-row {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--bw-space-6);
  align-items: center;
  /* Grid items default to min-width: auto (content min-size). Full-page
     specimens inside media (min-inline-size ~64rem) would otherwise expand
     the page and fail mobile sideways-scroll gates. */
  min-inline-size: 0;
}

.bw-feature-row__copy {
  display: flex;
  flex-direction: column;
  gap: var(--bw-space-3);
  max-inline-size: var(--bw-size-max-width-prose);
  min-inline-size: 0;
}

.bw-feature-row__title {
  margin: 0;
  font-size: var(--bw-text-heading-lg-size);
  font-weight: var(--bw-text-heading-lg-weight);
  line-height: var(--bw-text-heading-lg-line-height);
}

.bw-feature-row__body {
  margin: 0;
  color: var(--bw-color-fg-muted);
  line-height: var(--bw-text-body-lg-line-height);
  text-wrap: pretty;
}

.bw-feature-row__media {
  min-inline-size: 0;
  max-inline-size: 100%;
  overflow: auto;
}

.bw-feature-row__media :where(img, svg){
  display: block;
  inline-size: 100%;
  block-size: auto;
  border-radius: var(--bw-radius-lg);
}

@media (width >= 48rem) {
  .bw-feature-row {
    grid-template-columns: 1fr 1fr;
    gap: var(--bw-space-12);
  }

  /* The zig-zag. Applied ONLY at this breakpoint and above, and done with grid
   * placement rather than by reordering the markup, so the DOM order (copy,
   * then media) is the reading and tab order on every row. Below this width
   * every row stacks copy-first, because a media-first stack would drop a
   * picture between a heading and its own paragraph. */
  .bw-feature-row:nth-child(even) .bw-feature-row__copy {
    grid-column: 2;
    grid-row: 1;
  }

  .bw-feature-row:nth-child(even) .bw-feature-row__media {
    grid-column: 1;
    grid-row: 1;
  }
}

/* CTA / split -------------------------------------------------------------- */

.bw-cta-split {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--bw-space-6);
  padding: var(--bw-space-8);
  border: var(--bw-size-border-hairline) solid var(--bw-color-border);
  border-radius: var(--bw-radius-xl);
  background: var(--bw-color-surface-sunken);
}

.bw-cta-split__copy {
  display: flex;
  flex-direction: column;
  gap: var(--bw-space-2);
  max-inline-size: var(--bw-size-max-width-prose);
}

.bw-cta-split__heading {
  margin: 0;
  font-size: var(--bw-text-heading-lg-size);
  font-weight: var(--bw-text-heading-lg-weight);
  line-height: var(--bw-text-heading-lg-line-height);
}

.bw-cta-split__body {
  margin: 0;
  color: var(--bw-color-fg-muted);
  text-wrap: pretty;
}

.bw-cta-split__actions {
  flex-shrink: 0;
}

@media (width >= 48rem) {
  .bw-cta-split {
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
    gap: var(--bw-space-8);
  }
}

/* CTA / full-bleed --------------------------------------------------------- */

/* Escape size/margin come from the shared .bw-section--bleed / .bw-cta-bleed
 * rule (#667). This block keeps the inverse band treatment and its own
 * gutter (prose __inner,
same shape as .bw-cta--bleed). */
.bw-cta-bleed {
  padding-block: var(--bw-space-16);
  /* Its own gutter: having escaped the shell's, the copy would otherwise touch
   * the viewport edge on a phone. */
  padding-inline: var(--bw-density-page-gutter-inline);
  background: var(--bw-color-surface-inverse);
  color: var(--bw-color-fg-on-inverse);
}

.bw-cta-bleed__inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--bw-space-4);
  max-inline-size: var(--bw-size-max-width-prose);
  margin-inline: auto;
  text-align: center;
}

.bw-cta-bleed__heading {
  margin: 0;
  color: var(--bw-color-fg-on-inverse);
  font-family: var(--bw-text-heading-display-family);
  font-size: clamp(1.75rem, 6vw, var(--bw-font-size-3xl));
  overflow-wrap: break-word;
  font-weight: var(--bw-text-heading-display-weight);
  line-height: var(--bw-text-heading-display-line-height);
  letter-spacing: var(--bw-text-heading-display-tracking);
  text-wrap: balance;
}

.bw-cta-bleed__body {
  margin: 0;
  color: var(--bw-color-fg-on-inverse);
  opacity: 0.9;
  font-size: var(--bw-text-body-lg-size);
  text-wrap: pretty;
}

.bw-cta-bleed__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: var(--bw-space-5);
  margin-block-start: var(--bw-space-2);
}

/* The quiet second action. A plain link rather than a secondary button: the
 * secondary button's border colour is tuned for the page surface and reads as
 * a smudge on the inverse band. Underlined,
so it is not colour-only. */
.bw-cta-bleed__link {
  color: var(--bw-color-fg-on-inverse);
  text-decoration: underline;
  text-underline-offset: 0.2em;
  font-weight: var(--bw-font-weight-medium);
}

/* Content / media and text ------------------------------------------------- */

.bw-media-text {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--bw-space-8);
  align-items: center;
}

.bw-media-text__figure {
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: var(--bw-space-2);
}

.bw-media-text__figure :where(img, svg){
  display: block;
  inline-size: 100%;
  block-size: auto;
  border-radius: var(--bw-radius-lg);
}

.bw-media-text__figure :where(figcaption) {
  color: var(--bw-color-fg-muted);
  font-size: var(--bw-text-caption-size);
  line-height: var(--bw-text-caption-line-height);
}

@media (width >= 48rem) {
  .bw-media-text {
    grid-template-columns: 1fr 1fr;
    gap: var(--bw-space-12);
  }
}

/* Content / prose section -------------------------------------------------- */

.bw-content-section > .bw-prose {
  margin-inline: auto;
}

/* --- Pricing / single plan ---------------------------------------------------- */

/* The one-plan shape: no table,
because there is nothing to compare. The card
 * is capped at prose width and centred so it does not stretch into a banner on
 * a wide viewport. */
.bw-single-plan__intro {
  max-inline-size: var(--bw-size-max-width-prose);
  margin-inline: auto;
  margin-block-end: var(--bw-space-8);
  text-align: center;
}

.bw-single-plan__heading {
  margin: 0;
  font-size: var(--bw-text-heading-section-size);
  font-weight: var(--bw-text-heading-section-weight);
  line-height: var(--bw-text-heading-section-line-height);
  letter-spacing: var(--bw-text-heading-section-tracking);
}

.bw-single-plan__lede {
  margin: var(--bw-space-3) 0 0;
  color: var(--bw-color-fg-muted);
  text-wrap: pretty;
}

.bw-single-plan__card {
  max-inline-size: 26rem;
  margin-inline: auto;
}

/* The "what is included" list sits under the card rather than inside it,
so the
 * card stays a price and an action while the list can run two-up on a wide
 * screen (the base .bw-feature-list already does that at 48rem). */
.bw-single-plan__includes {
  max-inline-size: var(--bw-size-max-width-prose);
  margin-block-start: var(--bw-space-8);
  margin-inline: auto;
}

.bw-single-plan__note {
  margin: var(--bw-space-6) 0 0;
  color: var(--bw-color-fg-muted);
  font-size: var(--bw-text-body-sm-size);
  text-align: center;
}

/* Pricing / comparison table CSS lives in components.css as
 * .bw-comparison-table* (icvoss/django-brickwork#626),
with
 * .bw-pricing-comparison* kept as aliases for hand-authored markup.
 */

/* --- FAQ / two column ----------------------------------------------------------- */

.bw-faq-columns__heading {
  margin: 0 0 var(--bw-space-8);
  font-size: var(--bw-text-heading-section-size);
  font-weight: var(--bw-text-heading-section-weight);
  line-height: var(--bw-text-heading-section-line-height);
  letter-spacing: var(--bw-text-heading-section-tracking);
}

/* One column on a phone,
two from 48rem. Each column is its own exclusive
 * <details> group in the markup,
so opening a question on the right cannot
 * close one off-screen on the left. */
.bw-faq-columns__grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--bw-space-4) var(--bw-space-8);
  align-items: start;
}

@media (width >= 48rem) {
  .bw-faq-columns__grid {
    grid-template-columns: 1fr 1fr;
  }
}

.bw-faq-columns__column {
  display: grid;
  gap: var(--bw-space-4);
  align-content: start;
}

/* --- Testimonial / quote grid ---------------------------------------------------- */

.bw-testimonial-grid-section__heading {
  margin: 0 0 var(--bw-space-8);
  font-size: var(--bw-text-heading-section-size);
  font-weight: var(--bw-text-heading-section-weight);
  line-height: var(--bw-text-heading-section-line-height);
  letter-spacing: var(--bw-text-heading-section-tracking);
}

.bw-testimonial-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(18rem, 100%), 1fr));
  gap: var(--bw-space-6);
  align-items: start;
}

/* .bw-testimonial caps itself at prose width and centres,
which is right for a
 * lone quote and wrong inside a grid track: it would leave gaps between the
 * cards. The track is the constraint here. */
.bw-testimonial-grid .bw-testimonial {
  max-inline-size: none;
  margin-inline: 0;
  block-size: 100%;
}

/* --- Testimonial / logo and quote ------------------------------------------------ */

.bw-testimonial-logo {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--bw-space-6);
}

/* The mark carries a viewBox and no intrinsic width,
so the cap lives here.
 * currentColor in the SVG resolves through this rule's inherited ink,
which is
 * what lets one file work in both themes. */
.bw-testimonial-logo__mark {
  color: var(--bw-color-fg-muted);
  inline-size: 100%;
  max-inline-size: 10rem;
}

.bw-testimonial-logo__mark svg {
  inline-size: 100%;
  block-size: auto;
}

/* --- Stats / card row ------------------------------------------------------------ */

.bw-stat-cards-section__heading {
  margin: 0 0 var(--bw-space-8);
  font-size: var(--bw-text-heading-section-size);
  font-weight: var(--bw-text-heading-section-weight);
  line-height: var(--bw-text-heading-section-line-height);
  letter-spacing: var(--bw-text-heading-section-tracking);
}

.bw-stat-cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(16rem, 100%), 1fr));
  gap: var(--bw-space-6);
}

/* The card treatment the bare .bw-stat-band deliberately does not carry: this
 * variant exists for pages with no other card content,
where a naked number
 * band reads as unfinished. */
.bw-stat-card {
  display: flex;
  flex-direction: column;
  gap: var(--bw-space-2);
  padding: var(--bw-space-8);
  border: var(--bw-size-border-hairline) solid var(--bw-color-border);
  border-radius: var(--bw-radius-lg);
  background: var(--bw-color-surface-raised);
  box-shadow: var(--bw-elevation-1);
}

.bw-stat-card__value {
  margin: 0;
  font-size: var(--bw-text-heading-xl-size);
  font-weight: var(--bw-text-heading-xl-weight);
  line-height: var(--bw-text-heading-xl-line-height);
  /* Lining figures so a column of numbers aligns on the digit, not the glyph. */
  font-variant-numeric: tabular-nums;
}

.bw-stat-card__label {
  margin: 0;
  color: var(--bw-color-fg);
  text-wrap: pretty;
}

/* Pushed to the bottom so the notes sit on one baseline across a row whose
 * labels wrap to different heights. */
.bw-stat-card__note {
  margin: 0;
  margin-block-start: auto;
  padding-block-start: var(--bw-space-2);
  color: var(--bw-color-fg-muted);
  font-size: var(--bw-text-caption-size);
}

/* --- Listing ------------------------------------------------------------------ */

/* The three listing variants (card grid,
media list,
compact table) share this
 * section wrapper and NOTHING else: the shared thing between them turned out
 * to be the entry contract,
not the layout,
which is why no grid component was
 * promoted out of them. See the comment in examples/sections/listing/
 * media-list.html for that reasoning in full. */
.bw-listing-section__intro {
  max-inline-size: var(--bw-size-max-width-prose);
  margin-block-end: var(--bw-space-6);
}

.bw-listing-section__heading {
  margin: 0;
  font-size: var(--bw-text-heading-section-size);
  font-weight: var(--bw-text-heading-section-weight);
  line-height: var(--bw-text-heading-section-line-height);
}

.bw-listing-section__lede {
  margin: var(--bw-space-2) 0 0;
  color: var(--bw-color-fg-muted);
  text-wrap: pretty;
}

/* Listing / card grid ------------------------------------------------------ */

/* The same auto-fit idiom as .bw-stat-grid and .bw-stat-band,
with a wider
 * floor: a card carrying a title and a summary paragraph stops reading as a
 * card below about 18rem. min() keeps a single column on viewports narrower
 * than one card,
which is what makes this mobile-first with no breakpoint. */
.bw-listing-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(18rem, 100%), 1fr));
  gap: var(--bw-space-6);
}

/* The whole card is an anchor,
so it follows a.bw-card's contract exactly as
 * .bw-feature-card--link does: content keeps its own ink,
no underline smears
 * across the card,
and the focus ring comes from the global :focus-visible
 * rule. Unlike the chrome-less feature card this one has a surface,
so the
 * hover affordance is the elevation raise (shadow only,
never a transform,
* per MOT-010). */
.bw-listing-card {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--bw-space-2);
  padding: var(--bw-space-6);
  border: var(--bw-size-border-hairline) solid var(--bw-color-border);
  border-radius: var(--bw-radius-lg);
  background: var(--bw-color-surface-raised);
  box-shadow: var(--bw-elevation-1);
  color: inherit;
  text-decoration: none;
  transition: var(--bw-transition-shadow);
}

.bw-listing-card:hover,
.bw-listing-card:focus-visible {
  box-shadow: var(--bw-elevation-2);
}

.bw-listing-card__tag {
  font-size: var(--bw-text-overline-size);
  font-weight: var(--bw-text-overline-weight);
  line-height: var(--bw-text-overline-line-height);
  letter-spacing: var(--bw-text-overline-tracking);
  text-transform: uppercase;
  color: var(--bw-color-accent);
}

.bw-listing-card__title {
  margin: 0;
  font-family: var(--bw-text-heading-sm-family);
  font-size: var(--bw-text-heading-sm-size);
  font-weight: var(--bw-text-heading-sm-weight);
  line-height: var(--bw-text-heading-sm-line-height);
  transition: var(--bw-transition-colors);
}

.bw-listing-card:hover .bw-listing-card__title,
.bw-listing-card:focus-visible .bw-listing-card__title {
  color: var(--bw-color-accent);
}

.bw-listing-card__summary {
  margin: 0;
  color: var(--bw-color-fg-muted);
  font-size: var(--bw-text-body-md-size);
  line-height: var(--bw-text-body-md-line-height);
  text-wrap: pretty;
}

/* Pushed to the bottom so meta lines sit on one baseline across a row of
 * cards whose summaries differ in length. */
.bw-listing-card__meta {
  margin-block-start: auto;
  padding-block-start: var(--bw-space-2);
  color: var(--bw-color-fg-muted);
  font-size: var(--bw-text-caption-size);
}

/* Listing / media list ----------------------------------------------------- */

.bw-listing-list {
  display: grid;
  gap: var(--bw-space-8);
  margin: 0;
  padding: 0;
  list-style: none;
}

/* Stacked on a phone (image first,
matching the reading order),
a media row
 * from 40rem up. The image column is fixed rather than fractional so a short
 * entry and a long one present the same thumbnail size. */
.bw-listing-list__item {
  display: grid;
  gap: var(--bw-space-4);
  align-items: start;
}

/* 12rem,
not wider: at a 3:2 ratio the thumbnail sets the row height,
and a
 * wider column leaves a two-line summary floating in dead space beside it. */
@media (width >= 40rem) {
  .bw-listing-list__item {
    grid-template-columns: 12rem 1fr;
    gap: var(--bw-space-6);
  }
}

/* Sized for either shape: the shipped placeholder is an inline <svg>,
and the
 * documented swap is an <img>. object-fit only bites on the img,
and the
 * aspect-ratio keeps the row height stable across both so swapping one for the
 * other does not reflow the list. */
.bw-listing-list__media {
  inline-size: 100%;
  block-size: auto;
  aspect-ratio: 3 / 2;
  object-fit: cover;
  border-radius: var(--bw-radius-lg);
}

.bw-listing-list__body {
  display: flex;
  flex-direction: column;
  gap: var(--bw-space-2);
  align-items: flex-start;
}

.bw-listing-list__title {
  margin: 0;
  font-family: var(--bw-text-heading-sm-family);
  font-size: var(--bw-text-heading-sm-size);
  font-weight: var(--bw-text-heading-sm-weight);
  line-height: var(--bw-text-heading-sm-line-height);
}

/* Only the title is the link here,
not the whole row: the row is wide enough
 * that a full-row target would swallow the surrounding whitespace.
 * min-block-size (icvoss/django-brickwork#316): the heading-sm line-height
 * alone renders at 17px,
7px under the WCAG 2.5.8 24px floor,
since this is
 * a text-run link (line-height),
not a fixed-shape control. display: block
 * is safe because the link is the sole content of its own <h3> in the
 * shipped markup (media-list.html),
so there is no sibling inline content
 * on the same line to disturb; the same min-block-size: 1.5rem pattern as
 * .bw-data-table__row-link (#208). */
.bw-listing-list__link {
  display: block;
  min-block-size: 1.5rem;
  color: inherit;
  text-decoration: none;
  transition: var(--bw-transition-colors);
}

.bw-listing-list__link:hover,
.bw-listing-list__link:focus-visible {
  color: var(--bw-color-accent);
  text-decoration: underline;
}

.bw-listing-list__summary {
  margin: 0;
  color: var(--bw-color-fg-muted);
  text-wrap: pretty;
}

.bw-listing-list__meta {
  color: var(--bw-color-fg-muted);
  font-size: var(--bw-text-caption-size);
}

/* Listing / compact table -------------------------------------------------- */

/* A table cannot reflow into one column without ceasing to be a table,
so it
 * scrolls inside its own container rather than pushing the page sideways on a
 * phone. The container is focusable in the markup (tabindex="0") because a
 * scroll region reachable only by mouse or touch is unusable by keyboard; the
 * focus ring comes free from the global :focus-visible rule,
whose selector
 * already includes [tabindex]. */
.bw-listing-table__scroll {
  overflow-x: auto;
  /* See the note on .bw-comparison-table__scroll: overflow-x alone does not
   * clip a wide table's paint, so the page scrolls sideways instead of the
   * container. This table's columns happen to be narrow enough not to trip the
   * mobile gate today, which makes it a latent version of the same defect
   * rather than a different case: one longer document title would surface it. */
  contain: paint;
  border: var(--bw-size-border-hairline) solid var(--bw-color-border);
  border-radius: var(--bw-radius-lg);
}

.bw-listing-table {
  inline-size: 100%;
  border-collapse: collapse;
  font-size: var(--bw-text-body-md-size);
}

/* Visible on purpose: the caption names the table for everyone,
not only for
 * a screen reader. */
.bw-listing-table__caption {
  padding: var(--bw-space-4) var(--bw-space-4) 0;
  color: var(--bw-color-fg-muted);
  font-size: var(--bw-text-body-sm-size);
  text-align: start;
  caption-side: top;
}

.bw-listing-table th,
.bw-listing-table td {
  padding: var(--bw-space-3) var(--bw-space-4);
  text-align: start;
  vertical-align: top;
  border-block-end: var(--bw-size-border-hairline) solid var(--bw-color-border);
}

.bw-listing-table thead th {
  color: var(--bw-color-fg-muted);
  font-size: var(--bw-text-label-size);
  font-weight: var(--bw-text-label-weight);
  white-space: nowrap;
}

/* The row header carries the entry title,
so it stays at body weight rather
 * than inheriting the thead treatment. */
.bw-listing-table tbody th {
  font-weight: var(--bw-text-body-md-weight);
}

.bw-listing-table tbody tr:last-child th,
.bw-listing-table tbody tr:last-child td {
  border-block-end: none;
}

.bw-listing-table__link {
  color: var(--bw-color-accent);
  text-decoration: none;
}

.bw-listing-table__link:hover,
.bw-listing-table__link:focus-visible {
  text-decoration: underline;
}

/* --- Dark-theme resets for light-only marketing ambient (beautiful defaults) -- */

/* Soft ambient under elevation-* is a LIGHT-theme treatment so marketing
 * surfaces read on white canvas; dark elevation tokens already carry their
 * own depth,
so dark surfaces return to the plain elevation ramp here.
 * Mirrors the card ambient reset at the end of components.css. */
[data-theme="dark"] .bw-feature-grid > .bw-feature-card,
[data-theme="dark"] .bw-pricing-tier,
[data-theme="dark"] .bw-testimonial,
[data-theme="dark"] .bw-cta--tint,
[data-theme="dark"] .bw-stat-band-section,
[data-theme="dark"] .bw-stat-band .bw-stat,
[data-theme="dark"] .bw-hero__media img,
[data-theme="dark"] .bw-portrait__image img,
[data-theme="dark"] .bw-product-chrome,
[data-theme="dark"] .bw-site-header {
  box-shadow: var(--bw-elevation-1);
}
[data-theme="dark"] .bw-stat-band .bw-stat--chrome-plain {
  box-shadow: none;
}
[data-theme="dark"] .bw-product-chrome--rear {
  box-shadow: var(--bw-elevation-1);
}
[data-theme="dark"] .bw-feature-grid > .bw-feature-card--link:hover,
[data-theme="dark"] .bw-feature-grid > .bw-feature-card--link:focus-visible {
  box-shadow: var(--bw-elevation-2);
}
/* Bordered cells deliberately carry no elevation; keep that on dark too. */
[data-theme="dark"] .bw-feature-grid--bordered > .bw-feature-card,
[data-theme="dark"] .bw-feature-grid--bordered > .bw-feature-card--link:hover,
[data-theme="dark"] .bw-feature-grid--bordered > .bw-feature-card--link:focus-visible {
  box-shadow: none;
}
[data-theme="dark"] .bw-pricing-tier--highlighted {
  box-shadow: var(--bw-elevation-3);
}

/* --- Portrait (#116): person-presentation marketing band -------------------
 * Constrained 3:4 image at real size; align places the image start/end from
 * md up. Document order stays copy then image (reading/tab order unchanged).
 */
.bw-portrait {
  display: flex;
  flex-direction: column;
  gap: var(--bw-space-6);
  margin-block: var(--bw-component-section-gap-marketing);
}

.bw-portrait__copy {
  display: flex;
  flex-direction: column;
  gap: var(--bw-space-3);
  min-inline-size: 0;
}

.bw-portrait__name {
  margin: 0;
  font-family: var(--bw-font-family-display);
  font-size: var(--bw-font-size-3xl);
  font-weight: var(--bw-font-weight-semibold);
  color: var(--bw-color-fg);
  line-height: var(--bw-font-line-height-tight);
  letter-spacing: -0.02em;
}

.bw-portrait__role {
  margin: 0;
  font-family: var(--bw-font-family-sans);
  font-size: var(--bw-font-size-lg);
  color: var(--bw-color-fg-muted);
  line-height: var(--bw-font-line-height-relaxed);
}

.bw-portrait__body {
  margin: 0;
  max-inline-size: var(--bw-size-max-width-prose);
  font-family: var(--bw-font-family-sans);
  font-size: var(--bw-text-body-md-size);
  color: var(--bw-color-fg);
  line-height: var(--bw-font-line-height-relaxed);
}

.bw-portrait__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--bw-space-3);
  margin-block-start: var(--bw-space-2);
}

.bw-portrait__image {
  min-inline-size: 0;
  max-inline-size: 22rem;
}

.bw-portrait__image :where(img, svg){
  display: block;
  inline-size: 100%;
  block-size: auto;
}

.bw-portrait__image img {
  aspect-ratio: 3 / 4;
  object-fit: cover;
  border-radius: var(--bw-radius-lg);
  box-shadow:
    var(--bw-elevation-1),
    0 4px 14px -4px oklch(0 0 0 / 0.1);
}

@media (width >= 48rem) {
  .bw-portrait {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: var(--bw-space-8);
    align-items: center;
  }

  .bw-portrait--align-start {
    grid-template-columns: minmax(0, 18rem) minmax(0, 1fr);
  }

  .bw-portrait--align-start .bw-portrait__image {
    grid-column: 1;
    grid-row: 1;
    max-inline-size: none;
  }

  .bw-portrait--align-start .bw-portrait__copy {
    grid-column: 2;
    grid-row: 1;
  }

  .bw-portrait--align-end {
    grid-template-columns: minmax(0, 1fr) minmax(0, 18rem);
  }

  .bw-portrait--align-end .bw-portrait__copy {
    grid-column: 1;
    grid-row: 1;
  }

  .bw-portrait--align-end .bw-portrait__image {
    grid-column: 2;
    grid-row: 1;
    max-inline-size: none;
  }
}

/* --- Bio (#116): compact person strip --------------------------------------
 * Article-foot / short about. Square image; name is not an h2 so it does not
 * inject a heading into an article outline.
 */
.bw-bio {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: var(--bw-space-4);
  margin-block: var(--bw-component-section-gap-marketing);
  padding-block: var(--bw-space-4);
  border-block-start: var(--bw-size-border-hairline) solid var(--bw-color-border);
}

.bw-bio__image {
  flex-shrink: 0;
  inline-size: var(--bw-space-16);
}

.bw-bio__image :where(img, svg){
  display: block;
  inline-size: 100%;
  block-size: auto;
}

.bw-bio__image img {
  aspect-ratio: 1;
  object-fit: cover;
  border-radius: var(--bw-radius-lg);
}

.bw-bio__copy {
  display: flex;
  flex-direction: column;
  gap: var(--bw-space-1);
  min-inline-size: 0;
  flex: 1 1 12rem;
}

.bw-bio__name {
  margin: 0;
  font-family: var(--bw-font-family-sans);
  font-size: var(--bw-text-body-md-size);
  font-weight: var(--bw-font-weight-semibold);
  color: var(--bw-color-fg);
  line-height: var(--bw-font-line-height-tight);
}

.bw-bio__role {
  margin: 0;
  font-family: var(--bw-font-family-sans);
  font-size: var(--bw-text-caption-size);
  color: var(--bw-color-fg-muted);
}

.bw-bio__body {
  margin: 0;
  margin-block-start: var(--bw-space-1);
  max-inline-size: var(--bw-size-max-width-prose);
  font-family: var(--bw-font-family-sans);
  font-size: var(--bw-text-body-sm-size);
  color: var(--bw-color-fg);
  line-height: var(--bw-font-line-height-relaxed);
}

.bw-bio__profile {
  margin: 0;
  margin-block-start: var(--bw-space-2);
}

.bw-bio__profile-link {
  display: inline-flex;
  align-items: center;
  /* 24x24 tap-target floor (WCAG 2.5.8 / axe.spec.mjs tap sweep), same text-run
   * pattern as .bw-listing-list__link. */
  min-block-size: 1.5rem;
  font-family: var(--bw-font-family-sans);
  font-size: var(--bw-text-body-sm-size);
  font-weight: var(--bw-font-weight-medium);
  color: var(--bw-color-accent);
  text-decoration: underline;
  text-underline-offset: 0.15em;
}

.bw-bio__profile-link:hover {
  color: var(--bw-color-accent-hover);
}

/* --- Proof collage (#572,
ADR-108 reusable marketing proof primitive) ------
 * Kit collage or page-as-proof stage. Tiles compose _preview_frame.html for
 * the surface guard. Not gallery chrome and not product-chrome.
 */
.bw-proof-collage {
  display: flex;
  flex-direction: column;
  gap: var(--bw-space-6);
  margin-block: var(--bw-component-section-gap-marketing);
}

.bw-proof-collage__intro {
  display: flex;
  flex-direction: column;
  gap: var(--bw-space-3);
  max-inline-size: var(--bw-size-max-width-prose);
}

.bw-proof-collage__heading {
  margin: 0;
  font-family: var(--bw-font-family-display);
  font-size: var(--bw-font-size-3xl);
  font-weight: var(--bw-font-weight-semibold);
  color: var(--bw-color-fg);
  line-height: var(--bw-font-line-height-tight);
  letter-spacing: -0.02em;
}

.bw-proof-collage__lede {
  margin: 0;
  font-family: var(--bw-font-family-sans);
  font-size: var(--bw-font-size-lg);
  color: var(--bw-color-fg-muted);
  line-height: var(--bw-font-line-height-relaxed);
}

.bw-proof-collage__grid {
  display: grid;
  gap: var(--bw-space-4);
  align-items: start;
}

.bw-proof-collage--collage .bw-proof-collage__grid {
  grid-template-columns: 1fr;
}

@media (width >= 40rem) {
  .bw-proof-collage--collage .bw-proof-collage__grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (width >= 64rem) {
  .bw-proof-collage--collage .bw-proof-collage__grid {
    grid-template-columns: 1.15fr 1fr 0.95fr;
  }
}

.bw-proof-collage--page .bw-proof-collage__grid {
  grid-template-columns: minmax(0, 1fr);
  max-inline-size: var(--bw-component-content-max-width-marketing);
  margin-inline: auto;
}

.bw-proof-collage__tile {
  display: flex;
  flex-direction: column;
  gap: var(--bw-space-2);
  min-inline-size: 0;
}

.bw-proof-collage__label {
  margin: 0;
  font-family: var(--bw-font-family-mono);
  font-size: var(--bw-font-size-xs);
  color: var(--bw-color-fg-muted);
  letter-spacing: 0.02em;
}

/* Directory / numbered index (#647) ---------------------------------------- */

.bw-directory-section {
  display: flex;
  flex-direction: column;
}

.bw-directory-section__intro {
  max-inline-size: var(--bw-size-max-width-prose);
  margin-block-end: var(--bw-space-6);
}

.bw-directory-section__heading {
  margin: 0;
  font-size: var(--bw-text-heading-section-size);
  font-weight: var(--bw-text-heading-section-weight);
  line-height: var(--bw-text-heading-section-line-height);
  letter-spacing: var(--bw-text-heading-section-tracking);
}

.bw-directory-section__lede {
  margin: var(--bw-space-2) 0 0;
  color: var(--bw-color-fg-muted);
  text-wrap: pretty;
}

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

.bw-directory__item {
  display: grid;
  grid-template-columns: auto 1fr;
  column-gap: var(--bw-space-4);
  row-gap: var(--bw-space-3);
  align-items: start;
  padding-block: var(--bw-space-5);
  border-block-end: var(--bw-size-border-hairline) solid var(--bw-color-border);
}

.bw-directory__item:first-child {
  border-block-start: var(--bw-size-border-hairline) solid var(--bw-color-border);
}

.bw-directory__item--mark {
  grid-template-columns: auto auto 1fr;
}

/* Overline role for the numeral (issue: typography is out of scope beyond
 * that role). tabular-nums keeps 01/10 aligned in a column. */
.bw-directory__number {
  font-family: var(--bw-text-overline-family);
  font-size: var(--bw-text-overline-size);
  font-weight: var(--bw-text-overline-weight);
  line-height: var(--bw-text-overline-line-height);
  letter-spacing: var(--bw-text-overline-tracking);
  text-transform: uppercase;
  color: var(--bw-color-fg-muted);
  font-variant-numeric: tabular-nums;
  margin-block-start: 0.35em;
}

.bw-directory__mark {
  display: flex;
  align-items: center;
  justify-content: center;
  inline-size: 2.5rem;
  block-size: 2.5rem;
  color: var(--bw-color-accent);
}

.bw-directory__icon {
  display: block;
  flex-shrink: 0;
}

.bw-directory__mark :where(img, svg){
  display: block;
  max-inline-size: 100%;
  max-block-size: 100%;
  object-fit: contain;
}

.bw-directory__main {
  display: flex;
  flex-direction: column;
  gap: var(--bw-space-2);
  min-inline-size: 0;
}

.bw-directory__title {
  margin: 0;
  font-family: var(--bw-text-heading-sm-family);
  font-size: var(--bw-text-heading-sm-size);
  font-weight: var(--bw-text-heading-sm-weight);
  line-height: var(--bw-text-heading-sm-line-height);
}

/* Title-only link (media-list pattern): full-row anchors would swallow the
 * meta column. min-block-size meets the WCAG 2.5.8 24px floor. */
.bw-directory__link {
  display: block;
  min-block-size: 1.5rem;
  color: inherit;
  text-decoration: none;
  transition: var(--bw-transition-colors);
}

.bw-directory__link:hover,
.bw-directory__link:focus-visible {
  color: var(--bw-color-accent);
  text-decoration: underline;
}

.bw-directory__body {
  margin: 0;
  color: var(--bw-color-fg-muted);
  font-size: var(--bw-text-body-md-size);
  line-height: var(--bw-text-body-md-line-height);
  text-wrap: pretty;
}

.bw-directory__meta {
  display: flex;
  flex-direction: column;
  gap: var(--bw-space-1);
  align-items: flex-start;
  grid-column: 1 / -1;
  color: var(--bw-color-fg-muted);
  font-size: var(--bw-text-caption-size);
  line-height: var(--bw-text-caption-line-height);
}

.bw-directory__version {
  font-variant-numeric: tabular-nums;
  color: var(--bw-color-fg);
  font-weight: var(--bw-font-weight-medium);
}

.bw-directory__status {
  color: var(--bw-color-fg-muted);
}

.bw-directory__install {
  font-family: var(--bw-font-family-mono);
  font-size: inherit;
  color: var(--bw-color-fg-muted);
}

@media (width >= 48rem) {
  .bw-directory__item {
    grid-template-columns: 3rem 1fr;
    column-gap: var(--bw-space-6);
    align-items: center;
  }

  .bw-directory__item--mark {
    grid-template-columns: 3rem auto 1fr;
  }

  .bw-directory__item--meta {
    grid-template-columns: 3rem 1fr minmax(10rem, 14rem);
  }

  .bw-directory__item--mark.bw-directory__item--meta {
    grid-template-columns: 3rem auto 1fr minmax(10rem, 14rem);
  }

  .bw-directory__meta {
    grid-column: auto;
    align-items: flex-end;
    text-align: end;
  }
}

/* --- Case / result list (#674): ruled work records,
not cards -------------
 * Figure | name + note,
hairline rows. Sibling of directory (ruled index)
 * and feature grid (cards): this is the checkable case list personal and
 * consulting presence pages need under a figures band.
 */

.bw-case-list-section__intro {
  max-inline-size: var(--bw-size-max-width-prose);
  margin-block-end: var(--bw-space-6);
}

.bw-case-list-section__heading {
  margin: 0;
  font-family: var(--bw-text-heading-section-family);
  font-size: var(--bw-text-heading-section-size);
  font-weight: var(--bw-text-heading-section-weight);
  line-height: var(--bw-text-heading-section-line-height);
  letter-spacing: var(--bw-text-heading-section-tracking);
}

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

.bw-case-list__item {
  border-block-end: var(--bw-size-border-hairline) solid var(--bw-color-border);
}

.bw-case-list__item:first-child {
  border-block-start: var(--bw-size-border-hairline) solid var(--bw-color-border);
}

/* The row is the interactive unit when linked: one tab stop,
content keeps
 * its own ink,
focus ring from the global :focus-visible rule. */
.bw-case-list__row {
  display: grid;
  gap: var(--bw-space-2);
  align-items: start;
  padding-block: var(--bw-space-5);
  color: inherit;
  text-decoration: none;
}

.bw-case-list__item--result > .bw-case-list__row {
  gap: var(--bw-space-4);
}

@media (width >= 40rem) {
  .bw-case-list__item--result > .bw-case-list__row {
    grid-template-columns: minmax(4.5rem, 7rem) 1fr;
    column-gap: var(--bw-space-6);
    align-items: baseline;
  }
}

.bw-case-list__result {
  font-family: var(--bw-text-heading-sm-family);
  font-size: var(--bw-text-heading-sm-size);
  font-weight: var(--bw-text-heading-sm-weight);
  line-height: var(--bw-text-heading-sm-line-height);
  font-variant-numeric: tabular-nums;
  color: var(--bw-color-fg);
}

.bw-case-list__main {
  display: flex;
  flex-direction: column;
  gap: var(--bw-space-1);
  min-inline-size: 0;
}

.bw-case-list__heading {
  margin: 0;
  font-family: var(--bw-text-heading-sm-family);
  font-size: var(--bw-text-heading-sm-size);
  font-weight: var(--bw-text-heading-sm-weight);
  line-height: var(--bw-text-heading-sm-line-height);
}

.bw-case-list__row--link .bw-case-list__heading {
  transition: var(--bw-transition-colors);
}

.bw-case-list__row--link:hover .bw-case-list__heading,
.bw-case-list__row--link:focus-visible .bw-case-list__heading {
  color: var(--bw-color-accent);
}

.bw-case-list__body {
  margin: 0;
  color: var(--bw-color-fg-muted);
  font-size: var(--bw-text-body-md-size);
  line-height: var(--bw-text-body-md-line-height);
  text-wrap: pretty;
}

/* Methods / quiet columns (#675) -------------------------------------------
 * Reading layout for method pages: no card surface,
no whole-column hit
 * target. Underline text link only. Sibling of feature-grid (product tiles)
 * and directory (numbered index),
not a variant of either. */

.bw-methods-section {
  display: flex;
  flex-direction: column;
}

.bw-methods-section__heading {
  margin: 0 0 var(--bw-space-8);
  font-family: var(--bw-text-heading-section-family);
  font-size: var(--bw-text-heading-section-size);
  font-weight: var(--bw-text-heading-section-weight);
  line-height: var(--bw-text-heading-section-line-height);
  letter-spacing: var(--bw-text-heading-section-tracking);
}

.bw-methods {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--bw-space-8) var(--bw-space-12);
}

@media (width >= 40rem) {
  .bw-methods {
    grid-template-columns: 1fr 1fr;
  }
}

.bw-methods__item {
  margin: 0;
  padding: 0;
  border: 0;
  background: transparent;
  box-shadow: none;
  display: flex;
  flex-direction: column;
  gap: var(--bw-space-2);
}

.bw-methods__name {
  margin: 0;
  font-family: var(--bw-text-heading-sm-family);
  font-size: var(--bw-text-heading-sm-size);
  font-weight: var(--bw-text-heading-sm-weight);
  line-height: var(--bw-text-heading-sm-line-height);
}

.bw-methods__stages {
  margin: 0;
  color: var(--bw-color-fg-muted);
  font-size: var(--bw-text-body-sm-size);
  line-height: var(--bw-text-body-sm-line-height);
}

.bw-methods__body {
  margin: 0;
  max-inline-size: 32ch;
  color: var(--bw-color-fg-muted);
  font-size: var(--bw-text-body-md-size);
  line-height: var(--bw-text-body-md-line-height);
  text-wrap: pretty;
}

/* Underline text link (not a whole-column anchor). min-block-size meets the
 * WCAG 2.5.8 24px floor,
same pattern as .bw-bio__profile-link. */
.bw-methods__link {
  display: inline-block;
  align-self: flex-start;
  margin-block-start: var(--bw-space-3);
  min-block-size: 1.5rem;
  font-weight: var(--bw-font-weight-semibold);
  color: var(--bw-color-fg);
  text-decoration: none;
  border-block-end: var(--bw-size-border-hairline) solid
    color-mix(in oklab, var(--bw-color-fg) 55%, transparent);
  transition: var(--bw-transition-colors);
}

.bw-methods__link:hover,
.bw-methods__link:focus-visible {
  border-block-end-color: var(--bw-color-fg);
}

/* Outcomes / quiet columns (#696) ------------------------------------------
 * Equal columns of outcome copy (heading + body), optional section foot
 * text link. Sibling of methods (#675): no stages, no per-column CTA, no
 * card chrome. */

.bw-outcomes-section {
  display: flex;
  flex-direction: column;
}

.bw-outcomes {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--bw-space-8) var(--bw-space-12);
}

@media (width >= 40rem) {
  .bw-outcomes {
    grid-template-columns: repeat(auto-fit, minmax(min(14rem, 100%), 1fr));
  }
}

.bw-outcomes__item {
  margin: 0;
  padding: 0;
  border: 0;
  background: transparent;
  box-shadow: none;
  display: flex;
  flex-direction: column;
  gap: var(--bw-space-2);
}

.bw-outcomes__name {
  margin: 0;
  font-family: var(--bw-text-heading-sm-family);
  font-size: var(--bw-text-heading-sm-size);
  font-weight: var(--bw-text-heading-sm-weight);
  line-height: var(--bw-text-heading-sm-line-height);
}

.bw-outcomes__body {
  margin: 0;
  max-inline-size: 32ch;
  color: var(--bw-color-fg-muted);
  font-size: var(--bw-text-body-md-size);
  line-height: var(--bw-text-body-md-line-height);
  text-wrap: pretty;
}

.bw-outcomes-section__foot {
  margin: var(--bw-space-8) 0 0;
}

/* Underline text link at section level (not a button row). */
.bw-outcomes-section__link {
  display: inline-block;
  min-block-size: 1.5rem;
  font-weight: var(--bw-font-weight-semibold);
  color: var(--bw-color-fg);
  text-decoration: none;
  border-block-end: var(--bw-size-border-hairline) solid
    color-mix(in oklab, var(--bw-color-fg) 55%, transparent);
  transition: var(--bw-transition-colors);
}

.bw-outcomes-section__link:hover,
.bw-outcomes-section__link:focus-visible {
  border-block-end-color: var(--bw-color-fg);
}

/* Soft-stage atmosphere (BR-BW-MKT-007, #570). Opt-in wash-and-grid
   behind first-band copy. Extra markup only when atmosphere="soft-stage".
   Colour is only --bw-color-stage-*; not a synonym for media_placement
   "behind".

   Stacking is isolation, not child-lifting. The root gets its own stacking
   context (position: relative; isolation: isolate) and .bw-stage sits at
   z-index: -1 inside it. In an isolated context a negative z-index child
   paints after the root's own background and before every in-flow or
   z-index: 0/auto descendant, so .bw-stage always sits behind the band's
   real content without promoting any named child to its own layer. Lifting
   .bw-hero__copy / .bw-hero__media / .bw-hero__decoration to z-index: 1
   (the earlier approach) instead gave .bw-hero__media a stacking context
   under media_placement="behind", where it already carries position:
   relative with no z-index of its own (relying on document order to stay
   under .bw-hero__copy at z-index: 1): the promoted, DOM-later media then
   painted over the copy and its scrim, and separately overrode
   .bw-hero__decoration's own position: absolute; z-index: 0. Isolation
   leaves every existing hero/section descendant's position and z-index
   untouched, so every media_placement value keeps its current stacking. */
.bw-hero--atmosphere-soft-stage,
.bw-section--atmosphere-soft-stage {
  position: relative;
  isolation: isolate;
}

.bw-stage {
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background-image:
    linear-gradient(180deg, var(--bw-color-stage-wash) 0%, transparent 62%),
    linear-gradient(var(--bw-color-stage-grid) var(--bw-size-border-hairline), transparent var(--bw-size-border-hairline)),
    linear-gradient(90deg, var(--bw-color-stage-grid) var(--bw-size-border-hairline), transparent var(--bw-size-border-hairline));
  background-size:
    100% 100%,
    var(--bw-space-8) var(--bw-space-8),
    var(--bw-space-8) var(--bw-space-8);
  mask-image: linear-gradient(180deg, black 0%, transparent 88%);
}

/* Product shot (BR-BW-MKT-008, #571). Elevation wrap for stills in
   hero / feature media slots. Distinct from .bw-preview-frame (ILL-026).
   Reuses the existing product-chrome elevation language. */
.bw-product-shot {
  margin: 0;
  inline-size: 100%;
  max-inline-size: 100%;
  overflow: hidden;
  border-radius: var(--bw-radius-xl);
  border: var(--bw-size-border-hairline) solid
    color-mix(in oklab, var(--bw-color-fg) 12%, var(--bw-color-surface-raised));
  background: var(--bw-color-surface-raised);
  /* The ambient literal matches .bw-product-chrome's own box-shadow above
     (same elevation precedent, not an untokenised one-off). */
  box-shadow:
    var(--bw-elevation-2),
    0 8px 28px -10px oklch(0 0 0 / 0.14);
}

.bw-product-shot__media > :where(img, video, iframe, svg) {
  display: block;
  inline-size: 100%;
  block-size: auto;
}

.bw-product-shot--window-light .bw-product-shot__window {
  display: flex;
  align-items: center;
  gap: var(--bw-space-2);
  min-block-size: var(--bw-size-control-height-sm);
  padding-inline: var(--bw-space-3);
  background: var(--bw-color-surface-sunken);
  border-block-end: var(--bw-size-border-hairline) solid
    color-mix(in oklab, var(--bw-color-fg) 10%, transparent);
}

.bw-product-shot__dot {
  inline-size: var(--bw-space-1-5);
  block-size: var(--bw-space-1-5);
  border-radius: var(--bw-radius-full);
  background: var(--bw-color-fg-muted);
}

.bw-feature-row__media .bw-product-shot,
.bw-hero__media .bw-product-shot {
  max-inline-size: 100%;
}

/* CSS-only reveal (BR-BW-MKT-009). Resting state is the visible layout;
   motion is an enhancement gated to no-preference. Never set opacity 0
   at rest. Reduced-motion floor is MOT-003 (!important). */
@keyframes bw-reveal-enter {
  from {
    opacity: 0.92;
    transform: translateY(var(--bw-space-2));
  }

  to {
    opacity: 1;
    transform: none;
  }
}

@media (prefers-reduced-motion: no-preference) {
  .bw-hero--reveal-enter,
  .bw-section--reveal-enter {
    animation: bw-reveal-enter var(--bw-duration-slow) var(--bw-ease-out) both;
  }
}

@media (prefers-reduced-motion: reduce) {
  .bw-hero--reveal-enter,
  .bw-section--reveal-enter {
    animation: none !important;
    transform: none !important;
  }
}

/* end of reveal block: test_reveal_css_references_only_motion_tokens_for_timing
   bounds its scan to this sentinel so a future append does not widen it. */


/* ==== layer: wave-b ==== */
/*
 * Wave B controls: CMDK, tags, OTP, calendar grid, file tree, kbd, segmented.
 * Complements vendored chrome (combobox, date-picker-chrome, modal, toast, stepper).
 */

.bw-kbd {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-inline-size: 1.5rem;
  padding: 0.1rem 0.4rem;
  border-radius: var(--bw-radius-sm, 0.25rem);
  border: var(--bw-size-border-hairline, 1px) solid var(--bw-color-border);
  background: var(--bw-color-surface-raised);
  color: var(--bw-color-fg-muted);
  font-family: var(--bw-text-code-family, ui-monospace, monospace);
  font-size: var(--bw-text-body-sm-size);
  line-height: 1.2;
}

.bw-segmented {
  display: inline-flex;
  padding: var(--bw-space-1);
  gap: var(--bw-space-1);
  border-radius: var(--bw-radius-md);
  border: var(--bw-size-border-hairline, 1px) solid var(--bw-color-border);
  background: var(--bw-color-surface);
}

.bw-segmented__item {
  appearance: none;
  border: 0;
  background: transparent;
  color: var(--bw-color-fg-muted);
  padding: var(--bw-space-2) var(--bw-space-3);
  border-radius: calc(var(--bw-radius-md) - 2px);
  font: inherit;
  cursor: pointer;
}

.bw-segmented__item[aria-pressed="true"],
.bw-segmented__item[data-bw-active] {
  background: var(--bw-color-surface-raised);
  color: var(--bw-color-fg);
  box-shadow: var(--bw-elevation-1, none);
}

.bw-segmented__item:focus-visible {
  outline: var(--bw-focus-ring-width, 2px) solid var(--bw-color-focus-ring, var(--bw-color-accent));
  outline-offset: 1px;
}

.bw-tags {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--bw-space-2);
  min-block-size: 2.5rem;
  padding: var(--bw-space-2);
  border: var(--bw-size-border-hairline, 1px) solid var(--bw-color-border-control, var(--bw-color-border));
  border-radius: var(--bw-radius-md);
  background: var(--bw-color-surface);
}

.bw-tags:focus-within {
  outline: var(--bw-focus-ring-width, 2px) solid var(--bw-color-focus-ring, var(--bw-color-accent));
  outline-offset: var(--bw-focus-ring-offset, 2px);
}

.bw-tags__chip {
  display: inline-flex;
  align-items: center;
  gap: var(--bw-space-1);
  padding: 0.15rem 0.5rem;
  border-radius: var(--bw-radius-full, 999px);
  background: var(--bw-color-surface-raised);
  border: var(--bw-size-border-hairline, 1px) solid var(--bw-color-border);
  font-size: var(--bw-text-body-sm-size);
}

.bw-tags__chip-remove {
  appearance: none;
  border: 0;
  background: transparent;
  color: var(--bw-color-fg-muted);
  cursor: pointer;
  padding: 0;
  line-height: 1;
}

.bw-tags__input {
  flex: 1 1 8rem;
  min-inline-size: 6rem;
  border: 0;
  background: transparent;
  color: var(--bw-color-fg);
  font: inherit;
  outline: none;
}

.bw-otp {
  display: flex;
  gap: var(--bw-space-2);
}

.bw-otp__digit {
  inline-size: 2.75rem;
  block-size: 2.75rem;
  text-align: center;
  font-size: var(--bw-text-heading-md-size);
  border: var(--bw-size-border-hairline, 1px) solid var(--bw-color-border-control, var(--bw-color-border));
  border-radius: var(--bw-radius-md);
  background: var(--bw-color-surface);
  color: var(--bw-color-fg);
}

.bw-otp__digit:focus-visible {
  outline: var(--bw-focus-ring-width, 2px) solid var(--bw-color-focus-ring, var(--bw-color-accent));
  outline-offset: var(--bw-focus-ring-offset, 2px);
}

.bw-password {
  position: relative;
  display: block;
}

.bw-password__toggle {
  position: absolute;
  inset-inline-end: var(--bw-space-2);
  inset-block-start: 50%;
  transform: translateY(-50%);
}

.bw-password .bw-input {
  padding-inline-end: 3rem;
}

.bw-calendar {
  display: grid;
  gap: var(--bw-space-3);
  min-inline-size: 16rem;
}

.bw-calendar__nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--bw-space-2);
}

.bw-calendar__title {
  font-weight: var(--bw-text-heading-sm-weight, 600);
  margin: 0;
}

.bw-calendar__grid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: var(--bw-space-1);
}

.bw-calendar__dow {
  text-align: center;
  color: var(--bw-color-fg-muted);
  font-size: var(--bw-text-body-sm-size);
  padding-block: var(--bw-space-1);
}

.bw-calendar__day {
  appearance: none;
  border: 0;
  background: transparent;
  color: var(--bw-color-fg);
  border-radius: var(--bw-radius-md);
  aspect-ratio: 1;
  cursor: pointer;
  font: inherit;
}

.bw-calendar__day:hover {
  background: var(--bw-state-hover-overlay, color-mix(in srgb, var(--bw-color-fg) 8%, transparent));
}

.bw-calendar__day[aria-selected="true"] {
  background: var(--bw-color-accent);
  color: var(--bw-color-accent-fg);
}

.bw-calendar__day[data-bw-in-range] {
  background: color-mix(in srgb, var(--bw-color-accent) 18%, transparent);
}

.bw-calendar__day:disabled,
.bw-calendar__day[aria-disabled="true"] {
  opacity: 0.35;
  cursor: not-allowed;
}

.bw-calendar__day:focus-visible {
  outline: var(--bw-focus-ring-width, 2px) solid var(--bw-color-focus-ring, var(--bw-color-accent));
  outline-offset: 1px;
}

.bw-file-tree {
  list-style: none;
  margin: 0;
  padding: 0;
  font-size: var(--bw-text-body-sm-size);
}

.bw-file-tree ul {
  list-style: none;
  margin: 0;
  padding-inline-start: var(--bw-space-4);
}

.bw-file-tree__item {
  display: flex;
  align-items: center;
  gap: var(--bw-space-2);
  padding: var(--bw-space-1) var(--bw-space-2);
  border-radius: var(--bw-radius-sm, 0.25rem);
  cursor: pointer;
}

.bw-file-tree__item:hover,
.bw-file-tree__item[data-bw-active] {
  background: var(--bw-state-hover-overlay, color-mix(in srgb, var(--bw-color-fg) 8%, transparent));
}

.bw-file-tree__item:focus-visible {
  outline: var(--bw-focus-ring-width, 2px) solid var(--bw-color-focus-ring, var(--bw-color-accent));
}

.bw-cmdk {
  position: fixed;
  inset: 0;
  z-index: var(--bw-z-modal);
  display: none;
  align-items: flex-start;
  justify-content: center;
  padding: 12vh var(--bw-space-4) var(--bw-space-4);
}

.bw-cmdk[data-bw-open] {
  display: flex;
}

.bw-cmdk__scrim {
  position: absolute;
  inset: 0;
  background: color-mix(in srgb, var(--bw-color-fg) 45%, transparent);
}

.bw-cmdk__panel {
  position: relative;
  z-index: 1;
  width: min(100%, 36rem);
  max-block-size: min(70vh, 28rem);
  display: flex;
  flex-direction: column;
  background: var(--bw-color-surface-raised);
  border: var(--bw-size-border-hairline, 1px) solid var(--bw-color-border);
  border-radius: var(--bw-radius-lg);
  box-shadow: var(--bw-elevation-3, 0 16px 48px color-mix(in srgb, var(--bw-color-fg) 20%, transparent));
  overflow: hidden;
}

.bw-cmdk__search {
  display: flex;
  align-items: center;
  gap: var(--bw-space-3);
  padding: var(--bw-space-3) var(--bw-space-4);
  border-block-end: var(--bw-size-border-hairline, 1px) solid var(--bw-color-border);
}

.bw-cmdk__input {
  flex: 1;
  border: 0;
  background: transparent;
  color: var(--bw-color-fg);
  font: inherit;
  font-size: var(--bw-text-body-lg-size);
  outline: none;
}

.bw-cmdk__list {
  list-style: none;
  margin: 0;
  padding: var(--bw-space-2);
  overflow: auto;
}

.bw-cmdk__group-label {
  padding: var(--bw-space-2) var(--bw-space-3);
  color: var(--bw-color-fg-muted);
  font-size: var(--bw-text-body-sm-size);
  font-weight: 600;
}

.bw-cmdk__item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--bw-space-3);
  padding: var(--bw-space-2) var(--bw-space-3);
  border-radius: var(--bw-radius-md);
  cursor: pointer;
}

.bw-cmdk__item[data-bw-active],
.bw-cmdk__item:hover {
  background: var(--bw-state-hover-overlay, color-mix(in srgb, var(--bw-color-accent) 12%, transparent));
}

.bw-cmdk__item:focus-visible {
  outline: var(--bw-focus-ring-width, 2px) solid var(--bw-color-focus-ring, var(--bw-color-accent));
}

.bw-cmdk__empty {
  padding: var(--bw-space-6);
  text-align: center;
  color: var(--bw-color-fg-muted);
}

.bw-cmdk__hint {
  display: flex;
  gap: var(--bw-space-1);
  color: var(--bw-color-fg-muted);
}

.bw-field__hint {
  margin: var(--bw-space-1) 0 0;
  color: var(--bw-color-fg-muted);
  font-size: var(--bw-text-body-sm-size);
}

.bw-sheet {
  /* Alias for mobile-oriented slide-over demos */
}
.bw-sheet.bw-slide-over--end .bw-slide-over__panel {
  max-inline-size: min(100vw, 24rem);
}


/* ==== layer: wave-e ==== */
/*
 * Wave E chrome: sidebar rail, menubar, context menu, theme toggle, icons, viz.
 */

.bw-app-shell {
  display: grid;
  grid-template-columns: auto 1fr;
  min-block-size: 100vh;
}

.bw-sidebar-rail {
  display: flex;
  flex-direction: column;
  gap: var(--bw-space-2);
  padding: var(--bw-space-4);
  border-inline-end: var(--bw-size-border-hairline, 1px) solid var(--bw-color-border);
  background: var(--bw-color-surface);
  min-inline-size: 14rem;
  transition: min-inline-size var(--bw-duration-fast, 0.15s) ease;
}

.bw-sidebar-rail[data-bw-collapsed="true"] {
  min-inline-size: 4.5rem;
}

.bw-sidebar-rail[data-bw-collapsed="true"] .bw-btn:not(.bw-sidebar-rail__toggle) {
  font-size: 0;
  padding-inline: 0.5rem;
  justify-content: center;
}

.bw-sidebar-rail__toggle {
  align-self: flex-start;
}

.bw-menubar {
  display: flex;
  flex-wrap: wrap;
  gap: var(--bw-space-1);
  padding: var(--bw-space-1);
  border: var(--bw-size-border-hairline, 1px) solid var(--bw-color-border);
  border-radius: var(--bw-radius-md);
  background: var(--bw-color-surface);
}

.bw-menubar__item {
  appearance: none;
  border: 0;
  background: transparent;
  color: var(--bw-color-fg);
  padding: var(--bw-space-2) var(--bw-space-3);
  border-radius: var(--bw-radius-sm, 0.25rem);
  font: inherit;
  cursor: pointer;
}

.bw-menubar__item:hover,
.bw-menubar__item[aria-expanded="true"] {
  background: var(--bw-state-hover-overlay, color-mix(in srgb, var(--bw-color-fg) 8%, transparent));
}

.bw-menubar__item:focus-visible {
  outline: var(--bw-focus-ring-width, 2px) solid var(--bw-color-focus-ring, var(--bw-color-accent));
}

.bw-context-menu {
  display: none;
  flex-direction: column;
  min-inline-size: 10rem;
  padding: var(--bw-space-1);
  border: var(--bw-size-border-hairline, 1px) solid var(--bw-color-border);
  border-radius: var(--bw-radius-md);
  background: var(--bw-color-surface-raised);
  box-shadow: var(--bw-elevation-2, 0 8px 24px color-mix(in srgb, var(--bw-color-fg) 16%, transparent));
}

.bw-context-menu[data-bw-open] {
  display: flex;
}

.bw-context-menu__item {
  appearance: none;
  border: 0;
  background: transparent;
  text-align: start;
  color: var(--bw-color-fg);
  padding: var(--bw-space-2) var(--bw-space-3);
  border-radius: var(--bw-radius-sm, 0.25rem);
  font: inherit;
  cursor: pointer;
}

.bw-context-menu__item:hover {
  background: var(--bw-state-hover-overlay, color-mix(in srgb, var(--bw-color-accent) 12%, transparent));
}

.bw-theme-toggle {
  appearance: none;
  border: var(--bw-size-border-hairline, 1px) solid var(--bw-color-border);
  background: var(--bw-color-surface);
  color: var(--bw-color-fg);
  border-radius: var(--bw-radius-md);
  padding: var(--bw-space-2) var(--bw-space-3);
  font: inherit;
  cursor: pointer;
}

.bw-theme-toggle:focus-visible {
  outline: var(--bw-focus-ring-width, 2px) solid var(--bw-color-focus-ring, var(--bw-color-accent));
}

.bw-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  inline-size: 1.25rem;
  block-size: 1.25rem;
  border-radius: var(--bw-radius-sm, 0.25rem);
  background: var(--bw-color-surface-raised);
  color: var(--bw-color-fg-muted);
  font-size: var(--bw-text-body-sm-size);
  line-height: 1;
}

.bw-icon--lg {
  inline-size: 2.5rem;
  block-size: 2.5rem;
  font-size: var(--bw-text-heading-md-size);
}

.bw-viz-card {
  display: flex;
  flex-direction: column;
  gap: var(--bw-space-3);
  padding: var(--bw-space-4);
  border: var(--bw-size-border-hairline, 1px) solid var(--bw-color-border);
  border-radius: var(--bw-radius-lg);
  background: var(--bw-color-surface);
}

.bw-viz-card__header,
.bw-viz-card__legend {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--bw-space-3);
}

.bw-viz-card__legend {
  color: var(--bw-color-fg-muted);
  font-size: var(--bw-text-body-sm-size);
}

.bw-viz-card__mount {
  min-block-size: 6rem;
  display: grid;
  place-items: end stretch;
  padding: var(--bw-space-2);
  background: var(--bw-color-surface-raised);
  border-radius: var(--bw-radius-md);
}

.bw-sparkline {
  display: flex;
  align-items: flex-end;
  gap: 0.35rem;
  block-size: 4rem;
}

.bw-sparkline > span {
  flex: 1;
  background: var(--bw-color-accent);
  border-radius: 2px 2px 0 0;
  opacity: 0.85;
}

.bw-sparkline > span:nth-child(1) { block-size: 35%; }
.bw-sparkline > span:nth-child(2) { block-size: 55%; }
.bw-sparkline > span:nth-child(3) { block-size: 40%; }
.bw-sparkline > span:nth-child(4) { block-size: 75%; }
.bw-sparkline > span:nth-child(5) { block-size: 60%; }
